Login with AWS Cognito

From @jayair on Wed Dec 27 2017 18:04:07 GMT+0000 (UTC)

@mdhendri It is a few extra steps but it is fairly straightforward. Cognito will send a code to the user and they can reset their password with the code and their new password. I might write a chapter on this at some point, but here is the sample code from the Cogntio JS SDK docs - https://github.com/aws/amazon-cognito-identity-js

cognitoUser.forgotPassword({
    onSuccess: function (data) {
        // successfully initiated reset password request
        console.log('CodeDeliveryData from forgotPassword: ' + data);
    },
    onFailure: function(err) {
        alert(err);
    },
    //Optional automatic callback
    inputVerificationCode: function(data) {
        console.log('Code sent to: ' + data);
        var verificationCode = prompt('Please input verification code ' ,'');
        var newPassword = prompt('Enter new password ' ,'');
        cognitoUser.confirmPassword(verificationCode, newPassword, {
            onSuccess() {
                console.log('Password confirmed!');
            },
            onFailure(err) {
                console.log('Password not confirmed!');
            }
        });
    }
});

From @jayair on Wed Dec 27 2017 18:06:13 GMT+0000 (UTC)

@ohenneken I’m not entirely sure based on what is happening here but a good place to start would be by checking the logs for API Gateway and Lambda. We have a chapter on this here - https://serverless-stack.com/chapters/api-gateway-and-lambda-logs.html

From @ohenneken on Wed Dec 27 2017 22:07:55 GMT+0000 (UTC)

Hi Jay,

Thnx for responding.

I have these logs up and running, and i have been nspecting them, but there
is no clear indication why some rest-calls do not work correctly.

There is one ‘get’ call i cannot get to work, while other work fine and i
cannot not get any create (or POST) call to work via the deployed api.

I can however run code that creates entries programmatically, using the
DynamoDb API from aws-sdk.

I used that to populate the tables, doing:

var docClient = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: “policy_statements”,
Item: {
“label”: statement.label,
“Name”: statement.Name,
“parentObject”: statement.parentObject,
“policy”: statement.policy,
“AuthoritativeSourceReference”:
statement.AuthoritativeSourceReference,
“Description”: statement.Description
}
};

docClient.put(params, function(err, data) {

So the tables are ok and i can put data in the tables, but when i try to
call the deployed API via the application, this does not work.

Regards,
Oscar Henneken

From @jayair on Wed Dec 27 2017 22:51:26 GMT+0000 (UTC)

@ohenneken You should be able to inspect the Lambda logs to see why they are failing. What do the logs for the failed ones say?

From @ohenneken on Thu Dec 28 2017 00:02:55 GMT+0000 (UTC)

Hi Jay,

Well one error, for a create_favorite call gives this errors back on the
client:

app.js:118495 Uncaught (in promise) TypeError: Failed to execute ‘fetch’ on
‘Window’: Request with GET/HEAD method cannot have body.
at _callee3$ (
http://localhost:8080/app.js?40601732f7eafe02cf81:118495:20)
at tryCatch (http://localhost:8080/app.js?40601732f7eafe02cf81:98834:40)
at Generator.invoke [as _invoke] (
http://localhost:8080/app.js?40601732f7eafe02cf81:99068:22)
at Generator.prototype.(anonymous function) [as next] (
http://localhost:8080/app.js?40601732f7eafe02cf81:98886:21)
at step (http://localhost:8080/app.js?40601732f7eafe02cf81:23870:30)
at http://localhost:8080/app.js?40601732f7eafe02cf81:23881:13
at

app.js:115097 Error: User:
arn:aws:sts::891130555436:assumed-role/Cognito_bwisenotesappAuth_Role/CognitoIdentityCredentials
is not authorized to perform: dynamodb:PutItem on resource:
arn:aws:dynamodb:us-east-2:891130555436:table/create_favorite
at Request.extractError (app.js:14915)
at Request.callListeners (app.js:17842)
at Request.emit (app.js:17814)
at Request.emit (app.js:16471)
at Request.transition (app.js:15810)
at AcceptorStateMachine.runTo (app.js:21186)
at app.js:21198
at Request. (app.js:15826)
at Request. (app.js:16473)
at Request.callListeners (app.js:17852)

I suppose there is something not ok with the IAM roles & policies?

For example: the role for the error above was set like so:

{
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Action”: [
“mobileanalytics:PutEvents”,
“cognito-sync:",
"cognito-identity:
”
],
“Resource”: [
“"
]
},
{
“Effect”: “Allow”,
“Action”: [
"s3:
”
],
“Resource”: [
“arn:aws:s3:::bwise-notes/${cognito-identity.amazonaws.com:
sub}"
]
},
{
“Effect”: “Allow”,
“Action”: [
“execute-api:Invoke”
],
“Resource”: [
"arn:aws:execute-api:us-east-1:
:61u3er5pth/"
]
},
{
“Action”: [
“dynamodb:DescribeTable”,
“dynamodb:Query”,
“dynamodb:Scan”,
“dynamodb:GetItem”,
“dynamodb:PutItem”,
“dynamodb:UpdateItem”,
“dynamodb:DeleteItem”
],
“Resource”:
["arn:aws:dynamodb:us-east-1:
:","arn:aws:dynamodb:us-east-1:”],
“Effect”: “Allow”
},
{
“Effect”: “Allow”,
“Action”: [
“dynamodb:DescribeTable”,
“dynamodb:Query”,
“dynamodb:Scan”,
“dynamodb:GetItem”,
“dynamodb:PutItem”,
“dynamodb:UpdateItem”,
“dynamodb:DeleteItem”
],
“Resource”: [
“arn:aws:dynamodb:us-east-2:891130555436:table/regulators”,

“arn:aws:dynamodb:us-east-2:891130555436:table/regulation_changes”,

“arn:aws:dynamodb:us-east-2:891130555436:table/policy_statements”,
“arn:aws:dynamodb:us-east-2:891130555436:table/policies”,
“arn:aws:dynamodb:us-east-2:891130555436:table/favorites”,
“arn:aws:dynamodb:us-east-2:891130555436:.”
],
“Condition”: {
“ForAllValues:StringEquals”: {
“dynamodb:LeadingKeys”: [
“${www.amazon.com:user_id}”
]
}
}
}
]
}

I tried to set these very liberal though, but the AWS documentation is
overwhelming at that point i think and not clear enough.

Met vriendelijke groet,
Oscar Henneken

From @jayair on Sat Dec 30 2017 20:55:46 GMT+0000 (UTC)

@ohenneken The permissions for DynamoDB are set in the serverless.yml here https://github.com/AnomalyInnovations/serverless-stack-demo-api/blob/master/serverless.yml#L21

I’m not sure what your set up is and how it relates to the tutorial though.

From @ohenneken on Sat Dec 30 2017 22:06:17 GMT+0000 (UTC)

Hi Jay,

Yeah forget it, i learnt a lot and have acquired some basis to decide later on to start using Aws and serverless. Surely stuff will work in the end.

Thnx for your answers!

Regards,

Oscar

From @walshe on Sun Jan 21 2018 18:00:36 GMT+0000 (UTC)

hey guys, looking for some advice if you got any…

So this cognito stuff is really great, especially for managing users but also for using their authentication to get temporary user credentials in order to access secured api gateways.

I have a slightly different situation but wondering if I could somehow stay close to the same pattern/principles you guys are using.

So here is my situation… (by the way, BigCommerce does not support OpenID Connect OIDC)

  • A user logs into their BigCommerce store and installs my app (my app is the react app with serverless backend).
  • when user clicks my app from inside BigCommerce, then bigcommerce forwards the user to my app via Auth Callback URL … which is a serverless function I created. e.g. https://xxx.execute-api.us-east-1.amazonaws.com/prod/bc/oauth
  • In here I use a bigcommerce module to authorize the data sent to it from BigCommerce
    import {
    success,
    failure
    } from "./libs/response-lib";

    const BigCommerce = require('node-bigcommerce');

    const bigCommerce = new BigCommerce({
    logLevel: 'info',
    clientId: 'xxxx',
    secret: 'yyyy',
    callback: 'https://xxxxx.execute-api.us-east-1.amazonaws.com/prod/bc/oauth',
    responseType: 'json',
    apiVersion: 'v3' // Default is v2
    });

    export async function oauth(event, context, callback) {

    bigCommerce.authorize(JSON.stringify(event.queryStringParameters))
        .then(data => {

            console.log('Authorized! ' + JSON.stringify(data));
           
           // TODO what should I do here
         
            callback(null, {
                statusCode: 302,
                headers: {
                    Location: 'https://myreactapp.com',
                },
            })


        }).catch(error => {
            callback(null, failure(error));

        });

}

So above you can see the point at which I know the bigcommerce user is valid. The data I receive from bigcommerce is something like this: {"context":"stores/2345","code":"gpkof3rb4vo1ck0picwqjkeeeeett","scope":"store_v2_default"}.

So here is where I am at a crossroads and wondering what to do. Basically I ideally want to do something that allows me to hook into the same pattern as the tutorial and use cognito if possible.

For instance at the ‘TODO’ section above should I try to create (first time somehow) or retrieve a cognito user (every subsequent time) and get a token for that user using currentUser.getSession(..).getIdToken().getJwtToken() ? and then forward that token to some route in my react app e.g. myreactapp.com/bc/loginSuccess so that I can continue development just like the vein of the tutorial . When I create the cognito user I would perhaps give it some attribute that associates it with the BigCommerce user account e.g. the store id above

One other nice reason to use the cognito pool pattern is this:
While users will usually log in via the BigCommerce site and get forwarded to my app (at least once anyway for the first time), I will also want to give my users a direct login to my app. This access can only be given after they have first authenticated via BigCommerce at least once (obviously since otherwise we would have no bigcommerce data to connect them to). So if I could use the cognito pattern that I described above then this could all work really nicely… i.e. at the time I create the cognito user (inside bigCommerce.authorize()…) above, cognito would sent the conformation email to the user with something like this: ‘You logged into the app via BigCommerce but we have also created a username/password for direct access to the app, click here to confirm etc’

so anyway, curious if theres a better way or if such an approach is totally crackers…

From @jayair on Tue Jan 23 2018 15:10:18 GMT+0000 (UTC)

@walshe Yeah this isn’t straightforward but here is how I would do it. I haven’t tried it but theoretically it should work.

  1. Create/Auth the user using the Admin Cognito APIs (https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminCreateUser.html). This needs to be done in your serverless backend and not in the client since you don’t want to expose the Admin APIs publicly. These calls need to return the JWT token.

  2. Store the JWT token and handle sessions in the React app yourself instead of relying on the Cognito JS SDK.

So effectively, you are going to do the login and signup portions on your own in the serverless backend.

From @walshe on Tue Jan 23 2018 15:20:43 GMT+0000 (UTC)

thanks @jayair , actually I was thinking more since and thing I could further simplify:

so when BigCommerce forwards to my /bc/oauth and when bigCommerce.authorize(JSON.stringify(event.queryStringParameters)) succeeds, I do the following:

  1. I check if cognito user exists, if not , I create the new cognito user there with an attribute connecting user to the bigcommerce store (in the meantime an email gets sent to the new app user with login/password etc).
  2. Next I just forward user to the login form of the app (with a message saying check your email for password etc)

if on the other hand this is a second/subsequent time the user has authenticated with bigcommerce, then in this case (in my oauth method) the cognito user will be found and here I just forward them to the react app (without the message). If there is already a session still valid in the app then the user would go straight to the home page of app etc. If not then they are presented with the login form of the app

So in this approach the react app works exactly like it does in the tutorial, still uses the cognito js sdk and iam authorizer etc, saving me lots of work

thoughts ?

From @jayair on Tue Jan 23 2018 15:23:47 GMT+0000 (UTC)

@walshe Yeah this would be a lot simpler than handling the sessions yourself.

From @walshe on Tue Jan 23 2018 21:14:18 GMT+0000 (UTC)

@jayair one other question.

There will be an admin (maybe more than one) user for the react app.

I have a requirement that this admin once logged in to be able to ‘log in as’ another user. I guess I could pass the id or something of the ‘other’ cognito user as an extra ‘asUserId’ param to each api call and then on the server side check that the caller is ADMIN and the existence of the extra userID param and then run business logic using the userId param - but is there a better way of doing this ?

From @jayair on Thu Jan 25 2018 20:46:38 GMT+0000 (UTC)

@walshe I don’t think there is a built-in way to do this with Cognito (I’m not entirely sure though). So you’d probably have to come up with a solution that works for you.

From @walshe on Thu Jan 25 2018 21:01:40 GMT+0000 (UTC)

@jayair thanks

From @walshe on Fri Jan 26 2018 19:01:43 GMT+0000 (UTC)

re my comment above…

It turns out that BigCommerce doesnt want a user to have to login to the app a second time once validated by the oauth callback… (i.e. when they click my app link from inside bigcommerce)

So, in my oauth serverless callback, if I create the Cognito user, (and save some info in a db table connecting the user to a bigcommerce store etc) is it possible for me to get credentials for that new user and just forward these credentials to the react app.

I could add a special hook in the app to accept the credentials which would allow it to make api gateway calls that are locked down with the iam_authorizer. This would avoid the user having to explicitly log into the app if they come from BigCommerce, while still allowing them to use the apps login form for direct access outside of BigCommerce. @jayair

From @sunkay on Tue Apr 03 2018 14:10:40 GMT+0000 (UTC)

@jayair have you used the withAuthenticator HOC that comes with aws-amplify-react? Is that a good way to use for Auth?

From @jayair on Tue Apr 03 2018 17:53:12 GMT+0000 (UTC)

@sunkay I had a brief look at it. Just didn’t find enough examples on it and so I decided to stick with the simple methods that they have. I like the current setup since it’s pretty flexible and you could swap it out for a different auth provider. I would need to play around with a sample app to see it in detail.

From @smokeyblues on Fri May 04 2018 19:05:07 GMT+0000 (UTC)

Hey thanks for the tutorial. It has been informative and inspiring. Maybe too inspiring (lol) because I tried to integrate it with a gatsbyjs generated frontend and ran into a problem that doesn’t have a lot of documentation online.

I have a login page with cognito and aws amplify set up but when I click the ‘sign in’ button I get:

_this3 is not defined

or


That is the only thing it is returning. There is no console output server-side or client-side. This may be the wrong place to ask this question but I can’t find a workable solution anywhere else. Does anyone here know how to resolve this error?

Thanks in advance.

From @jayair on Mon May 07 2018 22:27:39 GMT+0000 (UTC)

@smokeyblues Can you boil it down to the line that is throwing the error?

Thank you so much for this generous book. I’ve been able to follow the instructions and authenticate against Cognito. There are so many pieces to the puzzle, I don’t think I’d have put it together without your help!

1 Like