accessToken verification
node.js
express.js
aws-amplify
JWT authentication

How to verify accessToken in node/express using aws-amplify?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In an Express API, token verification belongs on the server, not in the browser-oriented parts of Amplify. The reliable approach is to validate the Cognito JWT signature, issuer, expiration, and intended token type before a request reaches protected business logic.

Why Amplify Is Not the Main Server-Side Verifier

aws-amplify is designed primarily for frontend applications. It is excellent for signing users in and obtaining Cognito tokens, but an Express service should usually verify those tokens with a JWT verification library rather than by depending on Amplify runtime behavior.

For Amazon Cognito, the practical choice is aws-jwt-verify. It understands Cognito user pools, fetches the JWKS keys, caches them, and checks standard claims for you.

Install it in your API:

bash
npm install aws-jwt-verify

Verify a Cognito Access Token

An access token is different from an ID token. If your middleware expects an access token, verify tokenUse: 'access' explicitly. That prevents an ID token from being accepted accidentally.

Create a verifier once at startup:

javascript
1const { CognitoJwtVerifier } = require('aws-jwt-verify');
2
3const verifier = CognitoJwtVerifier.create({
4  userPoolId: 'us-east-1_Example123',
5  tokenUse: 'access',
6  clientId: '4exampleclientid123456789'
7});

Then build Express middleware:

javascript
1const express = require('express');
2const app = express();
3
4async function requireAccessToken(req, res, next) {
5  const authHeader = req.headers.authorization || '';
6  const [scheme, token] = authHeader.split(' ');
7
8  if (scheme !== 'Bearer' || !token) {
9    return res.status(401).json({ error: 'Missing bearer token' });
10  }
11
12  try {
13    const payload = await verifier.verify(token);
14    req.user = {
15      sub: payload.sub,
16      username: payload.username,
17      scope: payload.scope
18    };
19    next();
20  } catch (error) {
21    return res.status(401).json({ error: 'Invalid or expired token' });
22  }
23}
24
25app.get('/profile', requireAccessToken, (req, res) => {
26  res.json({ message: 'Authorized', user: req.user });
27});
28
29app.listen(3000);

That middleware checks the signature against Cognito's public keys and rejects expired or malformed tokens.

Where Amplify Fits

Amplify still matters if your client application uses it to sign in users and obtain the tokens sent to Express. A browser or mobile app might look like this:

javascript
1import { fetchAuthSession } from 'aws-amplify/auth';
2
3async function getAccessToken() {
4  const session = await fetchAuthSession();
5  return session.tokens?.accessToken?.toString();
6}

The client sends that token in the Authorization header, and the Express server verifies it independently. This split is important because the server must not trust a token merely because the client says it came from Amplify.

Validate the Right Claims

For Cognito access tokens, the minimum checks are:

  • signature is valid
  • token is not expired
  • issuer matches your user pool
  • audience or client binding matches your app client when applicable
  • token use is access

If your API relies on scopes or groups, inspect them after verification:

javascript
1function requireScope(expectedScope) {
2  return (req, res, next) => {
3    const scopes = (req.user.scope || '').split(' ');
4    if (!scopes.includes(expectedScope)) {
5      return res.status(403).json({ error: 'Insufficient scope' });
6    }
7    next();
8  };
9}
10
11app.get('/reports', requireAccessToken, requireScope('reports.read'), (req, res) => {
12  res.json({ reports: [] });
13});

Verification proves the token is genuine. Authorization logic decides whether that genuine token is allowed to perform a specific action.

If You Prefer jose

You can also verify Cognito JWTs with jose, but you then have to manage more details yourself, including JWKS loading and claim checks. That is useful when you need provider-agnostic JWT handling, but for Cognito-only APIs aws-jwt-verify is simpler and harder to misconfigure.

Common Pitfalls

A common mistake is verifying the wrong token type. Cognito issues both ID tokens and access tokens, and they serve different purposes. Accepting an ID token where an access token is required weakens the contract between client and API.

Another problem is treating Amplify as the trust boundary. The API must verify the JWT on every protected request, even if the frontend already authenticated successfully.

Teams also forget to check scopes or groups after signature verification. A valid token is only authenticated; it is not automatically authorized for every route.

Summary

  • Use Amplify on the client to obtain Cognito tokens, not as the primary verification mechanism inside Express.
  • Verify server-side with aws-jwt-verify or a comparable JWT library.
  • Require tokenUse: 'access' when protecting API endpoints with access tokens.
  • Separate authentication from authorization by checking scopes or groups after verification.
  • Never trust a bearer token until the API validates signature, expiry, issuer, and expected claims.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.