Skip to content

No server-side logout: refresh tokens stay valid for 7 days after the user logs out #235

Description

@dzekojohn4

Problem

Logging out does not revoke anything server-side. AuthController (backend/src/auth/auth.controller.ts) imports clearAuthCookies from backend/src/auth/helpers/auth-cookies.ts but never calls it, and there is no POST /api/auth/logout route. The frontend authStore.logout() (frontend/lib/store/authStore.ts) only clears localStorage/Zustand state. Meanwhile RefreshTokenRepositoryOperations.revokeAllRefreshTokens(userId) (backend/src/auth/providers/refreshToken.repository.ts) exists and is never invoked anywhere in the codebase.

The SSO logout path is equally incomplete: POST /api/auth/sso/logout (backend/src/auth/sso/sso.controller.ts) runs the SAML strategy only and neither clears the authAccessToken/authRefreshToken cookies nor revokes refresh tokens.

Consequence: after a user clicks "Log out", their refresh token remains valid for the full 7-day lifetime (JWT_REFRESH_EXPIRATION default). On a shared machine, or after a token leak, the session survives the user's explicit logout; there is also no revocation on password change, so a compromised credential set keeps working after a forced reset.

Why this is architecturally hard

  1. The logout contract must be defined. Should logout revoke only the current refresh-token family (matching the existing rotation/family-revocation machinery in AuthService.refreshToken) or all sessions (revokeAllRefreshTokens)? Each has different UX and security trade-offs (e.g. "log out everywhere" vs "log out this device").
  2. Idempotency and cookie handling. The endpoint must be safe to call repeatedly (a second logout is a success, not a 401), clear both auth cookies with the correct paths (/ and /api/auth/refresh-token per auth-cookies.ts), and work for both the cookie-authenticated SPA and Bearer-token clients.
  3. The frontend must call it. authStore.logout() is currently a synchronous local clear; it must become an async call to the new endpoint with the CSRF header (frontend/lib/apiClient.ts already attaches x-csrf-token for state-changing methods) while still clearing local state even if the network call fails.
  4. Audit and observability hooks already exist but are unused. AuditAction.LOGOUT and AuditAction.REFRESH_FAMILY_REVOKED are defined in backend/src/audit-log/entities/audit-log.entity.ts, and the security-IP log (SecurityIpLogService) expects security actions — logout must emit them.

Proposed design (not prescriptive)

  • Add POST /api/auth/logout (authenticated) that revokes the presented refresh token (or its family), clears both auth cookies, and writes an AuditAction.LOGOUT entry with the caller's IP.
  • Optionally add POST /api/auth/logout-all using revokeAllRefreshTokens, and revoke all tokens on password reset/change.
  • Update authStore.logout() to call the endpoint (best-effort: clear local state regardless) and clear the SSO session cookie on the SSO path.

Acceptance criteria

Service

  • POST /api/api/auth/logout (final path under the /api prefix) revokes the refresh token server-side, clears authAccessToken and authRefreshToken cookies, is idempotent (repeat calls return success), and writes an audit-log entry with the actor's IP.
  • A refresh token presented after logout is rejected by POST /api/auth/refresh-token, and password reset/change revokes all of the account's refresh tokens.
  • POST /api/auth/sso/logout also clears the NovaLabs auth cookies and revokes the SSO user's refresh tokens.

Frontend

  • authStore.logout() calls the logout endpoint (with CSRF header) and clears local state even if the request fails.

Tests

  • Tests cover: refresh-after-logout rejected, logout idempotency, cookie clearing, audit entry written, and logout-all revocation.

Out of scope

SAML SLO redirect flows at the IdP, and token blacklisting for the access token itself (it is short-lived by design).

Getting started

cd backend && npm install && npm run build && npm test
cd ../frontend && npm install && npm run lint

Good first files to read: backend/src/auth/auth.controller.ts, backend/src/auth/helpers/auth-cookies.ts, backend/src/auth/providers/refreshToken.repository.ts (revokeAllRefreshTokens, revokeToken), backend/src/audit-log/entities/audit-log.entity.ts (AuditAction.LOGOUT), and frontend/lib/store/authStore.ts (logout).


Complexity: High — 6 pts

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaignarea:authImported from local backlog 2026-07-16area:securityImported from local backlog 2026-07-16bugSomething isn't workingpriority:highImported from local backlog 2026-07-16

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions