Tutorial AWS API Gateway + Cognito (UserPool)

FCavalcanti
7 min readJun 11, 2018

--

The purpose of this tutorial is to have three fully working routes, respectively for /login, /logout and /refreshToken using lambda functions, API Gateway, Cognito UserPool.

The motivation behind it was because all tutorials I’ve watched are either incomplete/did not match my needs, and since the official documentation is extense (and sometimes with holes on it) I deciced to create a ‘throughout’ tutorial gluing together all steps needed to have a fully working authentication API using APIGateway+Cognito+UserPool+CustomAuthorizer+LambdaFunctions!

Enjoy!!!

And remember to comment any missguided steps or doubts.!!!

Link to partTwo!

Part one;
PUT /auth fully working with a freshly created UserPool (with appClient) with hostedUI for signUp, signIn and confirmEmail and a deployed API Gateway method using a freshly baked lambda function!!!

1. UserPool

We will setup a userPool to be used as the ‘database’ of ours users.

1.1 Go to Cognito Console — New User Pool
1.1.1 — Pool Name;
1.1.2 — Step through settings
1.1.3 — Choose first option ‘Username’ and mark the option ‘Also allow sign in with verified email address’
1.1.4 — Which standard attributes do you want to require? — choose ‘email’
1.1.5 — What password strength do you want to require? — in this tutorial we used default settings (minimum length 8 — require — numbers, special chars, uppercase/lowercase letters)
1.1.6 — Do you want to allow users to sign themselves up? — Mark the second option; ‘Allow users to sign themselves up’
1.1.7 — EnableMFA — no
1.1.8 — Verifications — mark ‘email’
1.1.9 — next — next — next — and create the pool

1.2 — App Client
1.2.1 — Click on ‘App clients’ (menu)
1.2.2 — Add an app client
1.2.3 — give the appClient a name, in our case we used ‘recursosArtisticosDefaultClient’
1.2.4 — Keep checked the option — Generate client secret
1.2.5 — Also mark the options [Enable sign-in API for server-based authentication (ADMIN_NO_SRP_AUTH) AND Enable username-password (non-SRP) flow for app-based authentication (USER_PASSWORD_AUTH)]
1.2.6 — Create app Client

Now we should note that we already created a UserPool and an appClient, we should be able to take a note of 3 important configuration settings.

1.3 — Copy constants
*PoolID (generalSettings) us-east-2_something…
*AppClientID (appClients screen) 70vsigpsomething…
*AppClientSecret (appClients screen) reallyLongString…

1.3 App integration
1.3.1 Domain Name (screen)
1.3.1.1 — Amazon Cognito domain
1.3.1.2 — Domain preffix — in our case we used ‘recursosartisticos’ so the whole URL will be ‘https://recursosartisticos.auth.us-east-2.amazoncognito.com'
1.3.1.3 — save changes
1.3.2 App client settings (screen)
1.3.2.1 — check the option ‘Cognito User Pool’ under — Enabled Identity Providers
1.3.2.2 — callbackUrl’s — for now we can use the newly generated URL — in our case will be ‘https://recursosartisticos.auth.us-east-2.amazoncognito.com' but we should revise this on the *FUTURE* [this URL is needed to use the HostedUI]
1.3.2.3 — SignOutURL — leave it blank but we will revise this on the *FUTURE*
1.3.2.4 — OAuth 2.0 — Allowed OAuth Flows — check the options [Authorization code grant AND Implicit grant]
1.3.2.5 — OAuth 2.0 — Allowed OAuth Scopes — check the options [email AND openid]
1.3.2.6 — save changes

*** now you can JUMP to step2 — the step1.4 will be done later on (right after step 2.2.9.5) ***

1.4 Create our first test user
1.4.1 — Go to screen ‘Users and groups’ under ‘General Settings’ on the homeScreen of ‘Manage your user pools’
1.4.2 — Click on create user
1.4.2.1 — Username — type ‘admin’
1.4.2.2 — you can uncheck the option ‘Send an invitation to this new user?’
1.4.2.3 — Temporary password type ‘Xkk4Z#2m’
1.4.2.4 — you can uncheck the option ‘Mark phone number as verified?’
1.4.2.5 — Email — type ‘admin@example.com’ and unckeck the option ‘Mark email as verified?’
ps. only verified users are able to logIn, and we want to use the hostedUI to verify users (this can be done in different ways — lambdaTriggers, cognitoConsole, etc)
1.4.2.6 — Create User
ps. Note the just created users and it’s status ‘FORCE_CHANGE_PASSWORD’.
1.4.2.7 — Open up sublime and let’s change above
1.4.2.8 — https://<your_domain>/login?response_type=token&client_id=<your_app_client_id>&scope=email+openid&redirect_uri=<your_callback_url>
1.4.2.9 — let’s change to meet our settings — https://recursosartisticos.auth.us-east-2.amazoncognito.com/login?response_type=code&client_id=70vsigpgm8c474losjk0i7f4n&redirect_uri=https://recursosartisticos.auth.us-east-2.amazoncognito.com
1.4.2.10 — You should see a webPage (hostedUI) with 2 fields. Username and Password. Go ahead and type ‘admin’ for the username and ‘Xkk4Z#3M’ as the password and click ‘SignIn’
1.4.2.11 — ChangePassword screen — go ahead and type a new password, we used ‘x*F-6q8@’ and click ‘Send’ -> you should be redirected to a blankScreen (callBackURL — we can revise this on the *FUTURE)
1.4.2.12 — Relod the page ‘Users and Groups’ and make sure the Status column of the admin user is now on the state ‘CONFIRMED’ (before it was FORCE_CHANGE_PASSWORD)

*** now you can JUMP to step2.3 ***

2. Lambda Triggers (functions)

We will use lambda functions to signIn, verifyToken and etc. Lets create our first lambda function to be used as a trigger. Go to the URL and download the function signin.py.

2.1 — Modify signin.py
2.1.1 — Open up your preferred IDE, in our case we’re using sublime3
2.1.2 — Modify constants. USER_POOL_ID, CLIENT_ID and CLIENT_SECRET (respectively). this are the copied constants from step 1.3 — Change the constants to match your settings
2.1.3 — Comment out the lines 74 to 80 — this lines are used to automatically create the user on the userPool — we DON’T want that — so that’s why we are comenting this lines…
2.1.4 — Modify the lines 86 to 88 to match the following;
*****CODE*****

id_token = resp[‘AuthenticationResult’][‘IdToken’]
access_token = resp[‘AuthenticationResult’][‘AccessToken’]
expires_in = resp[‘AuthenticationResult’][‘ExpiresIn’]
refresh_token = resp[‘AuthenticationResult’][‘RefreshToken’]
print(‘id token: ‘ + id_token)
return {‘status’: ‘success’, ‘id_token’: id_token, ‘access_token’: access_token, ‘expires_in’: expires_in, ‘refresh_token’: refresh_token}

*****END_CODE*****

2.1.4.1 — since the original code only returns the idToken, we need to modify the code to also include accessToken and refreshToken on the response json
2.1.4.2 — Public gist with the final code — https://gist.github.com/fcavalcantirj/dbdd2bda816e356e0af91ef050c2c575

2.2 — Create the first lambda function
2.2.1 — Create Function — Author from Scratch
2.2.2 — Name the function ‘SignIN’
2.2.3 — Runtime — choose ‘python2.7’
2.2.4 — Role — create a custom role
2.2.4.1 — keep default settings
2.2.4.2 — IAM Role — Create a new IAM Role
2.2.4.3 — Role Name — ‘lambda_sign_in’
2.2.4.4 — Policy
***COPY AND PASTE ABOVE

{
"Version":"2012–10–17",
"Statement":[
{
"Effect":"Allow",
"Action":[
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource":"arn:aws:logs:*:*:*"
},
{
"Effect":"Allow",
"Action":[
"cognito-identity:*",
"cognito-idp:*",
"cognito-sync:*",
"iam:ListRoles",
"iam:ListOpenIdConnectProviders",
"sns:ListPratformApplications"
],
"Resource":"*"
}
]
}

***END_COPY_PASTE***
2.2.4.5 — Allow
2.2.5 — Existing Role — choose ‘lambda_sign_in’
2.2.6 — Create Function
2.2.7 — Copy the contents from ‘signin.py’ (step 2.1) and paste inside the webEditor.
2.2.8 — Save changes
2.2.9 — Create a test for it
2.2.9.1 — Click on ‘Select a test event’ -> ‘Configure test events’ -> eventName choose ‘signInTest’ and modify json to look as above
*****CODE*****

{
“username”: “admin@example.com”,
“password”: “Xkk4Z#3M”
}

*****END_CODE*****
2.2.9.2 — Create
2.2.9.3 — Click on Test
2.2.9.4 — I got an error;
*****LOG*****

An error occurred (AccessDeniedException) when calling the AdminInitiateAuth operation: User: arn:aws:sts::522281387974:assumed-role/lambda_basic_execution/SignIN is not authorized to perform: cognito-idp:AdminInitiateAuth on resource: arn:aws:cognito-idp:us-east-2:522281387974:userpool/us-east-2_NOzdTfH9i

*****END_LOG*****
if you get this error make sure you repeat step 2.2.4.4 and make sure the role has the permissions needed to run this lambda function.
2.2.9.5 — Now the function succeeded with this log;
*****LOG*****

An error occurred (UserNotFoundException) when calling the AdminInitiateAuth operation: User does not exist.

*****END_LOG*****

*** makes sense because we have no users…let’s create our first user -> JUMP to step 1.4 ***

2.3 — ReTest the lambda signIn function
2.3.1 — Go to lambda management, select the just created function ‘SignIN’ and edit the test json by clicking ‘Configure test events’ and make sure the json looks like above
*****CODE*****

{
“username”: “admin”,
“password”: “x*F-6q8@”
}

*****END_CODE*****
2.3.2 — Click on ‘Save’ and then ‘Test’ and make sure your execution logs look like above;
*****LOG*****

{
"status":"success",
"id_token":"theIdToken…",
"access_token":"theAccessToken…",
"expires_in":3600,
"refresh_token":"theRefreshToken…"
}

*****END_LOG*****

*** JUMP to step 3.1.2 ***

3. API Gateway

We will have two distinct api gateways routes/resources. One for authentication, the one that will handle
/login /logout and /refreshTokens and another (mocked) to test if authentication is working properly.

3.1 Create the basic routes. In this turorial we will use the same route, and different verbs for different actions.

3.1.1 — PUT (login route)

.Create a new child resource called auth (/auth)
.Create a new method — PUT
.we will configure this method to use a lambda user in a few moments.
.for now use *IntegrationType as mock — as we will later change this to use a lambda

3.1.2 — Use the SignIN lambda function on the PUT method
3.1.2.1 — Click on ‘IntegrationRequest’ under the PUT method (login route)
3.1.2.2 — Choose ‘Lambda Function’ and fill the text field ‘Lambda Function’ with the newly created lambda function (step 2.2) which should be ‘SignIN’ and click ‘Save’
if the UI asks for confirmation, just click on OK

Now we have our first route (login) using the lambda function (SignIN)…we should deploy it to test this function outside AWS — on postman for example, or curl

3.2 — Click on Actions -> deploy to a new stage (dev) -> and deploy it.
3.2.1 — note the InvokeURL with should look like — https://szymzpz20m.execute-api.us-east-2.amazonaws.com/dev

Open up terminal and execute -> curl -v -XPUT -H “Content-type: application/json” -d ‘{“username”: “admin”,”password”: “x*F-6q8@”}’ ‘https://yourDeployedAPIGatewayURL/dev/auth'

3.2.1 — this command should produce the same (almost the same) output of the step 2.3.2

Let’s revise all the steps.
1. Create an UserPool with appClient
2. Create an lambda function
3. Create (and deploy) an API Gateway, with an PUT resource using the lambda function SignIN

if all these steps we’re donne correctly, now we should have a deployed API that has only one route ‘PUT /auth’ that receives a json as input with username/password and should authenticate (using a lambda function) an authorized user previously created by UserPool console, and confirmed by Cognito HostedUI!!!

Enjoy!!!!!

--

--