Closed nnurmano closed 4 years ago
Well, i don't understand — if i console.log session token it remains the same:
let session = await Auth.currentSession();
let token = session.getIdToken().getJwtToken();
console.log(token); // this token is always the same...
So... what is the point of running the currentSession
?
@artuska , from the documentation: https://aws-amplify.github.io/docs/js/authentication#retrieve-current-session
This method will automatically refresh the accessToken and idToken if tokens are expired and a valid refreshToken presented. So you can use this method to refresh the session if needed.
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 useAWS.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 32Really hope this helps someone. Spent way too long trying to figure it out. :P
Thank you for your solution. We had the same issue with large uploads. We tried many solutions but none of them worked for us.
A good start is to check AWSS3Provider
implementation:
https://github.com/aws-amplify/amplify-js/blob/a047ce73/packages/storage/src/Providers/AWSS3Provider.ts#L62
We created a custom Storage
class according to AWSS3Provider
but with authentication refresh.
import { AWS } from "@aws-amplify/core";
import { Auth } from "aws-amplify";
export default class Storage {
private _refreshTimeout;
// Token expiration is 60 min
private readonly REFRESH_INTERVAL = 50 * 60 * 1000; // 50 min
private readonly MIN_REFRESH_INTERVAL = 30 * 1000; // 30 sec
private readonly COGNITO_IDP_URL;
constructor({ region, userPoolId }) {
this.COGNITO_IDP_URL = `cognito-idp.${region}.amazonaws.com/${userPoolId}`;
}
private _clean = () => {
if (this._refreshTimeout) {
clearTimeout(this._refreshTimeout);
this._refreshTimeout = null;
}
};
private _scheduleCredentialsRefresh = (interval = this.REFRESH_INTERVAL) => {
this._refreshTimeout = setTimeout(async () => {
const session = await Auth.currentSession();
// @ts-ignore
AWS.config.credentials.params.Logins[
this.COGNITO_IDP_URL
] = session.getIdToken().getJwtToken();
// @ts-ignore
AWS.config.credentials.refresh(async error => {
if (this._refreshTimeout) {
this._scheduleCredentialsRefresh(
error ? this.MIN_REFRESH_INTERVAL : this.REFRESH_INTERVAL
);
}
});
}, interval);
};
private _createS3 = ({ bucket, region, ...restParams }) => {
// @ts-ignore
return new AWS.S3({
apiVersion: "2006-03-01",
signatureVersion: "v4",
params: { Bucket: bucket },
region,
...restParams,
});
};
private _initCredentials = async () => {
try {
const credentials = await Auth.currentUserCredentials();
AWS.config.credentials = credentials;
return credentials;
} catch (e) {
return null;
}
};
private _getParams = ({ credentials, level = "public", key, object }) => {
if (!credentials && level !== "public") {
throw new Error("Missing credentials");
}
let prefix;
const identityId = credentials.identityId;
switch (level) {
case "public":
prefix = "public";
break;
case "protected":
prefix = `protected/${identityId}`;
break;
case "private":
prefix = `private/${identityId}`;
break;
}
return {
// @ts-ignore
Key: `${prefix}/${key}`,
Body: object,
ContentType: "binary/octet-stream",
};
};
public put = (
key,
object,
{ progressCallback, level, bucket, region, ...restParams }
) =>
new Promise(async (resolve, reject) => {
try {
const credentials = await this._initCredentials();
const s3 = this._createS3({ bucket, region, ...restParams });
const params = this._getParams({ credentials, level, key, object });
if (credentials) {
this._scheduleCredentialsRefresh();
}
s3.upload(params)
.on("httpUploadProgress", progressCallback)
.promise()
.then(data => {
this._clean();
resolve(data);
})
.catch(error => {
this._clean();
reject(error);
});
} catch (error) {
this._clean();
reject(error);
}
});
}
export const MyStorage = new Storage({
region: "your-region",
userPoolId: "your-user-pool-id",
});
U can use Storage
in the same way like u use Amplify Storage
MyStorage.put(`fileName`, file, {
progressCallback: onProgress,
level: "private",
bucket: "bucket",
region: "region",
})
.then(onSuccess)
.catch(onFailure);
The token is refreshed every 50 min (REFRESH_INTERVAL). If the refresh fails for some reason we are still trying to refresh the token every 30 sec (MIN_REFRESH_INTERVAL).
@norbertdurcansk trying your custom storage class now as a shortcut, If it works, I will buy you a coffee after the pandemic :)
After some trial and error I have found that the answer from @jamesoflol is the most correct. Here it is the code that worked for me, adapted to the Angular HttpClient:
/**
* As the AWS Amplify SDK does not notify when the current access token expires
* we have to check the current token on every HTTP call. This class
* implements an HTTP request interceptor for the Angular HttpClient
*/
@Injectable()
export class AddAccessTokenToHttpHeader implements HttpInterceptor
{
constructor(protected amplify_service: AmplifyService)
{
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
{
// Amplify AuthClass.currentSession() checks the
// access token and refreshes it if required.
return (from((this.amplify_service.auth() as AuthClass).currentSession()).pipe(
switchMap(cognito_session =>
{
let auth_header: string = 'Bearer ' + cognito_session.getAccessToken().getJwtToken()
let new_request: HttpRequest<any> = request.clone({
setHeaders: {
Authorization: auth_header
}
});
return (next.handle(new_request));
})
));
}
}```
This issue has been automatically closed because of inactivity. Please open a new issue if are still encountering problems.
After some trial and error I have found that the answer from @jamesoflol is the most correct. Here it is the code that worked for me, adapted to the Angular HttpClient:
/** * As the AWS Amplify SDK does not notify when the current access token expires * we have to check the current token on every HTTP call. This class * implements an HTTP request interceptor for the Angular HttpClient */ @Injectable() export class AddAccessTokenToHttpHeader implements HttpInterceptor { constructor(protected amplify_service: AmplifyService) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // Amplify AuthClass.currentSession() checks the // access token and refreshes it if required. return (from((this.amplify_service.auth() as AuthClass).currentSession()).pipe( switchMap(cognito_session => { let auth_header: string = 'Bearer ' + cognito_session.getAccessToken().getJwtToken() let new_request: HttpRequest<any> = request.clone({ setHeaders: { Authorization: auth_header } }); return (next.handle(new_request)); }) )); } }```
How would you re-route the request on an unauthorized request?
This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.
Looking for a help forum? We recommend joining the Amplify Community Discord server *-help
channels or Discussions for those types of questions.
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?