Reputation: 20123
I'm attempting to develop a serverless JavaScript web application consisting of API Gateway (w/ an Amazon Cognito Custom Authorizer), Lambda, and DynamoDB. This post is my last attempt to make Cognito work for my needs before completely giving up on it (for a 2nd time). I'll try to infuse a bit what I've learned since the Cognito documentation is confusing and lacking.
With Cognito, you can develop a custom authentication workflow with your own registration, sign in, etc. web pages. You also have the option to leverage Amazon's Hosted Web UI to make things a bit simpler (although with very few HTML/CSS customization options). I'm using the latter.
When configuring an App Client for a Cognito User Pool, the most critical decision you have to make is whether to use an Authorization Code Grant or an Implicit Grant. With an Authorization Code Grant, a successful authentication will return a session token containing a JWT id_token, access_token, and refresh_token to your caller. With an Implicit Grant, your caller will receive an Authorization code that can be used to obtain an id_token and an access_token only, with no refresh_token. The Implicit Grant is most suitable for serverless (or single-page) applications because it won't expose a long-lived refresh_token to the client which can be easily compromised and used to assume valid access to your application. However, the access_token has a fixed expiration of one hour that cannot be configured at this time. So, without a refresh token to silently renew the access_token, your user will have to log-in every hour.
Since my web application in no way contains sensitive information, I'm going to use the Authorization Code Grant and persist the tokens in the browser's LocalStorage (which is unsafe, not recommended, and a bad practice) in hopes that, at some point, Cognito will fully comply with the OpenId Spec and provide full support for refresh tokens with implicit grants via prompt=none. As you can see, Amazon has been unresponsive on the matter. Heck, even an option to customize the expiration time of the access_token (with Implicit grant) would be a nice compromise.
So I've successfully deployed the sample application from the amazon-cognito-auth-js JavaScript library, which is meant to be used with the Hosted Web UI flow. This should not be confused with the amazon-cognito-identity-js library, which should be used if you're developing your own custom authentication workflow. The amazon-cognito-auth-js library supports both the Authorization Code Grant as well as the Implicit Grant and will handle parsing the tokens, caching/retrieving them to/from LocalStorage, and silently renewing the access_token with the refresh token (for Authorization Code Grant).
When I sign-in, my browser URL resembles the following: https://www.myapp.com/home?code=ABC123XYZ... and the three JWT tokens are set in the browser's LocalStorage. If I immediately refresh the page, however, I receive an "invalid_grant" error, because the Authorization code is still in the URL and has already been consumed. Should I just do a page redirect upon successful sign-in to remove the Authorization code from the URL? Here's the main code at play which I plan to call fromthe onLoad() of every page in my app:
function initCognitoSDK() {
var authData = {
ClientId : '<TODO: your app client ID here>', // Your client id here
AppWebDomain : '<TODO: your app web domain here>', // Exclude the "https://" part.
TokenScopesArray : <TODO: your scope array here>, // like ['openid','email','phone']...
RedirectUriSignIn : '<TODO: your redirect url when signed in here>',
RedirectUriSignOut : '<TODO: your redirect url when signed out here>',
IdentityProvider : '<TODO: your identity provider you want to specify here>',
UserPoolId : '<TODO: your user pool id here>',
AdvancedSecurityDataCollectionFlag : <TODO: boolean value indicating whether you want to enable advanced security data collection>
};
var auth = new AmazonCognitoIdentity.CognitoAuth(authData);
// You can also set state parameter
// auth.setState(<state parameter>);
auth.userhandler = {
onSuccess: function(result) {
alert("Sign in success");
showSignedIn(result);
},
onFailure: function(err) {
alert("Error!" + err);
}
};
// The default response_type is "token", uncomment the next line will make it be "code".
auth.useCodeGrantFlow();
return auth;
}
Upvotes: 8
Views: 2357
Reputation: 14905
You can either redirect to remove the code from query string or check on page load if your app can find a valid access token in the browser local storage and, in that case, ignore the code received in query string.
Upvotes: 0