Skip to content

OKTA-1093549 - CSRF on todo0 and agent0 applications in Secure AI Agents Example#14

Merged
iamspathan merged 1 commit intomainfrom
OKTA-1093549
Jan 29, 2026
Merged

OKTA-1093549 - CSRF on todo0 and agent0 applications in Secure AI Agents Example#14
iamspathan merged 1 commit intomainfrom
OKTA-1093549

Conversation

@iamspathan
Copy link
Copy Markdown
Collaborator

Approach

  1. Session-Based CSRF Tokens: Generate a cryptographically secure token using Node's built-in crypto.randomUUID() and store it in the user's session.

  2. Token Validation Middleware: Validate the token on all state-changing requests (POST, PUT, DELETE, PATCH). Reject requests with missing or invalid tokens with 403 Forbidden.

  3. Token Delivery:

    • Include token in a <meta> tag for JavaScript access
    • Include as hidden _csrf field in all forms

Implementation (minimal, no external dependencies):

import { randomUUID } from 'crypto';

// Generate token and make available to views
app.use((req, res, next) => {
  if (!req.session.csrfToken) {
    req.session.csrfToken = randomUUID();
  }
  res.locals.csrfToken = req.session.csrfToken;
  next();
});

// Validate token on state-changing requests
app.use((req, res, next) => {
  if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
    const token = req.body?._csrf || req.headers['x-csrf-token'];
    if (token !== req.session.csrfToken) {
      return res.status(403).send('Invalid CSRF token');
    }
  }
  next();
});

Files Changed

File Change
packages/todo0/src/app-server.ts Added CSRF token generation and validation middleware using crypto.randomUUID()
packages/todo0/views/index.ejs Added <meta name="csrf-token"> tag; added hidden _csrf fields to all forms

Verification

Automated tests confirm:

  • ✅ POST without CSRF token → 403 Forbidden
  • ✅ POST with wrong CSRF token → 403 Forbidden
  • ✅ POST with valid CSRF token → Passes CSRF check
  • ✅ Cross-session attack (stolen token) → 403 Forbidden (token tied to session)

Design Decisions

  1. No external library: Used Node's built-in crypto.randomUUID() instead of adding dependencies like csurf (deprecated) or csrf-csrf. This keeps the sample app simple.

  2. Preserved sameSite: 'lax': Did not change to strict because it would break OAuth callback flow where Okta redirects back to the app.

  3. agent0 not modified: The /api/chat endpoint uses fetch() with credentials: 'include', not form submission. Cross-origin fetch requests don't include cookies with sameSite: 'lax', so CSRF protection is already in place for API calls.


@iamspathan iamspathan merged commit 322d775 into main Jan 29, 2026
2 checks passed
@iamspathan iamspathan deleted the OKTA-1093549 branch January 29, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant