Giter Club home page Giter Club logo

Comments (7)

ottokruse avatar ottokruse commented on August 27, 2024

but I wanted to open this issue before doing any work on it to start a discussion to see if it's inline with the goals of this library

Thanks for that, and to be very honest our ideas around this are still forming.

The (client piece) of this lib was started to make it easy to work with the custom auth flows - magic links, FIDO2.

But we did add several other things too (e.g. all the stuff in amazon-cognito-passwordless-auth/cognito-api) so that this lib could be a one-stop-shop for Cognito User Pools, so we didn't per se need e.g. @aws-amplify/auth anymore. But we only added things we were actually using at the time, e.g. that's why federation is not in it yet, simply because in our prototypes we seldom use that.

Can we ask some customer feedback from you. What's your idea, what would make sense to you? How would you like to use this library? Would you still also use e.g. @aws-amplify/auth?

from amazon-cognito-passwordless-auth.

mikemeerschaert avatar mikemeerschaert commented on August 27, 2024

tl;dr

I can use @aws-amplify/auth for the OAuth stuff and amazon-cognito-passwordless-auth for the passwordless signin, but I'd rather just use one library that handles everything so in my UI code I just rely on one set of hooks regardless of how the user signed in.

I'd be great if @aws-amplify/auth and cognito itself could support passwordless authentication out of the box, and seeing as how amplify is so mature and has so many other features that would actually be the preferred solution in a perfect world, but realistically if amazon-cognito-passwordless-auth could support OAuth it would solve my use case.

Long version

I'm working on a side project in my spare time, it's a group event planning app (here's the landing page to give you an idea). I think one of the main ways I'll attract users is by event organizers inviting them, so I want to make that process as quick and easy as possible so people actually create accounts and use the app. Ideally, some people who are invited to events also then use it to create their own events, invite more users, etc.

Here's a flowchart of the process I'm trying to create:
reunifund-auth

I'm also creating a website and a mobile app, the website is mainly so an organizer can more easily sit down and put in all the details for the event on a laptop or desktop, and the mobile app is mainly for the attendees so they can more easily participate in the decision making, stay up to date with communication and access their itinerary while at the event.

Unfortunately I started with Auth.js before realizing it didn't work on react-native and am now in the process of re-doing my whole auth system :(. In order to accomplish this, I need the following from an auth library.

  • Secure, i.e. limited implementation on my part
  • passwordless support (out of the box preferred)
  • OAuth support
  • Works on Web
  • Works on React Native
  • Isn't too expensive

The issue with @aws-amplify/auth is the manual implementation of the passwordless stuff that this library takes care of, doable, but not ideal.

Here's my decision matrix, the only current missing piece for me is either passwordless support out of the box in amplify, or OAuth support here.

auth-library

from amazon-cognito-passwordless-auth.

ottokruse avatar ottokruse commented on August 27, 2024

Many thanks for that elaborate answer.

I can see that even using this lib together with @aws-amplify/auth you miss convenience, because the signInStatus field in the usePasswordless hook would not "see" that OAuth based sign in and just say "NOT_SIGNED_IN". One way or the other you have some custom coding then to make things smooth.

Adding OAuth doesn't seem like a very big deal so we could definitely do it.

One of the goals of the lib here is to be a minimum layer around Cognito, so minimum that it would be within everyone's reach to just inspect the source code and see how it works––should not be too many layers of code to dig through. This is one of the reasons why we don't use many dependencies: all the code you need is here.

Anyway let's add OAuth.

Design-wise for the configurationwe can just mirror Amplify as you're suggesting:

// Same as Amplify.configure(), just removed few things (see below)
Passwordless.configure({
  ...,
  oauth: {
      domain: 'your_cognito_domain',
      scope: [
        'phone',
        'email',
        'profile',
        'openid',
        'aws.cognito.signin.user.admin'
      ],
      redirectSignIn: 'http://localhost:3000/',
      redirectSignOut: 'http://localhost:3000/',
    }
})

I did remove clientId and responseType: let's use code only, and use the same clientId as the one at top level (not sure why Amplify supports using a different one for OAuth, let's first see that use case begin requested).

const { signInWithProvider } = usePasswordless()
//...
<button onClick=() => { signInWithProvider({ provider: "Google" }) }>Sign in with Google

Upon calling signInWithProvider() we initiate the redirect to Cognito. We can steal the code from here: https://github.com/aws-samples/cloudfront-authorization-at-edge/blob/53014854b1370080a4493c8d8e60ee30c05368dc/src/lambda-edge/check-auth/index.ts#L64

And we parse the Oauth code from URL query parameters on app load (like magic link). We can steal the code from https://github.com/aws-samples/cloudfront-authorization-at-edge/blob/53014854b1370080a4493c8d8e60ee30c05368dc/src/lambda-edge/parse-auth/index.ts#L35

Have to make associated changed to the usePasswordless hook and the way the signInStatus field is derived.

All in all, it's not super complicated but definitely some work!

from amazon-cognito-passwordless-auth.

mikemeerschaert avatar mikemeerschaert commented on August 27, 2024

I think that interface looks straightforward, but I actually ended up implementing oauth2 over the weekend and using this library code as a reference on how to do the passwordless part. I'm using next.js and I decided to just put the tokens in cookies and do all the checking and verification logic in my middleware instead. I did implement an oauth2 request using the /authorize endpoint, but thanks for those code samples, I think there is some good examples in there of how to make my own code more secure (I naively was not using state, but will add that in).

One issue I did run into, which maybe doesn't need to be handled by this library, but probably worth mentioning if you decide to press forward with this, is how to implement signups. the 'login with...' button allows for very easy account creation in cognito. I'm sorry if I missed it but I don't think this library mentions how to handle the sign up flow, it all assumes that users already exist in cognito, but when implementing OAuth either sign ups from providers will have to be disabled, or this issue would need to be addressed.

When a user signs up with an external provider, a provider account is automatically created for them in cognito. If a user then comes back in the future and tries to enter their email to login, they will be told an email has been sent, (if user existence errors are turned off) but no email will actually be sent, despite the fact that they actually do have an account. This is because cognito passes userNotFound: true to the custom auth methods for federated users. I think normally this is good from a security standpoint as someone could take over a users account if they knew their email, however with passwordless logins that risk is removed because you can only log in if you have access to the email account the user is attempting to log in with.

I tried just creating a new user using the AdminCreateUserCommand when the user logs in and an existing idp account is found, but this ends up creating duplicate users, even if you use the AdminLinkProviderForUserCommand to link them.

I also tried then deleting the federated user to remove the duplicated account, but that then messes up the refresh token for the user if they're logged in elsewhere.

My current sub-optimal solution was to modify the preSignUp lambda trigger to do the following steps:

  • Check if the new user is coming from an external IdP
  • If no
    • return the event object and all the user to be created normally
  • If yes
    • Check if an account already exists
      • If yes
        • use AdminLinkProviderForUserCommand to link the existing user to the provider account
        • return message indicating user was linked, but don't return event to prevent creating duplicate user
      • If no
        • use AdminCreateUserCommand to create a new user
        • use AdminLinkProviderForUserCommand to link the new user to the provider account
        • return message indicating new user was created, but don't return event to prevent creating duplicate user

this then returns a lambda error to my application (via the callback url), and I prompt the user to click the "sign in with" button again, and now that the user exists it lets them in and issues tokens. If you decide to press forward with this and can come up with a better solution, please let me know. I just started working with cognito last week so it is very possible there are features I just haven't stumbled upon yet that would make this work much more smoothly.

from amazon-cognito-passwordless-auth.

ottokruse avatar ottokruse commented on August 27, 2024

Hi @mikemeerschaert ,

There's a couple of related but orthogonal concerns here, let me list out what I see:

  • Cognito native users (those for which Cognito is the source IDP, i.e. Cognito stores their password) vs federated users (they get created as a user in Cognito automatically upon federating-in, as you've seen, and every time they federate-in their attributes in Cognito are updated to match the ones incoming from the federated IDP)
  • How do you manage user sign up? If you federate the user pool to another IDP you're shifting that responsibility to them. So, if you federate to IDPs with public sign up such as Google, you potentially might as well allow public sign up for native users on your user pool (if you are allowing native users to begin with)
  • Custom auth flows (e.g. magic links) only work for native Cognito users (because they use the Cognito API directly and not OAuth)
  • You can link users in Cognito, which people often want: that way there is only 1 sub in Cognito. Typically you link on e-mail address. If linked, it doesn't matter anymore how the user signs in (native user via eg custom auth, or federated) they get mapped to the same sub in Cognito.
  • Linking users automatically is something users have struggled with as it requires coding custom triggers and you need to capture an error at the client and try again (as you do, you can automate it so that user doesn't even notice?). You have found this SO item no doubt, note there is a user there that says the error is no longer thrown since 2/21/23 (not sure if true): https://stackoverflow.com/questions/47815161/cognito-auth-flow-fails-with-already-found-an-entry-for-username-facebook-10155
  • If your user pool client prevents user existence errors, that means that when a non-existing user signs in, the system is supposed to behave no different then for an existing user with invalid credential: all this to prevent existing user enumeration by threat actors. This is why for magic links we have coded that the system just says "we have sent you a magic link" even though it has not. This can be pretty confusing and if you use magic links I'm slightly inclined to advise to not suppress user existence errors, or come up with a way to tell them they do not (might not) have an account. Otherwise they are waiting for a magic link that never comes, wondering what went wrong. Now in your case, this user is a federated user, does not exists as a native user, and in that case you cannot sign as native user (which is confusing, as there exists a user in the user pool with that email address, but Cognito doesn't allow you to initiate "normal" native sign in for federated users). Maybe a work around is, that upon federating in for the 1st time, you also create a native user, and link it--rereading your message it seems you tried this but it does not work.

Be all that as it may, what we want for this lib is purely the following:

  • to be able to initiate a redirect to the Cognito domain's /authorize endpoint, so the user can federate in and/or use the Cognito hosted UI to sign in
  • to be able to handle the redirect back and trade the authorization code for JWTs or show an appropriate error

Is that right, aligns to your request?

This surely is several hours of work, probably days to get right and test thoroughly.

from amazon-cognito-passwordless-auth.

mikemeerschaert avatar mikemeerschaert commented on August 27, 2024

@ottokruse First off thank you for taking the time to step through those issues. I do allow public signups - I use the aws-sdk to create a new cognito user on sign-up, and to handle the federated sign-ups I have actually implemented something similar to what you suggested.

  • If a user uses a federated IDP to login, I use the pre-sign-up lambda trigger to check for an existing user with that email address.
  • If they do exist, I simply link the federated login to the existing native cognito user and return undefined so a duplicate user doesn't get created.
    • To prevent a SpoofedMe attack, the email comming from the federated IDP has to be verified
  • If they don't exist, I create a new native cognito user and link the federated user that would have been created using the event that gets passed to the handler by cognito.

The issue is that in either case I have to return undefined, or an error from the lambda to prevent another duplicate user from being created at that point.

I thought about just doing all of this in my API code rather than the lambda by having the page hosted on the callback URL in my application call an API route that would create a new native cognito user, link it to the federated IDP user (by using their details from the ID token), and delete the newly created IDP user. That would allow me to handle this without the user even noticing, but the issue with that is the refresh token passed to the callback would then be invalid as the user it was issued for would be deleted. Also, I can't just handle the error from the lambda without the user noticing because it prevents access and refresh tokens from being issued, so they have to log in again.

As for the library, yes that is what I was requesting, with the added point of handling the tokens using the same hooks so the application code doesn't have to care if someone logged in natively or via a federated IDP.

That being said, I no longer need this personally since I was able to implement a solution in my application, so you can feel free to close this request, I don't want you to have to spend a bunch of time implementing it just for my case, but maybe revive it if others ask in the future?

from amazon-cognito-passwordless-auth.

ottokruse avatar ottokruse commented on August 27, 2024

Haha okay no worries this issue was helpful to advance thinking.

Let's close it for now and wait for other users to "want" this too.

About React Native, you mentioned using it, but alas this lib doesn't support Magic Links yet for RN, did you see that? But thay may be something we can easily add (perhaps you want to land a PR for it?)

from amazon-cognito-passwordless-auth.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.