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:
jwtcallback — runs when a token is created or refreshed; used to embed custom claims.sessioncallback — 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;
| Claim | Value | Purpose |
|---|---|---|
aud | calmony-pay | Identifies the intended audience (this service). |
iss | process.env.NEXTAUTH_URL | Identifies 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:
- Tokens issued by other services — even if signed with a shared secret — cannot be used to authenticate against Calmony Pay.
- Tokens issued on one deployment (e.g. staging) cannot be replayed against another (e.g. production), provided
NEXTAUTH_URLdiffers between environments.
Security Impact
| Before v1.0.104 | From 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_URLmatches 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.