How We Built Revocable Sessions and Refresh-Token Replay Protection for the MyZubster Metaverse

Leader ●1 ●3 ●124
calendar_today • schedule9 min read

Ecco la versione in inglese pronta per CoderLegion.
How We Built Revocable Sessions and Refresh-Token Replay Protection for the MyZubster Metaverse
Authentication in a Metaverse platform is more than a login form.
An authenticated identity may control characters, social relationships, private spaces, Marketplace activity and, eventually, payment-related operations. For MyZubster, relying only on long-lived JSON Web Tokens was no longer a sufficient security model.
Under MYZ-71 — Authentication, Sessions & Account Security, we started replacing stateless browser authentication with persistent, revocable and observable server-side sessions.
This article describes what we implemented, how refresh-token rotation works, how we detect token replay, what we tested and what still needs to be completed before production deployment.
The implementation is currently in a Draft pull request. It has not been merged or declared production-ready.

Why stateless JWT authentication was not enough
JWTs are useful because a server can validate them without loading a database record for every request.
However, a completely stateless model creates important limitations:

  • logout cannot reliably invalidate an existing token;
  • users cannot see which devices are authenticated;
  • administrators cannot revoke one compromised device;
  • a stolen token remains valid until expiration;
  • password changes do not automatically terminate sessions;
  • long-lived tokens increase the potential impact of credential theft;
  • tokens stored in localStorage can be accessed by malicious JavaScript after an XSS compromise.
    We therefore introduced a hybrid architecture:
    Authentication
    ↓
    

    Persistent server-side session

    ↓
    

    Short-lived JWT access token

    ↓
    

    Rotating opaque refresh token

    ↓
    

    Replay detection and session revocation
    JWTs are still used for request authentication, but the server-side session is now the final source of authority.
    Persistent server-side sessions
    We added a MongoDB AuthSession model.
    A separate session is created for each authenticated device. The record contains:

  • a unique session identifier;
  • the user who owns the session;
  • creation and expiration timestamps;
  • the last activity timestamp;
  • revocation state and reason;
  • limited device metadata;
  • a privacy-preserving client-IP digest;
  • the current refresh-token hash;
  • a bounded history of consumed refresh-token hashes;
  • refresh-token rotation metadata.
    MongoDB TTL indexing is used to remove expired session records automatically.
    This enables real session lifecycle management. A valid JWT is rejected whenever its associated session is expired, missing or revoked.
    Session-bound access tokens
    New JWT access tokens include a server-generated sid claim.
    A simplified payload looks like this:
    {
    "sub": "user-id",
    "sid": "session-uuid",
    "jti": "access-token-uuid",
    "iat": 1790540000,
    "exp": 1790540900
    }
    Authentication now involves two checks:
    1. Verify the JWT signature and expiration
    2. Validate the persistent session referenced by sid
      This architecture preserves the advantages of JWTs while restoring the ability to revoke access immediately.
      If the user removes a device from the account-security page, the session becomes invalid even if its most recent JWT has not expired.
      Opaque refresh tokens
      The refresh token is not a JWT and does not contain trusted user data.
      Its conceptual format is:
      myzr.<session-id>.<cryptographically-random-secret>
      The random section is produced using a cryptographically secure random-number generator.
      The complete refresh token is sent to the authenticated browser, but it is never stored directly in MongoDB.
      Instead, the backend calculates:
      HMAC-SHA256(server secret, raw refresh token)
      Only the resulting digest is saved.
      This prevents a database leak from immediately exposing usable refresh credentials.
      Protected authentication cookies
      Refresh tokens are stored in cookies configured with:
      HttpOnly
      SameSite=Lax
      Secure in production
      Path=/api/auth
      HttpOnly prevents frontend JavaScript from reading the credential.
      The restricted path reduces the number of requests carrying the refresh cookie.
      Access tokens can also be received through an HttpOnly cookie. Bearer-token authentication remains temporarily supported to avoid breaking clients during the migration.
      Atomic refresh-token rotation
      A refresh token is single-use.
      When the browser calls:
      POST /api/auth/refresh
      the backend:
    3. reads the refresh cookie;
    4. extracts the session identifier;
    5. calculates the HMAC digest of the submitted token;
    6. generates a new opaque token;
    7. atomically compares the submitted hash with the current stored hash;
    8. replaces the stored hash;
    9. records the consumed hash;
    10. issues a new access token;
    11. sends new access and refresh cookies.
      The database update only succeeds if:
  • the session exists;
  • the session has not expired;
  • the session has not been revoked;
  • the submitted hash is still the current refresh-token hash.
    This is effectively a compare-and-swap operation.
    If two requests try to use the same refresh token simultaneously, only one can rotate it successfully.
    Refresh-token replay detection
    Rotation prevents normal reuse, but the system must also detect when an old token appears again.
    After every successful rotation, the consumed token hash is placed in a bounded history.
    If a token from that history is submitted again, the server treats it as a possible credential compromise.
    Previously consumed token submitted
              ↓
    

    Replay detected

              ↓
    

    Entire server-side session revoked

              ↓
    

    Authentication cookies cleared

              ↓
    

    A new login is required
    The session receives the revocation reason:
    refresh-token-replay
    The history is limited to the latest 50 hashes so the MongoDB document cannot grow indefinitely.
    Replay does not necessarily prove malicious activity. It could also indicate an outdated client or an unusual concurrency condition. Revoking the session is nevertheless the safest response.
    New authentication endpoints
    The current implementation provides the following endpoints.
    Refresh the session
    POST /api/auth/refresh
    This endpoint rotates the refresh token and issues a new access token.
    The raw refresh token is never included in the JSON response.
    Log out
    POST /api/auth/logout
    Logout is idempotent.
    Authentication cookies are cleared even when the access token is missing, malformed, expired or already revoked.
    When possible, the refresh token is used to find and revoke the persistent session.
    Retrieve the authenticated account
    GET /api/auth/me
    Returns the authenticated user and safe session metadata.
    List active devices
    GET /api/auth/me/sessions
    Returns the user’s active sessions and indicates the current device.
    Revoke a device
    DELETE /api/auth/me/sessions/:sessionId
    Allows users to terminate another session remotely.
    Revoking the current session also clears local authentication state and returns the user to the login flow.
    Password and social authentication
    Password registration and login now create persistent sessions and issue both access and refresh cookies.
    Verified social-login flows use the same architecture.
    The raw refresh token is not placed inside:

  • redirect URLs;
  • OAuth query parameters;
  • signed callback tickets;
  • public API responses.
    This reduces the risk of credentials leaking through browser history, analytics systems, proxy logs or referrer headers.
    Account-security interface
    The React frontend now includes:
    /account/security
    The page displays:
  • active sessions;
  • the current device;
  • session creation time;
  • last activity;
  • expiration time;
  • limited device information.
    Users can remotely revoke another device or terminate the current session.
    The page is linked from the authenticated account panel and Neon Plaza.
    The deployment configuration also supports the route as a single-page application path and applies:
    X-Robots-Tag: noindex, nofollow
    Account-security pages should not be indexed by search engines.
    Privacy-preserving session metadata
    The system does not store raw client IP addresses.
    Instead, it saves an HMAC-derived digest. This provides a limited correlation signal for security purposes without retaining the original IP address as normal application data.
    Device metadata is also intentionally limited.
    Session security should help users recognize their devices without becoming an invasive tracking mechanism.
    Legacy migration support
    The existing frontend still contains API clients that expect a token in localStorage or use:
    Authorization: Bearer
    Removing this immediately would break existing routes and clients.
    The migration is therefore divided into stages:
    Persistent sessions and cookie support
              ↓
    

    Rotating refresh tokens

              ↓
    

    Centralized access-token renewal

              ↓
    

    CSRF protection

              ↓
    

    Cookie-only authentication

              ↓
    

    Expiration of legacy browser tokens

              ↓
    

    Strict server-session enforcement

              ↓
    

    Removal of localStorage and bearer migration
    An environment option is available for strict enforcement:
    REQUIRE_SERVER_SESSION=true
    It should only be activated after existing clients and legacy credentials have been migrated.
    Testing the implementation
    The focused authentication implementation currently passes:
    Backend: 30/30 tests
    Frontend: 12/12 tests
    The React production build also completes successfully.
    Real MongoDB integration coverage verifies the complete replay lifecycle:
    Create persistent session

      ↓
    

    Use current refresh token

      ↓
    

    Rotate successfully

      ↓
    

    Submit the old token again

      ↓
    

    Reject the replay

      ↓
    

    Revoke the server-side session

      ↓
    

    Persist refresh-token-replay as the reason
    The following authentication suites also pass in GitHub Actions:
    tests/authSessionService.test.js
    tests/authSessionController.test.js
    tests/socialIdentityService.test.js
    Repository-wide CI status
    The complete repository pipeline is not yet green.
    The latest full backend execution reported:
    141 test suites passed
    42 test suites failed

738 tests passed
44 tests failed
The failing tests mainly concern pre-existing behavior in:

  • Zorgax payments and payment intents;
  • Marketplace flows;
  • seller-membership policies;
  • older Metaverse authentication expectations;
  • Metaverse observability;
  • legacy source-code assertion tests.
    The Security Audit and MYZ-164 policy workflows also remain red because of previously identified repository-wide conditions.
    The new MYZ-71 session and refresh-token suites are green, but the pull request must remain a Draft until the wider baseline and the remaining authentication work are addressed.
    What still needs to be implemented
    Centralized access-token renewal
    The frontend needs a shared API layer that can:
    1. identify an expired access token;
    2. call /api/auth/refresh;
    3. retry the original request;
    4. prevent simultaneous refresh calls;
    5. redirect to login when renewal fails.
      Without a centralized implementation, individual pages may respond differently when the access token expires.
      CSRF protection
      Cookie-based authentication requires explicit protection for state-changing operations.
      The final design should combine appropriate controls such as:
  • origin validation;
  • CSRF tokens;
  • double-submit cookies;
  • carefully selected SameSite policies;
  • rejection of unsafe cross-origin requests.
    SameSite=Lax should not be considered the only defence.
    Removal of browser-stored tokens
    The current frontend still updates the legacy localStorage token as a temporary compatibility bridge.
    The final architecture should not expose reusable authentication credentials to frontend JavaScript.
    Before removing localStorage, we must migrate:
  • every authenticated API client;
  • protected React routes;
  • realtime and websocket authentication;
  • mobile or embedded clients;
  • old sessions and legacy JWTs.
    Passkeys and magic links
    The MYZ-71 roadmap includes passwordless authentication based on flows such as:
    /auth/start
    /auth/verify
    Passkey support will require correct WebAuthn management for:
  • relying-party configuration;
  • allowed origins;
  • challenge creation and expiration;
  • credential counters;
  • credential revocation;
  • account recovery.
    Magic links will require short-lived, single-use verification tokens and replay protection.
    Step-up authentication
    Sensitive operations should require recent stronger verification.
    Examples include:
  • changing the primary email;
  • changing a password;
  • registering or deleting passkeys;
  • deleting the account;
  • accessing wallet or payment functionality;
  • modifying high-impact Marketplace settings.
    A valid session should not automatically authorize every sensitive action indefinitely.
    Password-change session policy
    The application must define what happens after a password change:
  • revoke every session;
  • keep only the current session;
  • allow the user to choose;
  • require step-up authentication.
    This policy must be enforced on the server and tested with real session records.
    Production OAuth validation
    Google, GitHub and Facebook login still need production verification for:
  • exact callback URLs;
  • public frontend and API origins;
  • provider secrets;
  • redirect allowlists;
  • account-linking behavior;
  • missing-email cases;
  • cookie behavior across domains.
    MongoDB Atlas validation
    The production deployment must verify:
  • TTL-index creation;
  • refresh-token indexes;
  • cleanup of expired sessions;
  • document growth;
  • rolling-deployment compatibility;
  • session behavior under concurrent refresh requests.
    Rate limiting
    Authentication endpoints need dedicated abuse protection.
    This includes limits for:
  • password login;
  • refresh requests;
  • password recovery;
  • magic-link generation;
  • WebAuthn challenge creation;
  • social-auth callbacks.
    Security monitoring
    Structured security events should be produced for:
  • login success and failure;
  • session creation;
  • session revocation;
  • remote device removal;
  • refresh-token replay;
  • password changes;
  • passkey registration;
  • suspicious authentication activity.
    Logs must never contain passwords, raw access tokens, raw refresh tokens, OAuth authorization codes or provider secrets.
    Recommended deployment sequence
    The safest rollout order is:
    1. review the Draft pull request;
    2. deploy with legacy compatibility enabled;
    3. verify MongoDB indexes;
    4. validate cookies using the real public domains;
    5. test login, refresh, logout and remote revocation;
    6. execute a controlled refresh-token replay test;
    7. implement centralized frontend renewal;
    8. add and validate CSRF protection;
    9. monitor session and replay events;
    10. allow legacy credentials to expire;
    11. activate strict server-session enforcement;
    12. remove localStorage and legacy bearer authentication;
    13. introduce passkeys, magic links and step-up authentication.
      Current progress
      Persistent server-side sessions DONE
      Session-bound JWT access tokens DONE
      Real logout DONE
      Active-device listing DONE
      Remote session revocation DONE
      Opaque rotating refresh tokens DONE
      Refresh-token replay detection DONE
      Password and social-login integration DONE
      Account-security interface DONE
      Focused backend and frontend testing DONE

Centralized access-token renewal PENDING
CSRF hardening PENDING
Cookie-only migration PENDING
Removal of localStorage authentication PENDING
Passkeys and magic links PENDING
Step-up authentication PENDING
Production OAuth validation PENDING
MongoDB Atlas production validation PENDING
Repository-wide green CI PENDING
Final thoughts
Secure authentication is not just about generating a token.
It requires a complete lifecycle:
Issue
Verify
Renew
Observe
Revoke
Recover
Monitor
MyZubster now has the foundation for persistent multi-device sessions, secure refresh-token rotation and automatic replay response.
The next major objective is completing the browser migration safely through centralized renewal, CSRF protection and the removal of JavaScript-accessible authentication credentials.
Only after those controls are validated should strict session enforcement be activated in production.
The current implementation can be reviewed in the Draft pull request:
https://github.com/danieldirimini-myzubster/myzubster/pull/10

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

Stop Implementing Authentication Inside Containers on Kubernetes

Alexandre Vazquez - Jul 25

Building Verifiable Bounty Payments and Revocable Account Sessions at MyZubster Over the latest MyZu

Myzubster - Sep 28

From Stateless JWTs to Revocable Sessions: Securing the MyZubster Metaverse

Myzubster - Sep 27

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

Securing the MyZubster Metaverse: From Stateless JWTs to Revocable Sessions

Myzubster - Sep 28
chevron_left
3.5k Points • 128 Badges
Rimini
94Posts
9Comments
32Connections

Related Jobs

View all jobs →

Commenters (This Week)

2 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!