Skip to content

[Security] OAuth CSRF: state nonce is generated but never stored or verified — connect flow is vulnerable to CSRF #248

@MehtabSandhu11

Description

@MehtabSandhu11

Summary

The /api/connect/github OAuth flow generates a nonce in the state parameter but never persists it anywhere. The callback at /api/connect/github/callback only checks that the state is base64-decodable JSON with a userId and nonce field — it never verifies that the nonce was actually issued by this server. An attacker can craft a forged state containing an arbitrary userId, complete the GitHub OAuth flow, and attach a GitHub token to any user account.

Root Cause

apps/backend/src/routes/connect.ts line 40:

// In a real app, store this in Redis to cross-check in callback
const state = JSON.stringify({
  userId: (request.user as any).id,
  nonce: generateState(),      // generated but never stored
});

Callback (line 63–68):

const decodedState = parseOAuthState(state);
if (!decodedState) { ... }
const userId = decodedState.userId;   // userId comes entirely from attacker-controlled input

There is no server-side validation that the nonce was actually issued for this user session.

Proposed Fix

  1. On /api/connect/github (the initiation endpoint), store the nonce in Redis keyed by nonce value with a 10-minute TTL:
   await app.redis.set(`oauth:nonce:${nonce}`, userId, 'EX', 600);
  1. On the callback, look up the nonce in Redis; reject if missing or if the stored userId doesn't match the decoded one:
   const storedUserId = await app.redis.get(`oauth:nonce:${decodedState.nonce}`);
   if (!storedUserId || storedUserId !== decodedState.userId) {
     return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?error=invalid_state`);
   }
   await app.redis.del(`oauth:nonce:${decodedState.nonce}`);

Acceptance Criteria

  • Nonce is stored in Redis on initiation and consumed (deleted) on callback
  • Callback rejects requests with unrecognised or mismatched nonces
  • A test asserts that a forged state (valid JSON, unknown nonce) is rejected with an error redirect
  • No change to happy-path UX

Please assign this issue to me under GSSoC

Metadata

Metadata

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions