All Docs
FeaturesCalmony PayUpdated March 15, 2026

Security Finding: SEC-19 — OAuth State Parameter (CSRF) Delegation

Security Finding: SEC-19 — OAuth State Parameter (CSRF) Delegation

Version: v1.0.87
Category: Authentication & Session (auth_session)
Severity: Medium — Requires follow-up action
Affected file: src/platform/auth/providers.ts


Background

Calmony Pay supports four OAuth 2.0 providers for authentication: Google, GitHub, Microsoft Entra, and Okta. The OAuth 2.0 specification requires that a state parameter be generated by the application before redirecting the user to the provider, and verified on callback to guard against cross-site request forgery (CSRF) attacks on the authentication flow.

The Finding

State parameter handling — both generation and verification — is delegated entirely to Auth.js v5 (beta). The application itself contains no assertion, test, or explicit documentation confirming that this protection is active and correctly applied across all four providers.

Because Auth.js v5 is currently in beta, this creates a visibility gap: if Auth.js were to introduce a regression in state handling, the application would have no independent safeguard or observable signal to detect it.

What This Means

ConcernDetail
CSRF protection sourceAuth.js v5 beta internals only
App-layer verificationNone
Providers affectedGoogle, GitHub, Microsoft Entra, Okta
Test coverageNo test asserts rejection of missing/mismatched state
Audit loggingNo provider/state logging on sign-in events

Recommended Remediation

1. Add Test Coverage

Write an end-to-end or unit test that confirms the OAuth callback endpoint rejects requests where the state parameter is absent or does not match the originally issued value. Coverage should span at least one provider, with the intent to extend to all four.

// Example: assert that a callback with a tampered state is rejected
it('rejects OAuth callback with mismatched state parameter', async () => {
  const response = await simulateOAuthCallback({
    provider: 'google',
    state: 'tampered-or-missing',
  });
  expect(response.status).toBe(400); // or equivalent rejection
});

2. Document the Auth.js Dependency

Add an inline comment or README note to src/platform/auth/providers.ts (and the relevant Auth.js configuration file) explicitly stating:

  • That CSRF state parameter protection is provided by Auth.js.
  • The exact Auth.js version in use.
  • A reference to the relevant Auth.js source or documentation confirming state enforcement is enabled by default.

This makes the security assumption visible to future maintainers and surfaced during dependency upgrade reviews.

// src/platform/auth/providers.ts
//
// SECURITY NOTE (SEC-19):
// OAuth CSRF protection via the `state` parameter is handled by Auth.js v5 (beta).
// Auth.js generates and verifies the state parameter automatically for all providers.
// Ref: https://authjs.dev/reference/core — confirm state behaviour on every major upgrade.
// If upgrading Auth.js, verify state enforcement has not regressed before deploying.

3. Add Audit Logging via signIn Callback

Implement a custom signIn event callback to log the provider and relevant session context on each authentication. This provides an observable signal if state-related errors occur in production.

events: {
  async signIn({ user, account }) {
    // Audit log: provider and user on every successful sign-in
    console.info('[auth] signIn', {
      provider: account?.provider,
      userId: user?.id,
      timestamp: new Date().toISOString(),
    });
  },
},

Note: This logging step does not replace state verification — it supplements it by making successful authentications observable.

Current Status

No code changes were shipped in v1.0.87. This release documents the finding and recommended remediation steps. Follow-up work to add test coverage, inline documentation, and audit logging should be tracked and addressed before Auth.js v5 exits beta or before any Auth.js dependency upgrade.