Do you want to request a feature or report a bug?
A bug/feature request together perhaps. This has to do with both Amplify and Cognito.
What is the current behavior?
When using Cognito + Amplify with a SPA javascript app (Vue app in my case), the only proper way to implement authentication is to use the Implicit Grant (reasoning for example here). Using the Implicit Grant, Amplify is unable to automatically refresh the tokens after they expire.
The sources (like the one linked above) recommend to use a _silent authentication_ or _silent refresh_ to renew the tokens. The problem is, that Amplify doesn't provide any way to do this - and Congnito doesn't support it either (there is no prompt=none
support as described here). This makes it impossible to design the SPA properly and provide a nice user experience.
What is the expected behavior?
I would expect that there is a way to renew the tokens when using Implicit Grant somehow.
I realize that this is probably more Congnito issue than Amplify issue, but since you guys probably work quite close together, I hope you could maybe provide some tips how to make Amplify properly usable with a SPA app. Thank you!
There is a discussion about the same thing in another repo, without a solution: https://github.com/aws/amazon-cognito-auth-js/issues/92
(Putting this here to avoid duplication. I have the same problem and describe my setup below):
Hey, it seems as though Amplify only handles credential refresh if we are using the 'code' / Auth flow. What is the guidance for automatically refreshing tokens if we are using 'token' / implicit flow, and have no refresh token? At the moment, Amplify just falls back to making unauthenticated requests. I would love to be able to use implicit flow and not have to worry about my session expiring.
Ideally I would like to be able to do this silently without interrupting the user's workflows /refreshing the page. Is that something that is possible? I've found it very difficult to answer these questions, but would be happy to contribute to documentation if I could come up with a sanctioned solution.
For reference, my setup is:
Auth.currentAuthenticatedUser()
throws:const url = `https://${domain}/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectSignIn}&response_type=${responseType}&prompt=none`;
window.location.assign(url);
I'm seeing that after my session expires, amplify tries to refresh my access token using the refresh token, but there isn't one since I'm using token / implicit flow. Failing that, it seems to give up and use guest user instead, which my identity pool doesn't (and won't) support.
Thanks for your help!
I am also having this same issue. Will a dev take a look at this please?
A followup to my original post:
After spending quite some time on the issue together with my colleagues and investigating different options, we came to a conclusion that this is simply not possible with Cognito. It is not an Amplify issue but rather Cognito as is. There is currently no way to perform a silent refresh when using the implicit flow. The reasons are following:
prompt=none
(specs here) query parameter on the /authorize
endpointSo... a bummer. I wish somebody would come here and bashed me to the ground that I'm wrong and stupid and there is actually a way, but I'm afraid that won't happen.
I have the same issue as well. This is a real blocker for using Cognito with implicit flow. Has any one solved silent refresh using cognito?
I'm also seeking for a resolution to handle refresh token for implicit flow since we are working on the SPA. Using authorization code flow can retrieve refresh token but it doesn't good because of security concern. In this example, you will see the response_type=code is required, but it should only be used for the mobile app.
Many examples using authenticateUser API, as the result, a refresh token will be stored at the client site (on the browser) - it doesn't a best practice, does it? The authenicateUser API will be received the same response with the authorization code. How about if we implement a storage from the backend and apply authorization code (or using common authenticateUser API)? Do we have any disadvantage or any security concern we need to take care?
This is unfortunately still not possible. I spend some days last year to resolve this problem, but ended up with a cookie-based solution utilising passport.js with nuxt.js. Not exactly ideal, but there was simply no other way around implicit grant with Cognito without having to re-login the user.
It seems implicit flow is only possible with Cognito user pools, not identity pools.
This SDK helps.
I have long wondered, what is so bad about using auth code grant with an SPA? The only difference, from what I can see, is that the refresh token is stored in the user's browser. Obviously, if someone gets access to that refresh token, then they can impersonate the user for the duration of the refresh token. That refresh can then be revoked (globalSignout).
And by comparison, the alternative is that you're using auth code grant and storing/handling the refresh token in your own backend. In which case, you still have to provide your user with a cookie or token to store in their browser, which could be used to access your backend for the expiration-duration of that cookie or token.
What's the difference? Why is the first scenario any worse?
I have longer wondered, what is so bad about using auth code grant with an SPA? The only difference, from what I can see, is that the refresh token is stored in the user's browser. Obviously, if someone gets access to that refresh token, then they can impersonate the user for the duration of the refresh token. That refresh can then be revoked (globalSignout).
And by comparison, the alternative is that you're using auth code grant and storing/handling the refresh token in your own backend. In which case, you still have to provide your user with a cookie or token to store in their browser, which could be used to access your backend for the expiration-duration of that cookie or token.
What's the difference? Why is the first scenario any worse?
Its not really that bad, since client_secret can be hidden and the refreshToken ttl can go all the way down to 1day. As far as i can see there is a lack of PKCE support in the aws-amplify SDK for Authorization Code Grant, unless HostedUI is utilised, which is not an option in my use-case.
I am having a similar scenario and a similar issue. Has anyone solved this one yet?
@vanpra1 The current parsing url to get authentication function is bounded with the use of hosted UI. In my case, I don't use the hosted UI. So I use the following codes to parse the URL and create the Auth user session.
const oauth = {
// Domain name
domain : '[your domain].auth.[region].amazoncognito.com',
// Authorized scopes
scope : ['email'],
// Callback URL
redirectSignIn : 'http://localhost:8000/',
// Sign out URL
redirectSignOut : 'http://localhost:8000/',
// 'code' for Authorization code grant,
// 'token' for Implicit grant
responseType: 'code',
// optional, for Cognito hosted ui specified options
options: {
// Indicates if the data collection is enabled to support Cognito advanced security features. By default, this flag is set to true.
AdvancedSecurityDataCollectionFlag : true
}
}
Auth.configure({
Auth: {
oauth: oauth
},
});
const currentUrl = window.location.href;
(Auth as any)._cognitoAuthClient.parseCognitoWebResponse(currentUrl);
I am using typescript, the _cognitoAuthClient is a private field of AuthClass. I didn't find an easy way of creating CognitoAuth from amazon-cognito-auth-js. So I ended up above codes to ignore the type checking.
With code grant, it automatically hit the token endpoint to get access token, id token.
@powerful23 does the above codes look reasonable to you?
This is what I'm currently testing. Not sure if it's perfect or handles everything, but it seems to work. It's part of a Vue SPA using vue-router. This is called on every router page change. There are commented out lines that I used when figuring out everything.
return new Promise(function(resolve, reject) {
Auth.currentAuthenticatedUser()
.then((user) => {
// console.log({message: 'validated', user: JSON.parse(JSON.stringify(user))})
if (user.getSignInUserSession() != null) {
commit('SET_CURRENT_USER', user.getSignInUserSession())
resolve(user)
}
user.getSession(function(err, data) {
if (err) {
// console.log({message: "error getting session", error: err})
reject(err)
}
commit('SET_CURRENT_USER', data)
resolve(user)
})
})
.catch((err) => {
reject(err)
})
}).catch(() => {
// let stateDup = JSON.parse(JSON.stringify(state))
// console.log({message: 'error validating', error: err, state: stateDup})
return Auth.currentCredentials()
.then((credentials) => {
// console.log({message: 'credentials found', creds: credentials})
if (credentials.needsRefresh()) {
credentials.refresh(state.currentUser.refreshToken, (ret) => {
// console.log({message: 'refreshing access token', returnInfo: ret})
return Auth.currentAuthenticatedUser().then((user) => {
// console.log({message: 'found user after fetched credentials', user: JSON.parse(JSON.stringify(user))})
commit('SET_CURRENT_USER', user.getSignInUserSession())
return user
})
})
}
return Auth.currentAuthenticatedUser().then((user) => {
// console.log({message: 'found user after fetched credentials', user: JSON.parse(JSON.stringify(user))})
commit('SET_CURRENT_USER', user.getSignInUserSession())
return user
})
})
.catch(() => {
// console.log({message: 'refreshing error informaton', error: err})
commit('SET_CURRENT_USER', null)
return false
})
This is still not supported. Marking as a feature request for syncing visibility with the Cognito team.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
It seems implicit flow is only possible with Cognito user pools, not identity pools.
@claurin Can you elaborate on this? It seems to me this issue is affecting user pools.
as I read here is not safe to store refresh tokens on an SPA because all of the application code and storage is easily accessible.
What if I use code grant and delete the RefreshToken from the local storage and store it on the server? then, when the app whant to refresh the tokens, first it retrieve the refresh token, use it to refresh the other tokens, and then erase again from local storage? is not 100% safe, but no one could copy/paste the refresh token form localStorage.
as I read here is not safe to store refresh tokens on an SPA because all of the application code and storage is easily accessible.
What if I use code grant and delete the RefreshToken from the local storage and store it on the server? then, when the app whant to refresh the tokens, first it retrieve the refresh token, use it to refresh the other tokens, and then erase again from local storage? is not 100% safe, but no one could copy/paste the refresh token form localStorage.
Why not have the server return a secure cookie containing the refresh token. The spa need not then worry about submitting a refresh token, the cookie will be submitted by the browser automatically, just adapt your server side code to check for it..?
This is not really an issue related to amplify-js but rather Cognito. Quite a bummer that this has not been resolved within 2 years...
Most helpful comment
This is still not supported. Marking as a feature request for syncing visibility with the Cognito team.