Skip to main content

User Authentication with AWS Cognito

AWS Cognito is a managed authentication service. Instead of building user registration, login, password hashing, token management, and MFA yourself, you delegate all of it to Cognito.

It gives you:

  • User registration with email/phone verification
  • Secure password policies
  • JWT-based sessions (Access Token, ID Token, Refresh Token)
  • MFA support
  • Social login (Google, Facebook, Apple) via Hosted UI
  • Password reset flows out of the box

Cognito Concepts

TermWhat it is
User PoolA directory of users. Handles registration, login, and token issuance.
App ClientA credential (Client ID + optionally a Client Secret) that your backend uses to call Cognito APIs.
Access TokenJWT used to authorize API requests. Short-lived (default 1 hour).
ID TokenJWT containing user identity claims (email, sub, custom attributes).
Refresh TokenLong-lived token used to get new Access/ID Tokens without re-login.

Create a User Pool

  1. Go to AWS Console → Cognito → Create user pool
  2. Choose Email as the sign-in option
  3. Set a password policy (minimum length, required characters)
  4. Under App clients, create an app client — name it my-app-backend
  5. Choose Don't generate a client secret for a public app client (simpler)
  6. Note the User Pool ID and App Client ID

Add these to your .env:

.env
AWS_REGION=us-east-1
COGNITO_USER_POOL_ID=us-east-1_xxxxxxxxx
COGNITO_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx

Install

npm install @aws-sdk/client-cognito-identity-provider aws-jwt-verify dotenv
  • @aws-sdk/client-cognito-identity-provider — calls Cognito APIs (register, login, etc.)
  • aws-jwt-verify — verifies Cognito JWTs locally without calling Cognito on every request

Cognito Client

config/cognito.js
import { CognitoIdentityProviderClient } from "@aws-sdk/client-cognito-identity-provider";

const cognito = new CognitoIdentityProviderClient({
region: process.env.AWS_REGION,
});

export default cognito;

Register a User

utils/auth.js
import {
SignUpCommand,
ConfirmSignUpCommand,
InitiateAuthCommand,
ForgotPasswordCommand,
ConfirmForgotPasswordCommand,
GlobalSignOutCommand,
} from "@aws-sdk/client-cognito-identity-provider";
import cognito from "../config/cognito.js";

const CLIENT_ID = process.env.COGNITO_CLIENT_ID;

export async function signUp(email, password, name) {
const command = new SignUpCommand({
ClientId: CLIENT_ID,
Username: email,
Password: password,
UserAttributes: [
{ Name: "email", Value: email },
{ Name: "name", Value: name },
],
});
return cognito.send(command);
}

export async function confirmSignUp(email, code) {
const command = new ConfirmSignUpCommand({
ClientId: CLIENT_ID,
Username: email,
ConfirmationCode: code,
});
return cognito.send(command);
}

export async function signIn(email, password) {
const command = new InitiateAuthCommand({
ClientId: CLIENT_ID,
AuthFlow: "USER_PASSWORD_AUTH",
AuthParameters: {
USERNAME: email,
PASSWORD: password,
},
});
const response = await cognito.send(command);
return response.AuthenticationResult;
}

export async function forgotPassword(email) {
const command = new ForgotPasswordCommand({
ClientId: CLIENT_ID,
Username: email,
});
return cognito.send(command);
}

export async function confirmForgotPassword(email, code, newPassword) {
const command = new ConfirmForgotPasswordCommand({
ClientId: CLIENT_ID,
Username: email,
ConfirmationCode: code,
Password: newPassword,
});
return cognito.send(command);
}

export async function signOut(accessToken) {
const command = new GlobalSignOutCommand({ AccessToken: accessToken });
return cognito.send(command);
}

Auth Routes

routes/auth.js
import express from "express";
import {
signUp,
confirmSignUp,
signIn,
forgotPassword,
confirmForgotPassword,
signOut,
} from "../utils/auth.js";

const router = express.Router();

router.post("/register", async (req, res) => {
const { email, password, name } = req.body;
if (!email || !password || !name) {
return res.status(400).json({ message: "email, password, and name are required" });
}
try {
await signUp(email, password, name);
res.status(201).json({ message: "Registration successful. Check your email for a verification code." });
} catch (err) {
res.status(400).json({ message: err.message });
}
});

router.post("/verify", async (req, res) => {
const { email, code } = req.body;
try {
await confirmSignUp(email, code);
res.json({ message: "Email verified. You can now log in." });
} catch (err) {
res.status(400).json({ message: err.message });
}
});

router.post("/login", async (req, res) => {
const { email, password } = req.body;
try {
const tokens = await signIn(email, password);
res.json({
accessToken: tokens.AccessToken,
idToken: tokens.IdToken,
refreshToken: tokens.RefreshToken,
expiresIn: tokens.ExpiresIn,
});
} catch (err) {
res.status(401).json({ message: err.message });
}
});

router.post("/forgot-password", async (req, res) => {
const { email } = req.body;
try {
await forgotPassword(email);
res.json({ message: "Password reset code sent to your email." });
} catch (err) {
res.status(400).json({ message: err.message });
}
});

router.post("/reset-password", async (req, res) => {
const { email, code, newPassword } = req.body;
try {
await confirmForgotPassword(email, code, newPassword);
res.json({ message: "Password reset successfully." });
} catch (err) {
res.status(400).json({ message: err.message });
}
});

router.post("/logout", async (req, res) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ message: "No token provided" });
try {
await signOut(token);
res.json({ message: "Logged out successfully." });
} catch (err) {
res.status(400).json({ message: err.message });
}
});

export default router;

Key Takeaways

  • Cognito handles all the hard parts of auth: secure storage, hashing, verification, token rotation
  • After login, the client receives three tokens — store only the Access Token in memory (or a secure cookie), not localStorage
  • The Access Token is what you send in Authorization: Bearer <token> headers
  • Next, see the Cognito Middleware doc to protect your routes using JWT verification