Skip to content

Next.js adapter JWT validation fails with LocalStack due to hardcoded AWS issuer URLs #14463

Open
@mathieubruguier

Description

@mathieubruguier

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

No response

Backend

None

Environment information

# Put output below this line

  System:
    OS: macOS 15.5
    CPU: (16) arm64 Apple M4 Max
    Memory: 66.25 MB / 64.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 22.16.0 - ~/.nvm/versions/node/v22.16.0/bin/node
    npm: 10.9.2 - ~/.nvm/versions/node/v22.16.0/bin/npm
    pnpm: 10.5.2 - ~/.nvm/versions/node/v22.16.0/bin/pnpm
    Watchman: 2025.05.26.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 138.0.7204.101
    Safari: 18.5
  npmPackages:
    @turbo/gen: ^2.5.0 => 2.5.0 
    husky: ^9.1.7 => 9.1.7 
    syncpack: ^13.0.3 => 13.0.3 
    turbo: ^2.5.0 => 2.5.0 
    typescript: ^5.8.3 => 5.8.3 
  npmGlobalPackages:
    @anthropic-ai/claude-code: 1.0.3
    corepack: 0.32.0
    npm: 10.9.2
    pnpm: 10.11.0

Describe the bug

AWS Amplify's JWT token validation in the Next.js adapter fails when using LocalStack for local development due to hardcoded AWS URL construction in the JWT verification process. The CognitoJwtVerifier.parseUserPoolId() method in cognito-verifier.js (lines 67-80) hardcodes the issuer URL as https://cognito-idp.${region}.amazonaws.com/${userPoolId}, but LocalStack JWTs contain issuer claims pointing to local endpoints (e.g., http://localhost:4567/us-east-1_localuserpool).

Expected behavior

The JWT validator should respect custom endpoints specified in the Amplify configuration. When userPoolEndpoint is configured to point to LocalStack (http://localhost:4567), the validator should construct issuer URLs using this endpoint instead of hardcoding amazonaws.com domains.

Reproduction steps

  1. Set up Next.js application with AWS Amplify adapter
  2. Configure LocalStack with Cognito IDP service
  3. Set environment variables:
  NEXT_PUBLIC_COGNITO_POOL_ENDPOINT=http://localhost:4567
  NEXT_PUBLIC_COGNITO_USER_POOL_ID=us-east-1_localuserpool
  1. Configure Amplify with custom endpoint:
  export const amplifyConfig: ResourcesConfig = {
    Auth: {
      Cognito: {
        userPoolId: 'us-east-1_localuserpool',
        userPoolEndpoint: 'http://localhost:4567',
        userPoolClientId: 'localclientid',
        identityPoolId: 'us-east-1:local-identity-pool',
        identityPoolEndpoint: 'http://localhost:4567',
      },
    },
  };
  1. Generate JWT token from LocalStack (issuer: http://localhost:4567/us-east-1_localuserpool)
  2. Attempt validation in Next.js middleware/server components
  3. Observe validation failure due to issuer mismatch

Code Snippet

// Put your code below this line.

Log output

// Put your logs below this line


aws-exports.js

No response

Manual configuration

{
  Auth: {
      Cognito: {
        userPoolId: appConfig.NEXT_PUBLIC_COGNITO_USER_POOL_ID, // us-east-1_localuserpool
        userPoolEndpoint: appConfig.NEXT_PUBLIC_COGNITO_POOL_ENDPOINT, // http://localhost:4567
        identityPoolId: appConfig.NEXT_PUBLIC_COGNITO_IDENTITY_POOL_ID, // us-east-1:localidentitypool
        userPoolClientId: appConfig.NEXT_PUBLIC_COGNITO_CLIENT_ID, // localuserpoolclient
        identityPoolEndpoint: appConfig.NEXT_PUBLIC_COGNITO_POOL_ENDPOINT, // http://localhost:4567
      },
    }
}

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

  • Affected files:
    • @aws-amplify/adapter-nextjs/dist/esm/utils/createTokenValidator.mjs
    • @aws-amplify/adapter-nextjs/dist/esm/utils/isValidCognitoToken.mjs
    • aws-jwt-verify/dist/esm/cognito-verifier.js (lines 67-80)
    • aws-jwt-verify/dist/esm/jwt-rsa.js (line 281)
  • Framework: Next.js with AWS Amplify adapter
  • Environment: LocalStack for local development
  • Works in: Pure React client-side applications (doesn't use server-side JWT validation)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions