All Docs
FeaturesCalmony PayUpdated March 15, 2026

Security Fix: JWT Audience & Issuer Claim Validation (SEC-20)

Security Fix: JWT Audience & Issuer Claim Validation (SEC-20)

Version: 1.0.104
Category: Authentication & Session Security

Overview

Calmony Pay v1.0.104 closes a security gap in JWT token validation identified under control SEC-20. The fix adds explicit aud (audience) and iss (issuer) claim enforcement to all tokens issued by the platform, preventing cross-service token replay and future-proofing authentication as the SaaS Factory platform expands.

Background

Calmony Pay uses Auth.js v5 for authentication. JWT tokens flow through two callbacks:

  • jwt callback — runs when a token is created or refreshed; used to embed custom claims.
  • session callback — runs when a session is read; used to expose token data to the application.

Prior to this release, the jwt callback only set token.id (the authenticated user's ID). Auth.js v5 does not automatically populate aud or iss claims — this must be done explicitly in application code.

The absence of these claims meant that a JWT issued by Calmony Pay had no cryptographic binding to the service or deployment origin. In a multi-service architecture — such as the broader SaaS Factory platform — a token from one service could be presented to another that shares the same signing secret, a classic cross-service token replay attack.

What Was Fixed

jwt Callback — Claim Injection

The jwt callback in src/lib/auth.ts now stamps two additional claims on every token at issuance:

// src/lib/auth.ts (jwt callback)
token.aud = 'calmony-pay';
token.iss = process.env.NEXTAUTH_URL;
ClaimValuePurpose
audcalmony-payIdentifies the intended audience (this service).
issprocess.env.NEXTAUTH_URLIdentifies the issuing deployment origin.

session Callback — Claim Validation

The session callback now validates both claims before allowing a session to be established. A token that does not carry the expected aud and iss values is rejected.

// src/lib/auth.ts (session callback)
if (token.aud !== 'calmony-pay' || token.iss !== process.env.NEXTAUTH_URL) {
  throw new Error('Invalid token claims: audience or issuer mismatch');
}

This ensures:

  1. Tokens issued by other services — even if signed with a shared secret — cannot be used to authenticate against Calmony Pay.
  2. Tokens issued on one deployment (e.g. staging) cannot be replayed against another (e.g. production), provided NEXTAUTH_URL differs between environments.

Security Impact

Before v1.0.104From v1.0.104
aud claim set✓ (calmony-pay)
iss claim set✓ (NEXTAUTH_URL)
Cross-service replay protection
Cross-environment replay protection

Severity in current deployment: Low (single-service, single signing secret).
Severity if left unaddressed as platform grows: Medium–High.

Environment Requirements

NEXTAUTH_URL must be set correctly in every environment. It is now used as the iss claim value and is validated on each session read. If this variable is missing or misconfigured, token validation will fail.

# .env
NEXTAUTH_URL=https://pay.calmony.com

For local development:

NEXTAUTH_URL=http://localhost:3000

Note: Ensure NEXTAUTH_URL matches exactly between token issuance and session validation environments. Any mismatch will cause authentication failures for existing sessions — users will need to log in again after this update is deployed.

Related Files

  • src/lib/auth.ts — JWT and session callback implementation.