Skip to content

feat(auth): add JWT token revocation with Redis blocklist#476

Merged
Harxhit merged 4 commits into
Dev-Card:mainfrom
antharya05:feat/jwt-token-revocation-clean
Jun 9, 2026
Merged

feat(auth): add JWT token revocation with Redis blocklist#476
Harxhit merged 4 commits into
Dev-Card:mainfrom
antharya05:feat/jwt-token-revocation-clean

Conversation

@antharya05

@antharya05 antharya05 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements secure JWT session invalidation using a Redis-backed blocklist.

Previously, JWTs remained valid until expiration even after logout, meaning a stolen token could continue accessing protected APIs after a user logged out. This change introduces server-side token revocation so tokens are immediately invalidated after logout while maintaining backward compatibility with existing logout flows.

Closes #306


Type of Change

  • Bug fix
  • New feature
  • Refactor (no functional change)
  • UI / Design change
  • Tests only
  • Documentation
  • Infrastructure / DevOps
  • Security

What Changed

  • Added shared JWT utilities in src/utils/jwt.ts:

    • extractRawJwt() to consistently extract tokens from Authorization headers or cookies
    • blocklistKey() to generate SHA-256 hashed Redis blocklist keys
  • Extended the global authentication decorator in app.ts:

    • Checks Redis blocklist before jwtVerify()
    • Rejects revoked tokens immediately
    • Registers @fastify/cookie before @fastify/jwt so cookie-based authentication works correctly for browser clients
  • Added authenticated DELETE /auth/logout endpoint:

    • Requires a valid JWT
    • Stores a hash of the token signature in Redis
    • Uses TTL equal to the token's remaining lifetime
    • Automatically self-cleans when the token expires
  • Simplified legacy POST /auth/logout:

    • Preserved for backward compatibility
    • Clears cookies only
    • No longer writes unverified tokens to Redis
  • Added comprehensive logout test coverage:

    • Revocation flow
    • Cookie authentication
    • Redis failures
    • Edge cases
    • End-to-end revocation behavior

How to Test

  1. Start the backend and Redis.
npm install
npm run dev
  1. Generate a JWT using an authenticated flow and access a protected endpoint.

  2. Revoke the token:

DELETE /auth/logout
Authorization: Bearer <token>
  1. Retry the same protected request using the revoked token.

Expected result:

401 Unauthorized
  1. Run the logout test suite:
npm test -- logout

Expected result:

  • All logout/revocation tests pass.

Checklist

  • My code follows the project's coding style.
  • TypeScript compiles without errors.
  • I have added or updated tests for the changes I made.
  • All relevant tests pass locally.
  • I have updated documentation where necessary.
  • No new console.log or debug statements left in the code.
  • Breaking changes are documented in this PR description.

Additional Context

Security decisions:

  • Redis blocklist entries store a SHA-256 hash of the JWT signature rather than the raw token.
  • Blocklist entries expire automatically using the JWT's remaining lifetime.
  • Redis failures intentionally fail open to avoid taking down authenticated requests during Redis outages; token expiration remains the fallback protection mechanism.
  • POST /auth/logout no longer writes unverified tokens to Redis, preventing abuse of the logout endpoint.

Repository note:

While validating this feature, I compared the latest main branch against this branch.

Existing failures in event.test.ts and team.test.ts occur identically on main and were not introduced by this authentication change.

This PR introduces no additional test regressions.

Adds secure logout that revokes the current JWT by storing a hash of its
signature in Redis with a TTL equal to the token's remaining lifetime.
The entry self-cleans when the JWT naturally expires, keeping Redis lean.

Changes:
- utils/jwt.ts: extractRawJwt() and blocklistKey(SHA-256(sig)) utilities
- app.ts:       authenticate decorator checks Redis blocklist before jwtVerify;
                registers @fastify/cookie before @fastify/jwt so cookie-based
                auth works for web browser clients (was silently broken before)
- routes/auth.ts: DELETE /auth/logout endpoint (requires valid JWT);
                  POST /auth/logout simplified to cookie-clear only (backward compat)
- logout.test.ts: 36 tests covering revocation flow, cookie auth, Redis failures,
                  edge cases, and end-to-end invariants
- app.test.ts:  set JWT_SECRET/ENCRYPTION_KEY fallbacks so CI can call buildApp()
- package.json: add typecheck script consumed by CI workflow
- ciScript.js:  fix path generation — test files in __tests__/ were being
                double-suffixed (logout.test.ts -> logout.test.test.ts)

Security decisions documented inline:
- Fail-open on Redis outage (acceptable for a portfolio app; JWT expiry is backup)
- SHA-256 hash of signature as blocklist key (claims never stored in Redis)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 4, 2026

Copy link
Copy Markdown

@antharya05 is attempting to deploy a commit to the Prashantkumar Khatri's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

CI — All Checks Passed

Backend — PASS

Check Result
Lint PASS
Test PASS
Typecheck PASS

Mobile — SKIP

Check Result
Lint -
Test -

Web — SKIP

Check Result
Check -
Build -

Last updated: Mon, 08 Jun 2026 16:17:09 GMT

@antharya05

Copy link
Copy Markdown
Contributor Author

Hey @Harxhit , The JWT-related validation is now passing:

  • logout.test.ts: 36/36 passing
  • app.test.ts: passing
  • Backend lint warnings resolved

The remaining backend-ci failure occurs during the repository-wide TypeScript typecheck step and originates from files outside the scope of this PR:

  • src/routes/team.ts
  • src/tests/team.test.ts
  • src/services/publicService.ts
  • src/utils/error.util.ts

These files are not modified by this PR and are not part of the JWT revocation implementation.Please review this PR, thank you.

@Harxhit Harxhit added the gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking. label Jun 6, 2026
@ShantKhatri ShantKhatri requested a review from Harxhit June 6, 2026 17:42
@Harxhit

Harxhit commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

Please fix the merge conflicts.

Signed-off-by: J.ANTHARYA <antharyajalligampala@gmail.com>
@antharya05

Copy link
Copy Markdown
Contributor Author

Hi @Harxhit,

I've resolved the merge conflicts and updated the branch with the latest changes from main.

I've verified that the JWT token revocation functionality remains intact after the conflict resolution, and the CI checks are now passing successfully.

The PR should be ready for review. Please let me know if any further changes are needed.

Thank you!

@Harxhit Harxhit added the backend label Jun 8, 2026
Comment thread apps/backend/src/routes/auth.ts Outdated
});
}

function generateState(): string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you please make a util file for this.

Comment thread apps/backend/src/routes/auth.ts Outdated
return randomBytes(32).toString('hex');
}

function buildOAuthState(clientState: string, mobileRedirectUri: string): string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similarly for this to.

Comment thread apps/backend/src/routes/auth.ts
Comment thread apps/backend/src/routes/auth.ts Outdated
return `${clientState}.${generateState()}`;
}

function getMobileRedirectUri(state?: string): string | null {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similarly for this too please.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we include userId in the warning log to make Redis token revocation failures easier to trace?

@antharya05

Copy link
Copy Markdown
Contributor Author

Hi @Harxhit,

I've addressed the review comments, resolved the merge conflicts, and pushed the updates.

The OAuth helper functions have been moved to a utility file, logging has been added, and the changes have been verified locally. All checks are passing on my end.

Please let me know if any further changes are needed. Thanks!

@Harxhit Harxhit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. All review comments have been addressed, and I verified that linting, type checking, and unit tests are passing locally.

Approving.

@Harxhit Harxhit merged commit d743430 into Dev-Card:main Jun 9, 2026
5 of 6 checks passed
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Congratulations @antharya05 on getting PR #476 merged!

Thank you for your contribution to the project.

To receive the appropriate GSSoC labels and recognition, please mention @Harxhit in the #get-labels channel on our Discord server and share your merged PR link.

@Harxhit Harxhit added level:critical High-impact or difficult contribution affecting core functionality. (+80 pts) quality:exceptional Outstanding contribution with excellent implementation quality. (×1.5 multiplier) type:performance Performance optimization (+15 pts) type:security Security-related fixes/improvements (+20 pts) type:devops CI/CD, infra, deployment, workflow work (+15 pts) type:feature New feature implementation (+10 pts) type:testing Adds/improves tests (+10 pts) labels Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking. level:critical High-impact or difficult contribution affecting core functionality. (+80 pts) quality:exceptional Outstanding contribution with excellent implementation quality. (×1.5 multiplier) type:devops CI/CD, infra, deployment, workflow work (+15 pts) type:feature New feature implementation (+10 pts) type:performance Performance optimization (+15 pts) type:security Security-related fixes/improvements (+20 pts) type:testing Adds/improves tests (+10 pts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Issue Template: Insecure Session Invalidation via Redis JWT Blocklist

2 participants