amazon-archives / amazon-cognito-auth-js

The Amazon Cognito Auth SDK for JavaScript simplifies adding sign-up, sign-in with user profile functionality to web apps.
Apache License 2.0
424 stars 232 forks source link

Refresh access and id tokens in a React/Angular SPA #92

Open ravenscar opened 6 years ago

ravenscar commented 6 years ago

Summary

I would say that without Cognito implementing prompt=none on the /oauth2/authorize endpoint, and whilst the cognito cookie on <your_domain>.auth.<region>.amazoncognito.com/ expires after 60 minutes instead of 30 days (or what is set for the user pool) it is unsuitable to be used as an out-of-the-box solution for Single Page Applications.

Details

The recommended OAuth2 flow for a Single Page Application (SPA) is the Implicit Grant. This flow, by design, does not issue a refresh token to the web app.

There are many issues in this project around refreshing tokens that seem to be resolved by switching to Authorization Code Grant instead, which is OK for some native apps and some non-SPA web apps (e.g. where there are round trips to a server) but is unsuitable for SPA apps.

It is considered unsafe and bad practice for a web application to store a refresh token in session storage, local storage, or in javascript accessible memory as this opens up the app to many attacks which could result in the refresh token being exposed to a malicious third party which is now able to obtain ID/API tokens for the user.

The OpenID Provider (OP) usually sets an http only secure cookie on user login, and this is used for refreshing in this case the OP is awscognito and the cookie would be set on <your_domain>.auth.<region>.amazoncognito.com/. Cognito does set this cookie.

The typical process for refreshing tokens in an SPA is to call the OP's authorize endpoint with a prompt=none in the query string. This would normally occur in an embedded iframe in the SPA so as to not alter the UI and would either return new tokens which can be stored or an error, which deletes the tokens.

This is not a standard or requirement as it is up the the OpenID Provider to determine how a user is authorized, however if this is not implemented and it means that an SPA must choose to log in whenever the access token expires, or to adopt poor practices and store refresh tokens in the SPA (there is another option where we could have our own server which stores the refresh tokens and can refresh api/id tokens and deliver them to the SPA).

This partially works using cognito hosted UI, there is a cognito cookie stored when you login at <your_domain>auth.<region>.amazoncognito.com/login?client_id=<your_client_id>&response_type=token&redirect_uri=<your_redirect> and if you subsequently call <your_domain>auth.<region>.amazoncognito.com/oauth2/authorize?client_id=<your_client_id>&response_type=token&redirect_uri=<your_redirect> it will redirect with new tokens, with updated timestamps, without requiring interaction.

Unfortunately the cognito token expires after 60 minutes so it can only be used to extend the session to a maximum of 120 minutes. Also since the prompt parameter is not implemented there is no way for the iframe to get an error so it cannot be used in an iframe.

References

hhp21 commented 6 years ago

Any thoughts on this? It seems to me that it makes implicit grant totally impractical to use.

a053a commented 6 years ago

I'd prefer if AWS allows the configuration the id_token and session cookie expiry TTL based on your own requirements.

cervantek commented 6 years ago

This seems like a deal breaker for using Cognito User Pools for SPA development. I am seeing the cognito cookie valid for 8 hours though now, so that is a little better. I agree though that without the prompt=none option, the re-authorize cannot be transparent to the user.

ppasmanik commented 6 years ago

Why there is no response from aws team on this??? This is making implicit flow absolutely useless. I am not sure how @cervantek got cognito cookie to work for 8 hours. I see only 1 hour, so you are stuck with maximum 2 hour for tokens if you do a workaround and make a call to oauth2/authorize endpoint for hosted login.

luomavaltteri commented 6 years ago

Wondering the same thing! This is a MUST feature for any real-world use.

gercorr-gw commented 5 years ago

This seems to be a vital feature for user experience. @cervantek do you have details on how to increase the cognito cookie timeout? Though even that would not fully solve the issue.

nihakue commented 5 years ago

The document that you referenced assumes that refresh tokens last indefinitely, but I believe that with Cognito User Pools you can configure the refresh token TTL

I don't know what security experts think about e.g. reducing the refresh token TTL to 1 day and storing it in the local storage of a SPA, but it seems no worse than increasing the TTL of the access token.

a053a commented 5 years ago

AWS needs to implement this properly by giving us the ability to configure cognito cookie timeout. If implemented, then using a proper Oidc JS library would get a new id_token and token periodically.

Beretta1979 commented 5 years ago

We need this feature too

ppasmanik commented 5 years ago

I agree with @nihakue . After talking to AWS team we went with that solution - setting refresh token ttl to 1 day, and doing refresh token every hour with auth code flow.

a053a commented 5 years ago

@ppasmanik @nihakue Although this is technically possible, it’s considered bad practice to store refresh tokens for SPA apps. AWS really needs to close in on this so they don’t keep putting applications at risk.

rajwilkhu commented 5 years ago

Could someone from the AWS team respond to this? Came across this issue whilst trying to implement silent refresh. I am now considering moving away from Cognito to something that supports the implicit flow better for SPAs.

luomavaltteri commented 5 years ago

Could someone from the AWS team respond to this? Came across this issue whilst trying to implement silent refresh. I am now considering moving away from Cognito to something that supports the implicit flow better for SPAs.

@rajwilkhu It's been very quiet for a long time. Choose something that supports silent refresh.

maziarz commented 5 years ago

While this may sound (very) crazy. What if the refreshToken had a more granular TTL, such as minutes or hours. Wouldn't this get us slightly closer to a silent auth/refresh solution?

a053a commented 5 years ago

@maziarz while it might help, it's actually the wrong solution. Refresh tokens shouldn't be used in SPA apps; rather, use the session cookie controlling the refresh, e.g. cognito to use session cookie to see if session cookie is still valid and reissue another token and update session cookie if warranted.

maziarz commented 5 years ago

@salmonz its not that i disagree, i ran into this problem 1.5 years ago and ended up implementing Cognito with passport.js in the back utilising secure cookies. But eventually it removed all benefits from being truly "serverless" and having low maintenance on a SPA. I do not believe that anyone has solved this issue in an elegant way, even auth0 is relying on a valid SSO session in order for this to actually work with prompt=none. It does not look like Cognito team are going to introduce a cookie mechanism any time soon..

a053a commented 5 years ago

AWS - do you have an update on this issue? Seems to me that the sign-in cookie expiry should be configurable in the AWS portal for a cognito instance, or per client configuration.

adlaika commented 5 years ago

Any update on this?

carvercj commented 5 years ago

Any updates??

vanpra1 commented 5 years ago

Any progress on this?

valecarlos commented 5 years ago

any updates?

AshUK commented 5 years ago

+1 for silent renew support on implicit grants

dpistole commented 5 years ago

Sub'd, this is a deal breaker for using cognito user pools with SPA, would be nice to hear something from AWS.

sjbthfc2 commented 5 years ago

AWS: please give us some sort of update on this issue? What workarounds can you propose for SPA apps, do you have any plans to deal with this? The silence is deafening!

jeremiahsmall commented 5 years ago

I guess we'll have to use Auth0 again.

sebastienfi commented 5 years ago

The typical process for refreshing tokens in an SPA is to call the OP's authorize endpoint with a prompt=none in the query string. This would normally occur in an embedded iframe in the SPA so as to not alter the UI and would either return new tokens which can be stored or an error, which deletes the tokens.

The iFrame flow does not work on the latest Safari. Auth0 warns about it here: https://auth0.com/docs/api-auth/token-renewal-in-safari

So I guess all we have left is

there is another option where we could have our own server which stores the refresh tokens and can refresh api/id tokens and deliver them to the SPA

sebastienfi commented 5 years ago

I agree with @nihakue . After talking to AWS team we went with that solution - setting refresh token ttl to 1 day, and doing refresh token every hour with auth code flow.

@ppasmanik Can you please elaborate?

ravenscar commented 5 years ago

@sebastienfi

I believe they are using the Authorization Code Grant instead of the Implicit Grant to get a code that can be exchanged for a refresh token, storing the refresh token in the SPA, and refreshing the access/id tokens hourly.

Storing the refresh token client side in a web app would normally be considered very insecure however they have limited the lifetime of the refresh token to 24 hours, which is a tradeoff they have had to make as AWS provides no silent auth.

I guess the upshot is that users would have to authenticate daily rather than hourly.

sebastienfi commented 5 years ago

@ravenscar Thank you for mentioning me - and for these clarifications!

ppasmanik commented 5 years ago

@ravenscarhttps://github.com/ravenscar You are correct. The cognito-auth-js library provides various APIs to get refresh token, session, etc. It stores them in local storage. We wrote a small library that wraps amazon-cognito-auth-js and provides React components that know how to handle both types of the flows depending on configuration, perform refresh of tokens using oauth2/authorize endpoint of hosted cognito at configurable intervals (between 10 and 55 minutes depending on user roles). The minimum refresh token ttl is 1 day for cognito pool and it is sufficient for our users. You can set it to longer if you need. We also integrated idle timeout into that library that logs a user out after a configurable time interval.


From: Sébastien Fichot notifications@github.com Sent: Tuesday, April 23, 2019 4:39 AM To: aws/amazon-cognito-auth-js Cc: Pasmanik, Paul; Mention Subject: Re: [aws/amazon-cognito-auth-js] Refresh access and id tokens in a React/Angular SPA (#92)

@ravenscarhttps://github.com/ravenscar Thank you for mentioning me - and for these clarifications!

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/aws/amazon-cognito-auth-js/issues/92#issuecomment-485700128, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AA2674YCPIHPLQLU5RAIQXTPR3DKTANCNFSM4EROSASA.


The information contained in this electronic transmission is intended only for the use of the recipient and may be confidential and privileged. Unauthorized use, disclosure, or reproduction is strictly prohibited and may be unlawful. If you have received this electronic transmission in error, please notify the sender immediately. In order to make sure you receive our emails with the latest news on Dante, please add info@danteinc.com to your contact list.

jeremiahsmall commented 5 years ago

Storing secrets in local storage is the entire problem. The workarounds described are too insecure for some to consider.

ppasmanik commented 5 years ago

Yes, storing secrets in local storage is not a good practice, however, it is questionable whether refresh token with validity limited to a set number of hours is really a secret. You need both unexpired token and refresh token to renew a token. If someone is able to get hold of an unexpired token, he will be able to get in. If someone is able to get hold of both an unexpired token and refresh token, he will be able to refresh the token several times up to a refresh token expiration time. In both cases, something or somebody needs to hack something to get hold of tokens from local storage.


From: Jeremiah Small notifications@github.com Sent: Friday, May 3, 2019 7:06 PM To: aws/amazon-cognito-auth-js Cc: Pasmanik, Paul; Mention Subject: Re: [aws/amazon-cognito-auth-js] Refresh access and id tokens in a React/Angular SPA (#92)

Storing secrets in local storage is the entire problem. The workarounds described are too insecure for some to consider.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/aws/amazon-cognito-auth-js/issues/92#issuecomment-489266305, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AA26745TF4AD2MUSI4NHATDPTTAODANCNFSM4EROSASA.


The information contained in this electronic transmission is intended only for the use of the recipient and may be confidential and privileged. Unauthorized use, disclosure, or reproduction is strictly prohibited and may be unlawful. If you have received this electronic transmission in error, please notify the sender immediately. In order to make sure you receive our emails with the latest news on Dante, please add info@danteinc.com to your contact list.

leotohill commented 5 years ago

Any thoughts on this? It seems to me that it makes implicit grant totally impractical to use.

Actually, I think you mean that the auth code grant is impractical. Only the auth code grant provides a refresh token. Also, the implicit grant is inadvisable these days.

leotohill commented 5 years ago

@ppasmanik , "You need both unexpired token and refresh token to renew a token." Actually, that's not correct. You need only the refresh token. See section 6 of https://tools.ietf.org/html/rfc6749, also https://www.oauth.com/oauth2-servers/making-authenticated-requests/refreshing-an-access-token/

ravenscar commented 5 years ago

Actually, I think you mean that the auth code grant is impractical. Only the auth code grant provides a refresh token. Also, the implicit grant is inadvisable these days.

@leotohill not sure I follow you here. Auth code grant cannot be used securely in a SPA because it returns a refresh token which cannot be securely stored.

When implemented correctly by an auth provider implicit grant can allow refresh without the token being stored in a place inaccessible to javascript, this is desirable if you are not auditing every node module in your dependency tree.

Do you have a recent source saying why this is suddenly inadvisable? What is the alternative "safer" way of storing refresh tokens in an app where all javascript code cannot be trusted.

leotohill commented 5 years ago

@ravenscar yes, this ietf best practice document.. The auth code grant is a more secure flow, and the refresh token can (should be) discarded, or not issued in the first place if that's an option.

Not sure i fully understand what you are saying about the auth provider "allow refresh without...", but could this work anyway, even if the token is acquired via the auth code flow?

I too find that this advice makes things challenging. I'm thinking of setting up an server where the refresh token can be stored server-side, and the server executes the refresh operation, returning the new token to the client. This would require a cookie-based session with the server.

ravenscar commented 5 years ago

@leotohill interesting read but not sure it's worth trading implicit grant headaches for CORS headaches. Maybe in 5 years if CORS allows multiple origins.

I am not sure if the silent refresh would work with auth-code flow. I can't see a reason it wouldn't, but I guess since it's optional it's up to the implementer.

Your idea seems to be re-implementing the out-of-the-box functionality of other auth providers such as Auth0. I'd advise just to use their services and forget that cognito user pools exists.

Even when you get past this hurdle you have a dozen more, such as discovering that the sign-up and log-in emails are case sensitive, so joe@gmail.com can't login if he signed up as Joe@gmail.com.

Nothing AWS has produced has disappointed me as much as cognito user pools. Shouldn't event be in beta.

ajhool commented 4 years ago

@ravenscar @sjbthfc2 @ppasmanik @sebastienfi

The last commit to this repo was 6 months ago. Please submit a ticket to the Support Center from your AWS Console (top right -> Support -> Suppor Center) if you want to actually get AWS' attention on this issue -- and they have a contractual obligation to respond to you within 24 hours (or less if this is a critical vulnerability and you are on a higher support plan)

I submitted a ticket to AWS support center, which hopefully has a channel to a different Cognito team than this open-source sdk team.

AWS makes a(n arbitrary IMO) distinction between open-sourced and closed-source/backend services, so I urge everybody else to open support tickets. Particularly if you work at a large enough company that you have the top-tier of customer service or work at a mid-large company and depend on cognito.

I agree with the other sentiment that cognito is a fundamental service, a fundamentally flawed service, and the one that makes me wonder if AWS has stretched itself too thin but the alternative providers in this area all have issues of their own, too.

evbo commented 4 years ago

fyi, AWS' official client library for cognito was able to confirm that better support for SPA authentication use-cases is in the Cognito team's backlog: https://github.com/aws-amplify/amplify-js/issues/3436#issuecomment-538400098