feat(auth): add JWT token revocation with Redis blocklist#476
Conversation
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>
|
@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. |
CI — All Checks PassedBackend — PASS
Mobile — SKIP
Web — SKIP
Last updated: |
|
Hey @Harxhit , The JWT-related validation is now passing:
The remaining backend-ci failure occurs during the repository-wide TypeScript typecheck step and originates from files outside the scope of this PR:
These files are not modified by this PR and are not part of the JWT revocation implementation.Please review this PR, thank you. |
|
Please fix the merge conflicts. |
Signed-off-by: J.ANTHARYA <antharyajalligampala@gmail.com>
|
Hi @Harxhit, I've resolved the merge conflicts and updated the branch with the latest changes from 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! |
| }); | ||
| } | ||
|
|
||
| function generateState(): string { |
There was a problem hiding this comment.
Could you please make a util file for this.
| return randomBytes(32).toString('hex'); | ||
| } | ||
|
|
||
| function buildOAuthState(clientState: string, mobileRedirectUri: string): string { |
| return `${clientState}.${generateState()}`; | ||
| } | ||
|
|
||
| function getMobileRedirectUri(state?: string): string | null { |
There was a problem hiding this comment.
Similarly for this too please.
There was a problem hiding this comment.
Should we include userId in the warning log to make Redis token revocation failures easier to trace?
|
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
left a comment
There was a problem hiding this comment.
LGTM. All review comments have been addressed, and I verified that linting, type checking, and unit tests are passing locally.
Approving.
|
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. |
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
What Changed
Added shared JWT utilities in
src/utils/jwt.ts:extractRawJwt()to consistently extract tokens from Authorization headers or cookiesblocklistKey()to generate SHA-256 hashed Redis blocklist keysExtended the global authentication decorator in
app.ts:jwtVerify()@fastify/cookiebefore@fastify/jwtso cookie-based authentication works correctly for browser clientsAdded authenticated
DELETE /auth/logoutendpoint:Simplified legacy
POST /auth/logout:Added comprehensive logout test coverage:
How to Test
Generate a JWT using an authenticated flow and access a protected endpoint.
Revoke the token:
Expected result:
401 UnauthorizedExpected result:
Checklist
Additional Context
Security decisions:
POST /auth/logoutno longer writes unverified tokens to Redis, preventing abuse of the logout endpoint.Repository note:
While validating this feature, I compared the latest
mainbranch against this branch.Existing failures in
event.test.tsandteam.test.tsoccur identically onmainand were not introduced by this authentication change.This PR introduces no additional test regressions.