I am trying to secure my serverless NodeJS apis using AWS Cognito User Pools.
Below is a sample of my serverless framework configuration:
service: hello-world
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs14.x
environment:
user_pool_id: { Ref: UserPool }
client_id: { Ref: UserClient }
iam:
role:
statements:
- Effect: Allow
Action:
- cognito-idp:AdminInitiateAuth
- cognito-idp:AdminCreateUser
- cognito-idp:AdminSetUserPassword
Resource: "*"
functions:
loginUser:
handler: ./auth/login.handler
events:
- http:
path: auth/login
method: post
cors: true
signupUser:
handler: ./auth/signup.handler
events:
- http:
path: auth/signup
method: post
cors: true
list:
handler: ./users/users.handler
events:
- http:
path: users/list
method: get
cors: true
authorizer:
type: COGNITO_USER_POOLS
authorizerId:
Ref: ApiGatewayAuthorizer
resources:
Resources:
ApiGatewayAuthorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
Name: CognitoUserPool
Type: COGNITO_USER_POOLS
IdentitySource: method.request.header.Authorization
RestApiId:
Ref: ApiGatewayRestApi
ProviderARNs:
- Fn::GetAtt:
- UserPool
- Arn
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: serverless-auth-pool
Schema:
- Name: email
Required: true
Mutable: true
Policies:
PasswordPolicy:
MinimumLength: 6
AutoVerifiedAttributes: ["email"]
UserClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: user-pool-ui
GenerateSecret: false
UserPoolId: { Ref: UserPool }
AccessTokenValidity: 1
IdTokenValidity: 1
ExplicitAuthFlows:
- "ADMIN_NO_SRP_AUTH"
I can successfully can call the signup and login endpoints to get a token and then use this token as an Authorization header to call my /users/list
endpoint to get a list of users.
My problem is that I was expecting the login endpoint to return 3 tokens - an id token, an access token and a refresh token.
The login endpoint currently only returns one token that has a claim of:
"token_use": "id"
If I pass this token to the /users/list
api then it is successfully validated, but I thought that the api would need the access token instead of the id token for authentication.
Does anyone know if my assumption is correct and how to fix the issue or have I misunderstood how the auth flow works ?
I managed to figure out that my login function was only returning the id token. Below is the updated login function to return all 3 tokens:
const AWS = require('aws-sdk')
const { sendResponse, validateInput } = require("../functions");
const cognito = new AWS.CognitoIdentityServiceProvider()
module.exports.handler = async (event) => {
try {
const isValid = validateInput(event.body)
if (!isValid)
return sendResponse(400, { message: 'Invalid input' })
const { email, password } = JSON.parse(event.body)
const { user_pool_id, client_id } = process.env
const params = {
AuthFlow: "ADMIN_NO_SRP_AUTH",
UserPoolId: user_pool_id,
ClientId: client_id,
AuthParameters: {
USERNAME: email,
PASSWORD: password
}
}
const response = await cognito.adminInitiateAuth(params).promise();
return sendResponse(200, {
message: 'Success',
accessToken: response.AuthenticationResult.AccessToken,
idToken: response.AuthenticationResult.IdToken ,
refreshToken: response.AuthenticationResult.RefreshToken ,
})
}
catch (error) {
const message = error.message ? error.message : 'Internal server error'
return sendResponse(500, { message })
}
}
Note: I also had to add scopes
to the authorizer to be make the api accept the access token
list:
handler: ./users/users.handler
events:
- http:
path: users/list
method: get
cors: true
authorizer:
type: COGNITO_USER_POOLS
scopes:
- aws.cognito.signin.user.admin
authorizerId:
Ref: ApiGatewayAuthorizer
The full signup/login flow that I am using is based on the following free code camp example:
https://www.freecodecamp.org/news/aws-cognito-authentication-with-serverless-and-nodejs/