DEV Community

Geovane Oliveira
Geovane Oliveira

Posted on

Securing Serverless APIs with Amazon Cognito and API Gateway JWT Authorizers

Introduction

Securing APIs is one of those topics that looks simple at first, but quickly becomes complex in real-world scenarios.
When you move to a serverless architecture, this challenge becomes even more critical.

Many implementations end up pushing authentication logic into Lambda functions, creating custom token validation, extra code paths, and unnecessary operational overhead.

In this article, I will walk through a practical and production-ready approach to secure an AWS API Gateway endpoint using Amazon Cognito and the native JWT Authorizer, without Lambda authorizers or custom authentication logic.

This setup is cost-efficient, scalable, and relies entirely on managed AWS services.

All the reference code and supporting artifacts used in this article are available in the following GitHub repository:
aws-secure-serverless-api-cognito-jwt


Architecture Overview

The architecture used in this experiment is intentionally simple:

Amazon Cognito User Pool for authentication

  • API Gateway HTTP API
  • Native JWT Authorizer
  • AWS Lambda as the backend
  • CloudWatch Logs for observability

The key idea is to let Amazon Cognito handle authentication, while API Gateway is responsible for validating JWTs before the request ever reaches Lambda.

This means invalid or expired tokens never invoke your backend code.

The full request flow is illustrated in the diagram below:


Step 1: Creating the Cognito User Pool and App Client

We start by creating a Cognito User Pool and an App Client.

Important configuration points:

  • Enable USER_PASSWORD_AUTH
  • Disable client secret (required for public clients and Postman testing)
  • Use email as a sign-in alias

Cognito User Pool used as the authentication provider

This App Client will be responsible for issuing Access Tokens and ID Tokens.

This corresponds to step 1 in the architecture, where the user authenticates directly with Amazon Cognito.

User confirmation requirement:

When users are created manually or via the AWS Console, they may initially be in the FORCE_CHANGE_PASSWORD or UNCONFIRMED state.

For this experiment, the user must be fully confirmed before authentication succeeds.

To ensure this, the user account was confirmed using AWS CloudShell and the AWS CLI, setting a permanent password and moving the user to the CONFIRMED state.

CloudShell confirming the Cognito user status

This step is important because Cognito will reject authentication attempts for users that are not fully confirmed.

Once the user is confirmed, authentication via Postman works as expected.

App Client settings

App Client settings B


Step 2: Authenticating with Cognito

Authentication is performed using the InitiateAuth API.

Using Postman, we send a request to the Cognito endpoint with:

  • AuthFlow: USER_PASSWORD_AUTH
  • ClientId
  • Username and password

If authentication succeeds, Cognito returns:

  • AccessToken
  • IdToken
  • RefreshToken

At this point, we already have everything needed to call a protected API.

PRINT 4 – Postman InitiateAuth request
PRINT 5 – Successful token response


Step 3: Configuring the API Gateway JWT Authorizer

Instead of using a Lambda Authorizer, we configure a native JWT Authorizer directly in API Gateway.

Key settings:

  • Issuer: Cognito User Pool issuer URL
  • Audience: Cognito App Client ID
  • Identity source: Authorization header

This tells API Gateway exactly how to validate incoming JWTs automatically.

PRINT 6 – JWT Authorizer configuration


Step 4: Protecting the API Route

Next, we attach the JWT Authorizer to the /secure route.

Once attached:

  • Requests without a token return 401 Unauthorized
  • Requests with invalid or expired tokens are rejected
  • Only valid tokens are allowed through

All of this is enforced by API Gateway itself, before Lambda is invoked.

PRINT 7 – Route authorization attached


Step 5: Lambda Backend and Token Claims

At this stage, authentication and token validation are already completed.

The Lambda function does not validate JWTs, does not decode tokens, and does not contain authentication logic.

API Gateway injects the decoded JWT claims directly into the request context, under:

event.requestContext.authorizer.jwt.claims

Below is the complete Lambda function used as the secure backend:

import json

def lambda_handler(event, context):
    print("Lambda secure-api-backend INVOCADA")
    print("RequestId:", context.aws_request_id)

    claims = event["requestContext"]["authorizer"]["jwt"]["claims"]

    print("JWT claims recebidas:")
    print(json.dumps({
        "sub": claims.get("sub"),
        "email": claims.get("email"),
        "username": claims.get("cognito:username"),
        "issuer": claims.get("iss"),
        "token_use": claims.get("token_use"),
        "scope": claims.get("scope")
    }))

    response = {
        "message": "Authorized request",
        "user": {
            "sub": claims.get("sub"),
            "email": claims.get("email"),
            "username": claims.get("cognito:username"),
            "issuer": claims.get("iss"),
            "token_use": claims.get("token_use"),
            "scope": claims.get("scope")
        }
    }

    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": json.dumps(response)
    }

Enter fullscreen mode Exit fullscreen mode

This keeps the backend extremely simple and focused:

  • API Gateway handles security
  • Lambda consumes already validated identity data
  • Logs provide full traceability

PRINT 8 – Lambda code


Step 6: Validation and Results

We tested three different scenarios:

No token
→ 401 Unauthorized
PRINT 10 – Postman unauthorized response

Invalid token
→ 401 Unauthorized
Invalid token

Valid Access Token
→ 200 OK and Lambda executed
PRINT 11 – Postman authorized response + Lambda output

CloudWatch Logs confirm that the Lambda function is only invoked when the token is valid, proving that the security boundary is enforced at the API Gateway level.

PRINT 9 – CloudWatch Logs showing JWT claims


Reference Implementation

The complete reference implementation, including the Lambda function, Postman collection, and architecture diagram, is available on GitHub:
aws-secure-serverless-api-cognito-jwt


Conclusion

Using Amazon Cognito together with API Gateway JWT Authorizers is one of the cleanest and most efficient ways to secure serverless APIs on AWS.

There is no custom authentication code, no Lambda Authorizers, and no additional infrastructure to manage.

API Gateway becomes the security gate, and Lambda focuses only on business logic.

This is a pattern I recommend for modern, scalable, and secure serverless applications.

Top comments (0)