Cognito User Pool: How to refresh Access Token using Refresh Token
Asked Answered
C

8

80

I am using Cognito user pool to authenticate users in my system. A successful authentication gives an ID Token (JWT), Access Token (JWT) and a Refresh Token. The documentation here, clearly mentions that the refresh token can be used to refresh access token, but does not mention how. My question is once my Access Token expires, how do I use the stored refresh token to refresh my access token again?

I searched through the JavaScript SDK and could not find any method to do the same. I definitely missed something.

Also I was thinking to do this via a Lambda function which takes in the access token and refresh token and responds with a refreshed access token. Would be great if anyone can throw some light on this.

Cuyp answered 25/5, 2016 at 16:31 Comment(1)
How to refresh the token for an ios-SDK of AWS?Crush
A
69

If you're in a situation where the Cognito Javascript SDK isn't going to work for your purposes, you can still see how it handles the refresh process in the SDK source:

You can see in refreshSession that the Cognito InitiateAuth endpoint is called with REFRESH_TOKEN_AUTH set for the AuthFlow value, and an object passed in as the AuthParameters value.

That object will need to be configured to suit the needs of your User Pool. Specifically, you may have to pass in your SECRET_HASH if your targeted App client id has an associated App client secret. User Pool Client Apps created for use with the Javascript SDK currently can't contain a client secret, and thus a SECRET_HASH isn't required to connect with them.

Another caveat that might throw you for a loop is if your User Pool is set to remember devices, and you don't pass in the DEVICE_KEY along with your REFRESH_TOKEN. The Cognito API currently returns an "Invalid Refresh Token" error if you are passing in the RefreshToken without also passing in your DeviceKey. This error is returned even if you are passing in a valid RefreshToken. The thread linked above illuminates that, though I do hope AWS updates their error handling to be less cryptic in the future.

As discussed in that thread, if you are using AdminInitiateAuth along with ADMIN_NO_SRP_AUTH, your successful authentication response payload does not currently contain NewDeviceMetadata; which means you won't have any DeviceKey to pass in as you attempt to refresh your tokens.

My app calls for implementation in Python, so here's an example that worked for me:

def refresh_token(self, username, refresh_token):
    try:
        return client.initiate_auth(
            ClientId=self.client_id,
            AuthFlow='REFRESH_TOKEN_AUTH',
            AuthParameters={
                'REFRESH_TOKEN': refresh_token,
                'SECRET_HASH': self.get_secret_hash(username)
                # Note that SECRET_HASH is missing from JSDK
                # Note also that DEVICE_KEY is missing from my example
            }
        )
    except botocore.exceptions.ClientError as e:
        return e.response
Antiphonary answered 29/11, 2016 at 21:8 Comment(10)
Thank you very much @Antiphonary for the informative post. I was not passing in DEVICE_KEY and I was getting 'Invalid Refresh Token' error, which is a very bad error handling on AWS side.Rewire
Nice answer, @afilbert--upvoted. (Unfortunately SO users started treating upvotes like personal spending money a few years ago. ;)Yippee
Thank you so much for the pointer about the device key! (I disabled device tracking and it works fine again).News
@Antiphonary Is SECRET_HASH the same thing as App client secret? Nevermind, the answer is "Yes" docs.aws.amazon.com/cognito-user-identity-pools/latest/…Godin
@nueverest the SECRET_HASH is required if the User Pool App has been defined with an App client secret, but they are not the same thing. The boto3 docs describe the SecretHash as the following: "A keyed-hash message authentication code (HMAC) calculated using the secret key of a user pool client and username plus the client ID in the message." boto3.readthedocs.io/en/latest/reference/services/… If you post a question on SO about how to create the SECRET_HASH, I'd be more than happy to share my solution as an answer.Antiphonary
@Antiphonary New question posted here: #44244941Godin
Has anyone faced an issue where. upon trying to refresh the token using the initiateAuth, a "Invalid login token. Token expired: 1502028294 >= 1502027330" occurs?Fayre
the headers aren't shown, but as of Jun 2018, they should be: Content-Type:application/x-amz-json-1.0 X-AMZ-TARGET:com.amazonaws.cognito.identity.idp.model.AWSCognitoIdentityProviderService.InitiateAuthAgathaagathe
FYI in order to be able to call REFRESH_TOKEN_AUTH after an ADMIN_NO_SRP_AUTH, you have to disable the device tracking. This was not very clear, hope it helps anybody!Barthol
I'm late for the party but I still have a question, I use USER_SRP_AUTH (also turn on Remember Device) for login and received the DeviceKey as well. However, when I tried to refresh_token I still got the Invalid Refresh Token although I already added the Device Key value to the AuthParameters. Did I do the wrong thing?Squeal
T
23

The JavaScript SDK handles refreshing of the tokens internally. When you call getSession to get tokens, in the absence of any valid cached access and id tokens the SDK uses the refresh token to get new access and id tokens. It invokes the user authentication, requiring user to provide username and password, only when the refresh token is also expired.

Trimer answered 25/5, 2016 at 18:33 Comment(4)
Mahesh, can you please give the code example there?Valene
hi Mahesh (and others that may need this) see link here github.com/aws/amazon-cognito-identity-js specifically use case 16. The returned session contains the tokens.Truitt
In this link, check the, "Get current user" section of their docs. When you call cognitoUser.getSession() the sdk will refresh your access/ ID tokens for you automatically, good to do before calling authenticated routes in your APIs. docs.aws.amazon.com/cognito/latest/developerguide/…Marius
Thanks Mahesh. Anyone looking for the confirmation on the above comment, please visit github.com/aws-amplify/amplify-js/blob/master/packages/…Inextirpable
W
13

Refreshing a session with the amazon-cognito-identity-js browser SDK; it mostly does it for you, and unless you're doing something unusual you won't need to handle the refresh token directly. Here's what you need to know:

Assume you have instantiated the user pool like this:

const userPool = new AmazonCognitoIdentity.CognitoUserPool({
  UserPoolId: USER_POOL_ID,
  ClientId: USER_POOL_CLIENT_ID
});

To find the last username authenticated, you would do this:

const cognitoUser = cognitoUserPool.getCurrentUser();

If it finds one, cognitoUser will be non-null, and you can do this, which will refresh your tokens behind the scenes if needed:

cognitoUser.getSession(function(err, data) {
  if (err) {
    // Prompt the user to reauthenticate by hand...
  } else {
    const cognitoUserSession = data;
    const yourIdToken = cognitoUserSession.getIdToken().jwtToken;
    const yourAccessToken = cognitoUserSession.getAccessToken().jwtToken;
  }
});

If you don't want these tokens persisted in local storage, you can:

cognitoUser.signOut();

The way it works is, after a successful authentication, the browser will store your JWT tokens, including that refresh token. It stores these in local storage in your browser by default, though you can provide your own storage object if you want. By default, the refresh token is valid for 30d, but it's a property (RefreshTokenValidity) of your UserPoolClient, which you can change. When you do the above, getSession() will first see whether the tokens you have in storage exist and are still valid; if not, it will try to use whatever refreshToken it finds there to authenticate you into a new session.

The documentation http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html indicates that the iOS and Android SDKs will do this for you, though I have not used those so can't vouch for that.

Weisman answered 8/10, 2017 at 2:35 Comment(1)
This solved hours of work trying to figure out how to use a IdentityPoolId to refresh a token with the amazon-cognito-identity-js library just to find out that all we had to do was call getSession and the identity pool id was not need! Thank you so much, I was trying user pool id's and other stuff and this fixed everything.Delaware
H
12

If you have a refresh token then you can get new access and id tokens by just making this simple POST request to Cognito:

POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token >
Content-Type='application/x-www-form-urlencoded'
Authorization=Basic base64(client_id + ':' + client_secret)

grant_type=refresh_token&
client_id=YOUR_CLIENT_ID&
refresh_token=YOUR_REFRESH_TOKEN

You will get the following response back:

HTTP/1.1 200 OK
Content-Type: application/json

{
   "access_token":"eyJz9sdfsdfsdfsd", 
   "id_token":"dmcxd329ujdmkemkd349r",
   "token_type":"Bearer", 
   "expires_in":3600
}

Keep in mind the Authorization header should contain the computed base64 aforementioned.

Hippogriff answered 3/2, 2019 at 18:35 Comment(3)
This would have been a concise solution if it did return a refresh_token, but it does not. The example you give comes from the AWS documentation (docs.aws.amazon.com/cognito/latest/developerguide/…) but they also offer a note: The refresh token is defined in the specification, but is not currently implemented to be returned from the Token Endpoint.Postulate
Thinks have changed :) The token endpoint returns refresh_token only when the grant_type is authorization_code.Spheroidicity
This answer is correct! I updated the HTTP response to reflect the fact that it doesn't return a new refresh token. Refreshing a token only gives you a new access token and a new id token. The refresh token used to renew them is valid for 30 days by default - if you didn't change it. And the refresh token itself cannot be renewed, but you can increase its validity up to 10 years (not something I'd recommend though).Vibraharp
G
6

I've been struggling with this as well in Javascript. Here's my solution, it is based on https://github.com/aws/amazon-cognito-identity-js BUT it doesn't rely on storage so you can use it in a lambda function if you wish. Edit: Fixed code, thanks to Crayons

const userPool = new AWSCognito.CognitoUserPool({
  UserPoolId: <COGNITO_USER_POOL>,
  ClientId: <COGNITO_APP_ID>
})

userPool.client.makeUnauthenticatedRequest('initiateAuth', {
  ClientId: <COGNITO_APP_ID>,
  AuthFlow: 'REFRESH_TOKEN_AUTH',
  AuthParameters: {
    'REFRESH_TOKEN': <REFRESH_TOKEN> // client refresh JWT
  }
}, (err, authResult) => {
  if (err) {
     throw err
  }
  console.log(authResult) // contains new session
})
Galang answered 25/9, 2017 at 14:21 Comment(1)
This works, however, AuthParameters format should be "REFRESH_TOKEN": <your_refresh_token>. Also note that if you have device tracking enabled, you must pass the clients device key in AuthParameters or turn device tracking off.Braille
J
5

Here is an example of how to do it with JavaScript on the server side using Node.js.

const AccessToken = new CognitoAccessToken({ AccessToken: tokens.accessToken });
const IdToken = new CognitoIdToken({ IdToken: tokens.idToken });
const RefreshToken = new CognitoRefreshToken({ RefreshToken: tokens.refreshToken });

const sessionData = {
  IdToken: IdToken,
  AccessToken: AccessToken,
  RefreshToken: RefreshToken
};
const userSession = new CognitoUserSession(sessionData);

const userData = {
  Username: email,
  Pool: this.userPool
};

const cognitoUser = new CognitoUser(userData);
cognitoUser.setSignInUserSession(userSession);

cognitoUser.getSession(function (err, session) { // You must run this to verify that session (internally)
  if (session.isValid()) {
    // Update attributes or whatever else you want to do
  } else {
    // TODO: What to do if session is invalid?
  }
});

You can see a complete working example in my blog post How to authenticate users with Tokens using Cognito.

Joceline answered 28/1, 2018 at 17:33 Comment(0)
I
4

Using NodeJS aws-sdk and a bit of Promise you can await authentication using Refresh Token with initiateAuth as follows:

const {CognitoIdentityServiceProvider} = require('aws-sdk');

const initiateAuth = (ClientId, REFRESH_TOKEN, DEVICE_KEY) =>
    new Promise((resolve, reject) => {
        const CISP = new CognitoIdentityServiceProvider();

        CISP.initiateAuth(
            {
                ClientId, // Cognito App Client Id
                AuthFlow: 'REFRESH_TOKEN_AUTH',
                AuthParameters: {
                    REFRESH_TOKEN,
                    DEVICE_KEY
                }
            },
            (err, data) => {
                if (err) {
                    return reject(err);
                }

                resolve(data);
            }
        );
    });

// ------ Usage ------ //

(async () => {
    const tokens = await initiateAuth('mY4pps3cR3T', '<R3FR3SHT0K3N>');

    console.log('Tokens', tokens);

    const {AuthenticationResult: {AccessToken, IdToken, ExpiresIn, TokenType}} = tokens;
})()

Keep in mind that if Device tracking is enabled you should pass a device key otherwise you can receive Invalid refresh token error.

Ickes answered 1/10, 2019 at 14:19 Comment(0)
C
0

Some of the answers here were very helpful and allowed me to track the method

const user = await Auth.currentUserPoolUser(); 

which gets the user and refreshes the access token if needed. Additionally it is already promisified.

Carpathoukraine answered 11/8, 2023 at 12:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.