How to Use AWS Cognito Authentication with Serverless and NodeJS: Complete Guide
By Braincuber Team
Published on April 4, 2026
What You'll Learn:
- Set up a Serverless Framework project for AWS Cognito authentication
- Configure serverless.yml with IAM permissions, Lambda functions, and Cognito resources
- Build user registration Lambda function with adminCreateUser API
- Build user login Lambda function with adminInitiateAuth and token generation
- Implement private route with Cognito User Pool authorizer on API Gateway
- Configure user pool schema, password policies, and token validity settings
In this complete tutorial, we will see how to create a REST API application for authentication using AWS Cognito, AWS Serverless, and NodeJS. We are going to use Lambda functions, API Gateway, and the Serverless Framework to achieve this. This step by step guide will walk you through every configuration and code file needed to build a fully functional authentication system. Whether you are a beginner or experienced developer, this beginner guide will show you exactly how to implement secure token-based authentication on AWS.
Project Setup and Structure
Before we start coding, let us understand the project structure we will be building. Our project organizes all Lambda function files in a folder named user and all utility functions in a separate folder called functions. The serverless.yml file is the core configuration file for any Serverless-based project.
serverless-cognito-auth/
├── functions/
│ └── index.js (utility functions)
├── user/
│ ├── signup.js (user registration Lambda)
│ ├── login.js (user login Lambda)
│ └── private.js (protected route Lambda)
└── serverless.yml (core Serverless config)
The user folder contains all our Lambda function files for authentication operations. The functions folder holds utility functions like sendResponse for sending HTTP responses and validateInput for validating request body data. The serverless.yml file is the core configuration file that defines our entire infrastructure.
Configuring the serverless.yml File
Let us start coding our serverless.yml file where we will be defining all our Lambda functions. It will hold our logic for Sign up, Sign in, and testing a private route. We will also define our AWS Cognito user pool and user pool client with different settings and permissions. Let us break this file into different parts so we can understand each part separately.
Defining IAM Permissions and Settings
We will start by defining things like environment variables, Serverless project configuration, settings, and AWS IAM permissions. This is the foundation of our authentication system.
service: serverless-cognito-auth
provider:
name: aws
runtime: nodejs14.x
environment:
user_pool_id: { Ref: UserPool }
client_id: { Ref: UserClient }
iamRoleStatements:
- Effect: Allow
Action:
- cognito-idp:AdminInitiateAuth
- cognito-idp:AdminCreateUser
- cognito-idp:AdminSetUserPassword
Resource: "*"
Under the provider block we are defining multiple configurations and settings. Let us discuss each part in detail.
The environment block defines all our environment variables which we want to use in our project, like in our Lambda functions. We set the user pool id and client id of our AWS Cognito user pool and client. We are also referencing the resources which we are going to define later on in this file. These references are going to give us the id for the created user pool and client.
The iamRoleStatements block defines all the AWS IAM permissions which we want to give to our resources. In our case, these permissions are required by our Lambda functions which are going to use the AWS Cognito API. We need three permissions: AdminInitiateAuth for login, AdminCreateUser for registration, and AdminSetUserPassword for setting passwords during signup.
Defining the Lambda Functions
Next, we will define our Lambda functions. We are going to need three of them: one for user registration, one for user login, and the last one to test a private route.
functions:
loginUser:
handler: user/login.handler
events:
- http:
path: user/login
method: post
cors: true
signupUser:
handler: user/signup.handler
events:
- http:
path: user/signup
method: post
cors: true
privateAPI:
handler: user/private.handler
events:
- http:
path: user/private
method: post
cors: true
authorizer:
name: PrivateAuthorizer
type: COGNITO_USER_POOLS
arn:
Fn::GetAtt:
- UserPool
- Arn
claims:
- email
In the events block, we define the event on which our Lambda function will get invoked. So in our case, we are adding HTTP event here, which will be our AWS API Gateway call.
The authorizer configuration is critical. Here we define our authorizer which will get called before our main Lambda function gets invoked. We are using AWS Cognito authorizer for our API Gateway which checks on each request if the valid access token is being passed with it. And only then it allows our main Lambda function to be invoked.
We need to pass ARN of our AWS Cognito user pool, so we are referencing that resource and getting the ARN from it by using the Fn::GetAtt function. We are also using the claims block to have the specific fields available from the decoded access token object in our main Lambda function in the event object.
Defining the Cognito Resources
Finally, we are going to define all the resources which we need in our serverless.yml file. Here we are creating our AWS Cognito user pool and client.
resources:
Resources:
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: 5
IdTokenValidity: 5
ExplicitAuthFlows:
- "ADMIN_NO_SRP_AUTH"
Let us go through each of these options in detail:
| Property | Description |
|---|---|
| Schema | Defines the schema of user data created in the user pool. We define attributes like email, age, gender, and so on. Email is set as Required and Mutable. |
| Policies | Defines password validation policy - settings for how the password should be before it can get saved in the user pool. MinimumLength is set to 6. |
| AutoVerifiedAttributes | Sets fields to be automatically verified like email and phone number. Setting this skips the verification process for newly created users. |
| AccessTokenValidity | Defines the number of hours the access token will be valid. Set to 5 hours in this configuration. |
| ExplicitAuthFlows | Defines authentication flows allowed by the user pool client. ADMIN_NO_SRP_AUTH authorizes users with username and password. |
Important: ADMIN_NO_SRP_AUTH
The ADMIN_NO_SRP_AUTH flow is used to authorize users with username and password directly. This is why we are passing it as the value in ExplicitAuthFlows. Without this, the login authentication flow would not work with our simple username/password approach.
Coding the Lambda Functions
It is now time to start coding our REST API logic by creating Lambda functions for user registration, user login, and our private route to test everything out.
User Registration (signup.js)
First, we are going to create a new file inside the user folder and name it signup.js. This file will hold all the logic related to user registration. Let us see how the code will look in this file by breaking it into parts.
const AWS = require('aws-sdk')
const { sendResponse, validateInput } = require("../functions");
const cognito = new AWS.CognitoIdentityServiceProvider()
module.exports.handler = async (event) => {
const isValid = validateInput(event.body)
if (!isValid)
return sendResponse(400, { message: 'Invalid input' })
const { email, password } = JSON.parse(event.body)
const { user_pool_id } = process.env
const params = {
UserPoolId: user_pool_id,
Username: email,
UserAttributes: [
{ Name: 'email', Value: email },
{ Name: 'email_verified', Value: 'true' }
],
MessageAction: 'SUPPRESS'
}
const response = await cognito.adminCreateUser(params).promise()
if (response.User) {
const paramsForSetPass = {
Password: password,
UserPoolId: user_pool_id,
Username: email,
Permanent: true
}
await cognito.adminSetUserPassword(paramsForSetPass).promise()
}
return sendResponse(200, {
message: 'User registration successful'
})
}
Let us break down each section of this code:
Imports: We are going to use the aws-sdk NPM package to interact with AWS Cognito API. We are also importing two utility functions: sendResponse for sending the response of the HTTP request, and validateInput for validating the request body data. We are also getting the instance of the Cognito Identity Provider to interact with the user pool API.
Validating Request Body: Here we are validating the request body data and checking if the data is valid or not. If it is not valid, we are returning the response and sending an appropriate message with a 400 status code.
Creating User in Cognito: Here we get the email and password from the request body and also the user pool id from the environment variables object. After that, we create a parameter object for the adminCreateUser API. MessageAction is set as SUPPRESS because we do not want to send the default email sent by AWS Cognito when a new user gets created in the user pool.
Setting Password: When our user gets created in the user pool, we need to set the password for that user. We do this because we do not want users to create a password when they login as they are already sending their password in the HTTP request. This will also change the user status to CONFIRMED in the Cognito user pool. We also need to pass Permanent as true because otherwise a temporary password will be generated for the user.
User Login (login.js)
Now we will start with the user login by creating a file inside the user folder named login.js. This login API will start the authentication process and send the identity token to the user which they can use to access the authorized routes.
The login.js file will look very similar to signup.js. The only difference will be the parameters and the API call.
const AWS = require('aws-sdk')
const { sendResponse, validateInput } = require("../functions");
const cognito = new AWS.CognitoIdentityServiceProvider()
module.exports.handler = async (event) => {
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',
token: response.AuthenticationResult.IdToken
})
}
The main thing to understand in this code is that we are using AuthFlow as ADMIN_NO_SRP_AUTH which is used for authenticating the user based on username and password. After that we are just calling the adminInitiateAuth API and sending the identity token to the user.
The IdToken returned in response.AuthenticationResult.IdToken is what the user will use to access protected routes. This token contains the claims (like email) that we configured in the authorizer section of our serverless.yml file.
Private Route (private.js)
We will add one more Lambda function which will act as a private route. To access this API endpoint we will need to send a valid identity token in the request header with the key Authorization.
Start by creating a new file inside the user folder and name it private.js.
const { sendResponse } = require("../functions");
module.exports.handler = async (event) => {
return sendResponse(200, {
message: `Email ${event.requestContext.authorizer.claims.email} has been authorized`
})
}
Here we are just getting the email from the request and sending a simple response. This Lambda function will only get invoked if the request passes the authorizer layer added in the API Gateway configuration. The email is available through event.requestContext.authorizer.claims.email because we specified claims: [email] in our serverless.yml authorizer configuration.
Utility Functions
The utility functions in the functions folder provide shared functionality across all our Lambda handlers. The sendResponse function formats HTTP responses consistently, and validateInput ensures request data meets our requirements before processing.
sendResponse
Formats and returns standardized HTTP responses with proper status codes, headers, and JSON body for API Gateway integration.
validateInput
Validates request body data to ensure required fields (email, password) are present and properly formatted before processing.
How the Authentication Flow Works
Understanding the complete authentication flow is essential for building and debugging your system. Here is the step by step guide to how each piece connects together.
Deploy Infrastructure
Run serverless deploy to create the Cognito User Pool, User Pool Client, API Gateway, and three Lambda functions (signup, login, private) in your AWS account.
Register a User
Send a POST request to /user/signup with email and password. The Lambda creates the user in Cognito via adminCreateUser, sets the password permanently via adminSetUserPassword, and returns success.
Login and Get Token
Send a POST request to /user/login with email and password. The Lambda calls adminInitiateAuth with ADMIN_NO_SRP_AUTH flow and returns the IdToken to the client.
Access Protected Routes
Send a POST request to /user/private with the IdToken in the Authorization header. API Gateway validates the token via Cognito authorizer before invoking the Lambda function.
Cognito User Pool Configuration Reference
| Setting | Value | Purpose |
|---|---|---|
| UserPoolName | serverless-auth-pool | Identifies the user pool in AWS Console |
| Schema Email | Required: true, Mutable: true | Email is mandatory and can be changed later |
| PasswordPolicy | MinimumLength: 6 | Minimum password length requirement |
| AutoVerifiedAttributes | ["email"] | Skip email verification step for new users |
| AccessTokenValidity | 5 hours | Access token expiration duration |
| IdTokenValidity | 5 hours | Identity token expiration duration |
| ExplicitAuthFlows | ADMIN_NO_SRP_AUTH | Enable username/password authentication |
API Endpoints Summary
| Endpoint | Method | Auth Required | Description |
|---|---|---|---|
| /user/signup | POST | No | Register new user in Cognito user pool |
| /user/login | POST | No | Authenticate user and return IdToken |
| /user/private | POST | Yes (IdToken) | Protected route requiring valid Cognito token |
Possible Improvements
There are many things you can add or improve in the current code. Here are some recommended enhancements for production use.
Enhanced Data Validation
The data validation can be increased with email format checking, password strength requirements, and more comprehensive input sanitization.
Forgot Password Flow
A forget password feature can be added using Cognito's built-in forgot password APIs with email-based verification codes.
Token Refresh Mechanism
Implement refresh token handling to allow users to get new access tokens without re-authenticating when their tokens expire.
Error Handling
Add comprehensive error handling for Cognito API failures, network issues, and edge cases like duplicate user registration.
Frequently Asked Questions
What is AWS Cognito and why use it with Serverless?
AWS Cognito is a fully managed identity service that provides user sign-up, sign-in, and access control. Combined with Serverless Framework and NodeJS Lambda functions, it creates a scalable, cost-effective authentication system without managing servers.
What does ADMIN_NO_SRP_AUTH mean in Cognito?
ADMIN_NO_SRP_AUTH is an authentication flow that allows users to authenticate directly with username and password without the Secure Remote Password (SRP) protocol. It simplifies the login process for server-side authentication scenarios.
How does the Cognito authorizer work on API Gateway?
The Cognito authorizer intercepts each API request before the Lambda function executes. It validates the Authorization header token against the User Pool, and only allows the request through if the token is valid and not expired.
Why set MessageAction to SUPPRESS during user creation?
Setting MessageAction to SUPPRESS prevents AWS Cognito from sending the default welcome email when a new user is created. This is useful when you want to handle user communication through your own email system instead.
How long are Cognito tokens valid by default?
In this configuration, both Access Token and Id Token are valid for 5 hours. You can adjust these values in the UserPoolClient configuration. Refresh tokens are valid for 30 days by default and can be used to obtain new tokens.
Need Help with AWS Architecture?
Our experts can help you design secure, scalable authentication systems and cloud infrastructure on AWS.
