Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPA with Implicit Grant - handling token refresh #1218

Open
jiristanglica opened this issue Jul 12, 2018 · 14 comments

Comments

Projects
None yet
@jiristanglica
Copy link

commented Jul 12, 2018

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: aws/amazon-cognito-auth-js#92

@elorzafe elorzafe added the Auth label Jul 31, 2018

@nihakue

This comment has been minimized.

Copy link

commented Jul 31, 2018

(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:

  • Cognito User pool with federated identities (Using a company internal SAML federated idp)
  • Using Cognito Federated Identity Pool with User pool set as IdP
  • Using @aws-amplify/auth@1.0.2 with oauth setup with the following call when Auth.currentAuthenticatedUser() throws:
const url = `https://${domain}/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectSignIn}&response_type=${responseType}&prompt=none`;
  window.location.assign(url);
  • Using @aws-amplify/api@1.0.2 to call API Gateway + Lambda (not using custom headers, since API gateway is using AWS_IAM authentication instead of User Pool)

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!

@nikkon226

This comment has been minimized.

Copy link

commented Sep 6, 2018

I am also having this same issue. Will a dev take a look at this please?

@jiristanglica

This comment has been minimized.

Copy link
Author

commented Sep 7, 2018

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:

  • Cognito does not support the prompt=none (specs here) query parameter on the /authorize endpoint
  • Cognito blocks iframes
  • Cognito is not OIDC compliant (see the second reply here)

So... 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.

@rajwilkhu

This comment has been minimized.

Copy link

commented Sep 18, 2018

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?

@stormit-vn

This comment has been minimized.

Copy link

commented Oct 5, 2018

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?

@maziarz

This comment has been minimized.

Copy link

commented Oct 16, 2018

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.

@claurin

This comment has been minimized.

Copy link

commented Nov 13, 2018

It seems implicit flow is only possible with Cognito user pools, not identity pools.

@claurin

This comment has been minimized.

Copy link

commented Nov 13, 2018

This SDK helps.

@jamesoflol

This comment has been minimized.

Copy link

commented Jan 23, 2019

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?

@maziarz

This comment has been minimized.

Copy link

commented Jan 23, 2019

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.

@vanpra1

This comment has been minimized.

Copy link

commented Feb 18, 2019

I am having a similar scenario and a similar issue. Has anyone solved this one yet?

@ruiyang

This comment has been minimized.

Copy link

commented Feb 26, 2019

@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?

@nikkon226

This comment has been minimized.

Copy link

commented Feb 26, 2019

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
    })
@jordanranz

This comment has been minimized.

Copy link
Contributor

commented Apr 26, 2019

This is still not supported. Marking as a feature request for syncing visibility with the Cognito team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.