Skip to content

Secure backend data access with signed auth tokens and RBAC#48

Merged
fuzziecoder merged 1 commit intomainfrom
codex/implement-backend-data-security
Feb 25, 2026
Merged

Secure backend data access with signed auth tokens and RBAC#48
fuzziecoder merged 1 commit intomainfrom
codex/implement-backend-data-security

Conversation

@fuzziecoder
Copy link
Owner

@fuzziecoder fuzziecoder commented Feb 25, 2026

Motivation

  • Replace the predictable demo tokens and open data endpoints with a lightweight, integrity-checked auth mechanism and enforce access control to protect user and billing data.
  • Add configuration for token handling and CORS so deployments can customize secrets and origin policy.

Description

  • Replaced demo-token-* tokens with HMAC-SHA256 signed bearer tokens that include sub, role, and exp claims and added constant-time signature verification and expiry checks in backend/server.js.
  • Enforced authorization checks on routes: GET /api/orders and GET /api/orders/:id restrict non-admins to their own orders, POST /api/orders blocks non-admin creation for other users, and GET /api/bills/:spotId and DELETE /api/users/:userId are admin-only; updated CORS to allow Authorization header and added env-driven origin via CORS_ALLOW_ORIGIN.
  • Added token generation helpers generateAuthToken/verifyAuthToken and new env-configurable settings AUTH_TOKEN_SECRET and AUTH_TOKEN_TTL_SECONDS in backend/server.js.
  • Converted backend/env.js to ESM import style and expanded the zod env schema to include the new security and rate-limit variables, and updated backend/README.md to document the new auth behavior and env vars.
  • Modified files: backend/server.js, backend/env.js, backend/README.md.

Testing

  • Ran node --check backend/server.js which succeeded.
  • Ran node --check backend/env.js which succeeded.
  • Attempted a live backend smoke test by starting the server and exercising the login and protected endpoints, but the runtime start/test failed in this environment due to missing package dependency (dotenv not installed), so the end-to-end smoke test did not complete.

Codex Task


Open with Devin

Summary by CodeRabbit

  • New Features

    • Implemented signed bearer token authentication with user ID, role, and expiry information
    • Added role-based authorization: admins can access all orders and user data; regular users limited to own orders
    • Configured login rate limiting capabilities
  • Configuration

    • Added environment variables for token secret, TTL, CORS origin, and rate limiting settings

@vercel
Copy link

vercel bot commented Feb 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
brocode-spot-update-app Ready Ready Preview, Comment Feb 25, 2026 1:43pm

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c92939 and a0c6e61.

📒 Files selected for processing (3)
  • backend/README.md
  • backend/env.js
  • backend/server.js

📝 Walkthrough

Walkthrough

This PR implements Issue #30, introducing HMAC-signed bearer token authentication to secure backend data access. Changes include adding token generation and verification logic to the login endpoint, enforcing authorization checks on multiple API routes, adding auth-related environment variables, and migrating the codebase to ES Modules.

Changes

Cohort / File(s) Summary
Documentation & Configuration
backend/README.md, backend/env.js
Documentation of new signed auth token mechanism and authorization enforcement; migration to ES Modules with expanded environment schema including AUTH_TOKEN_SECRET, AUTH_TOKEN_TTL_SECONDS, CORS_ALLOW_ORIGIN, and LOGIN_RATE_LIMIT_* variables.
Authentication Implementation
backend/server.js
Introduction of HMAC-signed token generation, signing, and verification utilities; per-request auth checks on GET/POST /api/orders and GET /api/orders/:id with role-based access control; admin-only enforcement on GET /api/bills/:spotId and DELETE /api/users/:userId; updated login flow to return signed tokens; CORS configuration via environment variables.

Sequence Diagram

sequenceDiagram
    participant User as User/Client
    participant Server as Server
    participant Auth as Auth System
    
    User->>Server: POST /api/login (credentials)
    Server->>Auth: generateAuthToken(user)
    Auth->>Auth: signToken(payload, secret)
    Auth->>Auth: toBase64Url(signature)
    Auth-->>Server: signed token
    Server-->>User: { token: "signed.bearer.token" }
    
    User->>Server: GET /api/orders (Authorization: Bearer token)
    Server->>Auth: verifyAuthToken(token, secret)
    Auth->>Auth: validate signature in constant time
    Auth-->>Server: { userId, role, isValid }
    Server->>Server: enforce role-based access (user vs admin)
    Server-->>User: filtered orders
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #31: Modifies authentication and login flow in backend/server.js, implementing alternative credential storage mechanism alongside this PR's signed token approach.
  • #47: Adds bearer-token parsing and authentication/authorization enforcement on GET /api/orders/:id, directly overlapping with this PR's token verification logic.
  • #37: Modifies login flow and environment configuration (LOGIN_RATE_LIMIT_* variables), complementing this PR's auth-related environment variable additions.

Suggested labels

apertre3.0, hard

Poem

🐰 Hop, skip, and a cryptographic jump!
With HMAC-signed tokens, no more auth slump.
Bearer tokens travel, secured with care,
Role-based gates guard what's fair to share.
A rabbit's delight—permissions in flight! 🔐

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/implement-backend-data-security

Comment @coderabbitai help to get the list of available commands and usage tips.

@fuzziecoder fuzziecoder merged commit 72c2c81 into main Feb 25, 2026
4 of 5 checks passed
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines 339 to +345
}

if (method === 'GET' && path.startsWith('/api/bills/')) {
const authedUser = getUserFromAuthHeader(req.headers.authorization);
if (!authedUser || authedUser.role !== 'admin') {
sendJson(res, 403, { error: 'Forbidden' });
return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Admin-only endpoints return 403 instead of 401 for unauthenticated requests

The GET /api/bills/:spotId and DELETE /api/users/:userId endpoints return HTTP 403 (Forbidden) when the request has no token or an invalid/expired token, instead of HTTP 401 (Unauthorized). Per HTTP semantics, 401 means "you are not authenticated" while 403 means "you are authenticated but lack permission." Returning 403 for unauthenticated requests conflates these two conditions.

Root Cause and Impact

At backend/server.js:339-345 and backend/server.js:350-354, the check if (!authedUser || authedUser.role !== 'admin') collapses both the unauthenticated case (!authedUser) and the unauthorized case (role !== 'admin') into a single 403 response.

Compare with the other protected endpoints like GET /api/orders at backend/server.js:252-256 and POST /api/orders at backend/server.js:300-303, which correctly return 401 first for unauthenticated users, and only check authorization afterward.

For example, a request with no Authorization header to GET /api/bills/spot-1 returns {"error": "Forbidden"} with status 403, when it should return {"error": "Unauthorized"} with status 401. This makes it harder for API clients to distinguish between "need to log in" vs "logged in but insufficient privileges," and breaks the pattern established by the other endpoints in this same PR.

Suggested change
}
if (method === 'GET' && path.startsWith('/api/bills/')) {
const authedUser = getUserFromAuthHeader(req.headers.authorization);
if (!authedUser || authedUser.role !== 'admin') {
sendJson(res, 403, { error: 'Forbidden' });
return;
const authedUser = getUserFromAuthHeader(req.headers.authorization);
if (!authedUser) {
sendJson(res, 401, { error: 'Unauthorized' });
return;
}
if (authedUser.role !== 'admin') {
sendJson(res, 403, { error: 'Forbidden' });
return;
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant