Closed nnurmano closed 4 years ago
i literally tried everything, still doesn't work. wait 1hr refresh (works fine) wait 1hr refresh (error login token expired)
methods i tried:
AWS.config.credentials.get()
AWS.config.credentials.refresh()
AWS.config.credentials.getPromise()
AWS.config.credentials.refreshPromise()
cognitoUser.getSession()
cognitoUser.refreshSession()
I would like to achieve the same with amplify library.
I had used
export function retrieveUserFromLocalStorage() { return new Promise((success, failure) => { // grab the
cognitoUserobject from
userPool // this is possible without login because we had already logged in before (whereas verifyPIN and resetPassword have not) const cognitoUser = userPool.getCurrentUser(); if (cognitoUser != null) { // get the latest session from
cognitoUser cognitoUser.getSession(function (error, session) { // if failed to get session, reject the promise if (error) { console.log('Error occurred while retrieving user account', error); failure(error); return; } // check that the session is valid console.log('Is session still valid?: ' + session.isValid()); // save to localStorage the jwtToken from the
session`
localStorage.setItem('idToken', session.getIdToken().getJwtToken());
// Edge case, AWS Cognito does not allow for the Logins attr to be dynamically generated. So we must create the loginsObj beforehand
const loginsObj = {
// our loginsObj will just use the jwtToken to verify our user
[USERPOOL_ID]: session.getIdToken().getJwtToken()
}
// create a new `CognitoIdentityCredentials` object to set our credentials
// we are logging into a AWS federated identity pool
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: IDENTITY_POOL_ID, // your identity pool id here
Logins: loginsObj
})
// refresh the credentials so we can use it in our app
AWS.config.credentials.refresh(function () {
// resolve the promise by again building the user object to be used in our React-Redux app
success(buildUserObject(cognitoUser))
})
});
} else {
// if failure, reject the promise
failure('Failed to retrieve user from localStorage');
}
`
To refresh user session, are you using the same?
Yes, i'm pretty much doing the same thing. I've tried many variations. There seems to be a bug because I can successfully refresh after 1 hr, but after 2hrs I suddenly receive an error, although the login token should still be valid.
If this is a bug, could you try with different version of aws cognito packages?
I was using a ~ 1 year old version, than updated to the latest version of cognito and cognito identity, both didn't work.
I am also trying to figure this out, anyone have any luck?
Since i can only refresh successfully once i'm doing it like this now:
if(AWS.config.credentials.expired) {
confirm("Session is expired, Page needs to be refreshed!");
if(confirm()) {
location.reload();
}
}
If one of you guys come up with a proper solution for this, please post them here. i'd be really happy.
import { Auth } from 'aws-amplify';
// Async call to Cognito to check for token
// This will be checked every time a protected route is loaded
Auth.currentSession()
.then((response) => {
Axios.defaults.headers.common = {
authToken: response.idToken.jwtToken,
};
})
.catch((error) => {
console.log(error);
});
How about this? It is from AUTH library itself, with some modifications export const keepAlive = () => { if (!Auth.credentials) { Auth.setCredentialsForGuest(); }
const ts = new Date().getTime();
const delta = 10 * 60 * 1000; // 10 minutes
let credentials = Auth.credentials || {};
const { expired, expireTime } = credentials;
if (!expired && expireTime > ts + delta) {
return Promise.resolve(credentials);
}
return new Promise((resolve, reject) => {
Auth.currentUserCredentials()
.then(() => {
credentials = Auth.credentials;
credentials.refresh(error => {
_Logger.debug('Credentials are changed from previous');
if (error) {
_Logger.debug('Credentials refresh error', error);
resolve(null);
} else {
_Logger.debug('Credentials has been refreshed', credentials);
resolve(credentials);
}
});
})
.catch(() => resolve(null));
});
}
I have the same issue. I have taken to forcing the user to do a page reload.
I'd also like to know what the best way of doing this is; calling currentCredentials().then(credentials => credentials.refresh())
refreshes the expireTime
of currentCredentials
(what does this mean?) but hasn't refreshed the access/id jwt tokens.
There seems to be a lot of questions on how to do this. Could it be possible that an example of this be added to the documentation?
yeeeeeeeeeeeeeeeeeeeeeeeeeeeah, after almost 2 weeks i finally solved it.
You need the Refresh Token to receive a new Id Token. Once the Refreshed Token is acquired, update the AWS.config.credentials
object with the new Id Token.
here's an example on how to set this up, runs smoothly!
refresh_token = session.getRefreshToken(); // you'll get session from calling cognitoUser.getSession()
if (AWS.config.credentials.needsRefresh()) {
cognitoUser.refreshSession(refresh_token, (err, session) => {
if(err) {
console.log(err);
}
else {
AWS.config.credentials.params.Logins['cognito-idp.<YOUR-REGION>.amazonaws.com/<YOUR_USER_POOL_ID>'] = session.getIdToken().getJwtToken();
AWS.config.credentials.refresh((err)=> {
if(err) {
console.log(err);
}
else{
console.log("TOKEN SUCCESSFULLY UPDATED");
}
});
}
});
}
@tipsfedora thank you very much for this! I will definitely check it out!!
Closing the issue. Feel free to reopen it if necessary.
It would be nice to get some guidance on how to do this with the Amplify lib. It appears to me that the session is automatically refreshed if expired, but there is no way to refresh the token early. Is that the case?
@retwedt that's correct, the session is automatically refreshed, you could technically refresh the token yourself however by doing this same approach since the amplify lib uses the cognito lib under the covers. We are working on giving more flexibility / simplifying this as well (in our backlog) to both refresh and also disable automatic refresh.
Great thanks!
I wasn't sur about how to manage the refresh token and how to ask for id and access tokens, so if we are using aws amplify, all this is happened under cover, I don't have to ask for new id token or access token right ?
@Jasminou yes aws amplify will refresh your session automatically if expired.
It will refresh if you call the SDK for it, e.g., with Auth.currentSession(), and it finds an expired token + a valid refresh token.
AFAIK there's no timing mechanism to update your localStorage for you in the background. (Auth0's JS SDK uses setTimeout to update localStorage, but that's got its own issues.) If like me you're using Amplify just for the auth, and doing the rest with REST, then you'll want to call Auth.currentSession() before every API call, to get the latest access token. E.g. using Axios HTTP request framework:
// Add latest auth access token to every http request
axios.interceptors.request.use(function (config) {
return Auth.currentSession()
.then(session => {
// User is logged in. Set auth header on all requests
config.headers.Authorization = 'Bearer ' + session.accessToken.jwtToken
return Promise.resolve(config)
})
.catch(() => {
// No logged-in user: don't set auth header
return Promise.resolve(config)
})
})
@jamesoflol yes. The session will be refreshed when calling Auth.currentSession()
and Amplify will also call Auth.currentCredentials()
before sending any request to AWS services like S3, Pinpoint so the credentials get automatically refreshed.
@jamesoflol It's probably a good idea to catch Auth.currentSession()
and resolve in case you make unauthenticated calls.
@StevenDufresne Agreed. I'll update my example as it seems this issue gets some traffic
It would definitely be helpful if the documentation actually mentioned that behavior for currentSession()
. We are only using Auth, so we aren't using other modules that will automatically refresh, and we'd opted for the "set timeout to refresh tokens manually" approach until finding this thread.
@kevlarr hi, thanks for your feedback. I have sent a pr accordingly: https://github.com/aws-amplify/docs/pull/535
Whoa, seriously @powerful23? You are a rockstar! Apologies for not doing that myself.
@kevlarr No problem! It's great to have those feedbacks that would gradually improve the docs.
In case I'm not too late to the party, I noticed that Amplify's API docs suggest setting a custom header when you first configure the Amplify settings.
Amplify.configure({
API: {
endpoints: [
{
name: "sampleCloudApi",
endpoint: "https://xyz.execute-api.us-east-1.amazonaws.com/Development",
custom_header: async () => {
// With Cognito User Pools use this:
return { Authorization: (await Auth.currentSession()).idToken.jwtToken }
}
}
]
}
});
I like this approach because now every request is syntatically simple
API.get('myRestEndpoint', "/mystuff")
@JLee21 Oh that's excellent, thank you. We have only been using the Auth
module so had not seen that regarding the API
module. Looks like it might be worth using instead of rolling our own mini-lib to handle refreshing tokens.
FYI, for any others who end up here from Google, if you're using Cognito Identity Pools without Cognito User Pools, then @JLee21's sample above can be adapted as follows:
Amplify.configure({
API: {
endpoints: [
{
name: "sampleCloudApi",
endpoint: "https://xyz.execute-api.us-east-1.amazonaws.com/Development",
custom_header: async () => {
// fetch the user that was previously authenticated with Facebook via a call
// to `await Auth.federatedSignIn()`
const user = await Auth.currentAuthenticatedUser();
console.log (`will authenticate to AWS with this user: ${JSON.stringify(user)}`);
return { 'cognito-identity-id' : user.id }
// With Cognito User Pools use the code below instead
// return { Authorization: (await Auth.currentSession()).idToken.jwtToken }
}
}
]
}
});
Unfortunately, Amplify docs do not mention how to refresh token on demand but they always keep saying that Amplify will refresh when required but what if I want to refresh token now, I need to refresh due to some reason, they had not told us. Let's come to the point, here is now you can refresh token on demand.
import { Auth } from 'aws-amplify';
try {
const cognitoUser = await Auth.currentAuthenticatedUser();
const currentSession = await Auth.currentSession();
cognitoUser.refreshSession(currentSession.refreshToken, (err, session) => {
console.log('session', err, session);
const { idToken, refreshToken, accessToken } = session;
// do whatever you want to do now :)
});
} catch (e) {
console.log('Unable to refresh Token', e);
}
@mzohaibqc works like magic. thanks!
@justingrant @JLee21
How would this be done in an app that uses a cognito identity pool with cognito user pools and federated fb/google sign in? Do you have an easy way to combine the two that you have proposed?
if you are using amplifyJS than this will helpful for you>>>>> https://aws-amplify.github.io/docs/js/authentication#retrieve-current-session
I keep running into cases where when passing the token to our api it says token expired. Not sure whats up as we are definitely calling Auth.currentSession()
before every request.
Amplify does not allow refreshing your tokens directly for good reasons.
Long story short, you can retrieve a "CognitoUser" object from Amplify AuthClass using the currentAuthenticatedUser
and from there you can do what a CognitoUser can do.
I ran into a situation where my Cognito JWT token was expiring on long-running S3 uploads (fails at the 1 hour mark). I couldn't find anything that gave a solution as to how you refresh the token in the middle of a request, so after hours of digging through the Amplify lib and AWS SDK, I finally figured out a solution. You do have to use the AWS SDK directly (sorry Amplify Storage), but I was already doing that so I could pass in the queueSize option to fix the other timeout issue.
When using the AWS SDK, you basically have two options for passing in credentials: 1) the S3 constructor, and 2) the global AWS config. Amplify seems to always pass them in through the constructor, and I'm not sure you can update them that way; however, if you just set the credentials in the global AWS config, you can update the JWT token directly every time you refresh it.
So first I grab the current user's credentials and store it in AWS.config.credentials:
import {Credentials} from '@aws-amplify/core'
Credentials.get().then(creds => {
AWS.config.credentials = creds
})
Then you can instantiate a S3 object without passing in credentials and start your upload.
const s3 = new S3({
apiVersion: '2006-03-01',
signatureVersion: 'v4',
region: 'us-west-2',
params: {Bucket: bucket}
})
// Upload params and opts here.
const upload = s3.upload(params, opts).on('httpUploadProgress', progress => {...}).promise()
Then what I do is use setInterval()
to call a refresh method every so often. Technically the Cognito token last for an hour, so you can refresh it every 50 minutes or use AWS.config.credentials.needsRefresh()
to keep it more generic.
const refreshToken = async () => {
var session = await Auth.currentSession() //Will refresh token if needed.
const region = Amplify._config.aws_project_region
const user_pool_id = Amplify._config.aws_user_pools_id
AWS.config.credentials.params.Logins['cognito-idp.'+region+'.amazonaws.com/'+user_pool_id] = session.getIdToken().getJwtToken()
AWS.config.credentials.refresh((err) => {
if(err)
console.log("Error updating token")
else
console.log("Token updated")
})
}
const cRef = setInterval(refreshToken, 50*60*1000)
I'm sure I'm violating some best practices here but it works. Just don't forget to clearInterval()
when the upload finishes or catches an error.
And the code that @david114 posted earlier definitely sent me down the right path. I just added in the Amplify way of refreshing the JWT token instead of doing it manually just to keep Amplify in sync with S3. That code can be found in the amplify-js
lib here under Use Case 32
Really hope this helps someone. Spent way too long trying to figure it out. :P
@b0morris Thanks a lot for sharing your solutions. A few months ago, actually in April, I faced the same issue that S3 uploads were failing if file upload took more than 1 hour so even after a lot of struggle, I could not find the right solution to refresh token that is being used by S3 upload. So I finally had to use custom temporary credentials
from backend API to upload large files.
Amplify needs to handle this scenario but they always keep saying, it will automatically refresh the token but it's will not in such case.
Another issue or limitation with Cognito based credentials is that we can't restrict uploads on an s3 path based on some custom usepool attribute e.g. role or TenantID. but anyway, Thanks for sharing :)
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.
not stale
@david114 the solution that you proposed actually giving me an exception repeatedly: TypeError: Cannot read property 'Logins' of undefined Please suggest what could be the issue because in credentials there is no key exists named as 'params'.
@ripundeep
What Libaries are you using? Our Website is quite old now and we are not using AWS Amplify at all.
We're using: aws-sdk-2.172.0.min.js aws-cognito-sdk.min.js amazon-cognito-identity.min.js
Using these files, I can call AWS.config.credentials.params.Logins without any problems
@david114 thanks for the reply. I am working on node-js and using amazon-cognito-identity-js. I guess you have utilized in another JavaScript.
@ripundeep we were using plain client-sided javascript
FWIW in case it helps anyone else, I was initially thinking that Auth.currentSession()
was not refreshing the token. But in my case we are listening for auth changes with Hub
to store our token and make api calls outside of the amplify api library by using axios direct. But when Auth.currentSession()
refreshes the token it does not fire a Hub
event so our application did not know that a token was updated.
Feels like a bug that no event is fired when a refresh token happens. So for now I've added an axios interceptor to insert the token from Auth.currentSession()
into every axios call.
@jeffsheets - care to share the code you used for that interceptor in case it might be useful to others?
@justingrant Oh yep, here you go! This uses the Axios Interceptors setup to inject the Auth token on every call. I can't remember why our app uses idToken instead of accessToken, so depending on the app that might have to change, but here's the general idea. https://github.com/axios/axios#interceptors
export const axiosRequestInterceptor = async config => {
//This will get the cached session, or refresh the token first if it has expired
const session = await Auth.currentSession();
if (session && session.idToken) {
config.headers.Authorization = session.idToken.jwtToken;
}
return config;
};
axios.interceptors.request.use(axiosRequestInterceptor, e => Promise.reject(e));
This is what I do with axios on every request:
axios.interceptors.request.use(async function (config: AxiosRequestConfig) {
const session = await Auth.currentSession()
try {
const token = session.getAccessToken().getJwtToken()
config.headers.authorization = token
} catch (error) {
log.error(error)
await store.dispatch('auth/logout')
await router.push({
name: 'login'
})
}
return config
}, async function (error: AxiosError) {
return Promise.reject(error)
})
And because sometimes my users get logged out randomly, I refresh the token manually on an interval (still experimenting with this):
let cognitoKeepaliveInterval: any = null
function startCognitoKeepaliveInterval() {
if (cognitoKeepaliveInterval) clearInterval(cognitoKeepaliveInterval)
cognitoKeepaliveInterval = setInterval(async () => {
const session = await Auth.currentSession()
const user = await Auth.currentAuthenticatedUser()
user.refreshSession(session.getRefreshToken(), () => {
log.info('refreshed auth session')
})
}, 30 * 60 * 1000) // 30 minutes
}
There is a similar issue in https://github.com/aws/aws-amplify/issues/405, but the OP uses old approach, I would like to know how to refresh tokens based on aws-amplify library? Also, this is probably related, how could I make sure that the user is always logged in?