Skip to content

Instantly share code, notes, and snippets.

@damonmaria
Last active May 31, 2021 10:13
Show Gist options
  • Save damonmaria/d4daac2dac8014cffd9d5872355a4ad4 to your computer and use it in GitHub Desktop.
Save damonmaria/d4daac2dac8014cffd9d5872355a4ad4 to your computer and use it in GitHub Desktop.
Keeping Cognito user pool and AWS tokens refreshed in browser, symptoms if you need this is the error: "Invalid login token. Token expired: 1446742058 >= 1446727732"
import AWS from 'aws-sdk/global'
import eventEmitter from 'event-emitter'
import differenceInMilliseconds from 'date-fns/difference_in_milliseconds'
import minDate from 'date-fns/min'
// Set this to match your setup
const env = {
awsRegion: XXXX,
identityPoolId: XXXX,
userPoolId: XXXX,
}
AWS.config = new AWS.Config({
region: env.awsRegion,
})
export default eventEmitter({
_refreshTimer: null,
_cognito: null,
// Trigger everything from here.
// For example: call after cognitoUser.getSession(), or cognitoUser.authenticateUser() onSuccess callback
async updateCognitoSession(user, session) {
this._cancelRefresh() // Immediately cancel any refresh, so we won't have 2 calls to this method happening at the same time
this._cognito = { user, session }
// Apply cognito session to AWS credentials so we can use all AWS services
AWS.config.update({
credentials: new AWS.CognitoIdentityCredentials({
IdentityPoolId: env.identityPoolId,
Logins: {
[`cognito-idp.${env.awsRegion}.amazonaws.com/${env.userPoolId}`]: session.getIdToken().getJwtToken(),
},
})
})
await AWS.config.credentials.getPromise() // This will refresh AWS credentials as we've just updated them
this._credentialsSet()
},
signOut() {
this._cognito && this._cognito.user.signOut()
this._cacnelSession()
this.emit('signOut')
},
async refreshSession() {
if (this._cognito) {
try {
const oiriginal = this._cognito
const newSession = await new Promise((resolve, reject) => {
this._cognito.user.refreshSession(oiriginal.session.getRefreshToken(), (err, result) => {
if (err) {
reject(err)
} else {
resolve(result)
}
})
})
if (this._cognito === oiriginal) { // Don't conitue if someone else has updated _cognito in the meantime
await this.updateCognitoSession(oiriginal.user, newSession) // Calls _credentialsSet which schedules next refresh
}
} catch (e) {
if (e.code === 'NotAuthorizedException') {
this._credentialsInvalid()
} else {
console.error(e)
this._scheduleRefresh() // Try again
}
}
}
},
_credentialsSet() {
this.emit('set', AWS.config.credentials) // We have new credentials, whoever needs them should listen
this._scheduleRefresh()
},
_credentialsInvalid() {
this._cacnelSession()
this.emit('invalid') // Listen to this and get the user to log in again
},
_scheduleRefresh() {
if (this._refreshTimer == null && this._cognito) {
const msToExpiry = differenceInMilliseconds(
minDate(
this._cognito.session.getIdToken().getExpiration() * 1000,
this._cognito.session.getAccessToken().getExpiration() * 1000,
AWS.config.credentials.expireTime,
),
new Date(),
)
const adjustedMs = Math.max(msToExpiry / 2, 10000) // Ensure we don't call too often, and don't go negative if expiry already happened
console.log(`${new Date()}: Scheduling token refresh in ${adjustedMs / 1000}s`)
this._refreshTimer = setTimeout(
() => {
this._refreshTimer = null // So it is only set when there is a timer running
this.refreshSession()
},
adjustedMs,
)
}
},
_cacnelSession() {
this._cognito = null
this._cancelRefresh()
},
_cancelRefresh() {
clearTimeout(this._refreshTimer)
this._refreshTimer = null
}
})
@david114
Copy link

david114 commented Mar 23, 2018

Thats a lot of code, i simplified a bit.
If you receive Invalid login token. Token expired: 1446742058 >= 1446727732 error, just call this function:

 refresh_token = session.getRefreshToken(); // session is the callback from calling cognitoUser.getSession()
 var update_session = function () {
    cognitoUser.refreshSession(refresh_token, (err, session) => {
      if (err) {
        console.log(err);
      }
      else {
        AWS.config.credentials.params.Logins['cognito-idp.<REGION>.amazonaws.com/<USERPOOL-ID>'] = session.getIdToken().getJwtToken();
        AWS.config.credentials.refresh((err) => {
          if (err) {
            console.log(err);
          }
          else {
            console.log("TOKEN SUCCESSFULLY UPDATED");
          }
        });
      }
    });
  }

@DanishDeveloper360
Copy link

DanishDeveloper360 commented Feb 12, 2019

@david114 I read your comments here aws-amplify/amplify-js#405 , Thanks a ton for this research and solution.

Shall I update using the below or using your comment above? is there a difference ? I dont think so, but need a suggestion:

`// Apply cognito session to AWS credentials so we can use all AWS services

AWS.config.update({
  credentials: new AWS.CognitoIdentityCredentials({
    IdentityPoolId: env.identityPoolId,
    Logins: {
      [`cognito-idp.${env.awsRegion}.amazonaws.com/${env.userPoolId}`]: session.getIdToken().getJwtToken(),
    },
  })
})

await AWS.config.credentials.getPromise()  // This will refresh AWS credentials as we've just updated them`

@david114
Copy link

david114 commented May 31, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment