Securing the MyZubster Metaverse: From Stateless JWTs to Revocable Sessions

Leader ●1 ●3 ●123
calendar_today • schedule8 min read

Securing the MyZubster Metaverse: From Stateless JWTs to Revocable Sessions
We have completed another major security milestone for the MyZubster Metaverse.
The original authentication system depended mainly on browser-stored JWT access tokens. That solution was simple, but it could not provide the session lifecycle controls required by a platform with persistent identities, virtual characters, private rooms, realtime interactions and future Marketplace or payment operations.
Under MYZ-71 — Authentication, Sessions & Account Security, we have now implemented:

  • persistent server-side sessions;
  • session-bound JWT access tokens;
  • real logout;
  • active-device management;
  • remote session revocation;
  • rotating opaque refresh tokens;
  • refresh-token replay detection;
  • automatic access-token renewal for the Metaverse;
  • concurrent refresh deduplication;
  • origin-based CSRF protection;
  • structured authentication errors.
    The implementation is currently available in a Draft pull request:
    https://github.com/danieldirimini-myzubster/myzubster/pull/10
    It has not been merged or declared production-ready yet.
    Why stateless JWTs were no longer enough
    A server can validate a JWT without loading a database session, but a completely stateless design has significant limitations.
    A token may remain valid even after:
  • the user logs out;
  • the device is no longer trusted;
  • the password changes;
  • the account owner wants to disconnect another device;
  • a security event requires immediate revocation.
    Long-lived tokens also increase the impact of credential theft.
    If they are stored in localStorage, they are accessible to frontend JavaScript. This makes an XSS vulnerability significantly more dangerous.
    We moved toward a hybrid architecture:
    User authentication
      ↓
    

    Persistent server-side session

      ↓
    

    Short-lived JWT access token

      ↓
    

    Rotating opaque refresh token

      ↓
    

    Automatic renewal

      ↓
    

    Replay detection and revocation
    The JWT is still useful, but the persistent session is now the final source of authority.
    Persistent server-side sessions
    Each authenticated device receives its own MongoDB AuthSession.
    A session records:

  • the session identifier;
  • the owning user;
  • creation and expiration timestamps;
  • last activity;
  • limited device metadata;
  • a privacy-preserving network digest;
  • revocation time and reason;
  • the current refresh-token hash;
  • previously consumed refresh-token hashes;
  • refresh-token rotation metadata.
    MongoDB TTL indexing is used to remove expired sessions automatically.
    A valid JWT is rejected when its corresponding session is missing, expired or revoked.
    Binding access tokens to sessions
    New access tokens include a server-generated sid claim.
    A simplified token looks like this:
    {
    "sub": "user-id",
    "sid": "session-uuid",
    "jti": "access-token-uuid",
    "iat": 1790540000,
    "exp": 1790540900
    }
    Request authentication now consists of two steps:
    Verify the JWT
    ↓
    

    Validate the server-side session
    This provides both efficient request authentication and immediate revocation.
    If a device is removed from the account-security interface, its JWT stops working even if it has not reached its expiration time.
    Real logout and active-device management
    The React frontend now includes:
    /account/security
    Users can inspect:

  • active sessions;
  • the current device;
  • creation time;
  • last activity;
  • expiration time;
  • limited device information.
    They can disconnect another device or terminate their current session.
    The supporting endpoints are:
    POST /api/auth/logout
    GET /api/auth/me
    GET /api/auth/me/sessions
    DELETE /api/auth/me/sessions/:sessionId
    Logout is idempotent.
    Even when the access token is expired or malformed, the backend clears the authentication cookies. When possible, the refresh token is used to identify and revoke the persistent session.
    Opaque refresh tokens
    The refresh token is deliberately not a JWT.
    Its conceptual format is:
    myzr.<session-id>.<cryptographically-random-secret>
    The raw token is never stored in MongoDB.
    The backend calculates:
    HMAC-SHA256(server secret, raw refresh token)
    Only the resulting digest is persisted.
    If the session database is exposed, an attacker cannot directly use the stored digest to renew a session.
    Atomic refresh-token rotation
    Refresh tokens are single-use.
    When the browser calls:
    POST /api/auth/refresh
    the server:
    1. reads the HttpOnly refresh cookie;
    2. extracts the session identifier;
    3. hashes the submitted token;
    4. creates a new random refresh token;
    5. atomically compares the submitted hash with the current stored hash;
    6. replaces the current hash;
    7. stores the consumed hash in a bounded history;
    8. issues a new access token;
    9. sends new authentication cookies.
      Only one request can successfully consume the current token.
      This prevents ordinary concurrent requests from rotating the same credential more than once.
      Refresh-token replay detection
      The server maintains a bounded history of consumed refresh-token hashes.
      If a previously consumed token appears again, the session is treated as potentially compromised:
      Old refresh token submitted
      ↓
      Replay detected
      ↓
      Entire session revoked
      ↓
      Cookies cleared
      ↓
      New login required
      The revocation reason is recorded as:
      refresh-token-replay
      The history is limited so the MongoDB session document cannot grow indefinitely.
      Automatic renewal in the Metaverse
      We introduced a reusable frontend client:
      authenticatedFetch(input, options)
      The client handles the access-token lifecycle:
      Protected request
      ↓
      401 access-token response
      ↓
      POST /api/auth/refresh
      ↓
      Receive rotated access token
      ↓
      Retry original request once
      The complete Metaverse API module now uses this client.
      It covers operations involving:
  • profiles;
  • rooms;
  • invitations;
  • sessions;
  • participants;
  • stage access;
  • messages;
  • moderation;
  • reports;
  • landmarks;
  • realtime world actions.
    The client retries only recognized access-token errors and never enters an infinite retry loop.
    Deduplicating concurrent refresh operations
    A Metaverse page can perform several API requests simultaneously.
    When an access token expires, multiple requests may receive a 401 at approximately the same time.
    Without coordination, every request could call /api/auth/refresh using the same single-use refresh token. This could produce an accidental replay event.
    The client therefore shares one in-flight refresh operation:
    Request A → 401 ┐
    Request B → 401 ├─→ One refresh
    Request C → 401 ┘
                   ↓
             New access token
                   ↓
            Retry A, B and C
    

    Tests confirm that two simultaneous protected requests produce exactly one refresh operation and two successful retries.
    Handling terminal authentication failures
    The client does not silently renew an explicitly revoked session.
    For example:
    AUTH_SESSION_REVOKED
    is returned directly to the application.
    If the refresh token is no longer valid, the client:

  • clears the temporary browser credentials;
  • emits:
    myzubster:auth-expired
    The UI can use this event to return the user to authentication without every page implementing its own renewal logic.
    CSRF protection
    Cookie authentication introduces a different security risk: browsers attach cookies automatically.
    State-changing requests must therefore be protected against Cross-Site Request Forgery.
    We added an origin-based CSRF guard for unsafe cookie-authenticated methods:
    POST
    PUT
    PATCH
    DELETE
    The guard evaluates:
  • Origin;
  • Referer;
  • Sec-Fetch-Site;
  • configured application origins;
  • the actual request target.
    A browser request marked as cross-site is rejected before the backend performs a session lookup:
    Sec-Fetch-Site: cross-site
    The API returns a structured response:
    {
    "success": false,
    "request_id": "request-uuid",
    "error": {
    "code": "AUTH_CSRF_REJECTED",
    "message": "Origine della richiesta non autorizzata"
    }
    }
    Which routes are protected?
    The trusted-origin policy is applied to:
  • password registration;
  • password login;
  • OAuth ticket verification;
  • social-login ticket exchange;
  • refresh-token rotation;
  • logout;
  • Gmail verification exchange;
  • protected mutations authenticated through cookies.
    Bearer-token clients remain compatible during the migration because they do not depend on automatically attached cookies.
    CLI and server-to-server clients that do not send browser origin metadata also remain supported.
    Configuring trusted origins
    Production and preview origins can be configured with:
    AUTH_TRUSTED_ORIGINS=https://www.myzubster.com,https://preview.myzubster.com
    The middleware also recognizes:
    FRONTEND_URL
    PUBLIC_APP_URL
    GATEWAY_PUBLIC_URL
    Local development accepts:
    http://localhost:3000
    http://127.0.0.1:3000
    These values still need to be verified in the real deployment before the platform switches to cookie-only authentication.
    Cookie protections
    The authentication cookies use:
    HttpOnly
    SameSite=Lax
    Secure in production
    The refresh cookie is restricted to:
    Path=/api/auth
    This prevents it from being attached to unrelated application requests.
    SameSite=Lax provides another security layer, but it is not treated as the only CSRF defence.
    Privacy considerations
    Raw client IP addresses are not stored.
    The server stores an HMAC-derived digest instead. This allows limited correlation for security analysis without retaining the original address as ordinary application data.
    Device metadata is also intentionally limited.
    A session-security feature should help users recognize their devices without becoming an invasive tracking mechanism.
    Validation results
    The focused backend security scope passes:
    30/30 tests
    The focused frontend and Metaverse scope passes:
    19/19 tests
    The test coverage includes:
  • persistent session creation;
  • session-bound JWT validation;
  • real logout;
  • remote revocation;
  • refresh-token hashing;
  • atomic token rotation;
  • refresh replay detection;
  • replay-driven session revocation;
  • automatic access-token renewal;
  • exactly-once retry;
  • concurrent refresh deduplication;
  • structured authentication failures;
  • same-origin CSRF acceptance;
  • configured frontend-origin acceptance;
  • cross-site CSRF rejection;
  • rejection before database lookup;
  • bearer-token compatibility;
  • non-browser compatibility.
    The React production build also completes successfully.
    GitHub Actions status
    The affected backend suites are green:
    tests/authSessionService.test.js
    tests/authSessionController.test.js
    tests/authMiddlewareSessions.test.js
    tests/socialIdentityService.test.js
    tests/csrfProtection.test.js
    The complete repository currently reports:
    142 test suites passed
    42 test suites failed

746 tests passed
44 tests failed
The remaining failures belong to existing repository-wide problems involving Zorgax, Marketplace, seller policies and legacy Metaverse expectations.
The new authentication implementation is green in its affected scope, but the pull request remains a Draft because the repository as a whole is not yet CI-clean.
What is completed?
Persistent server-side sessions DONE
Session-bound access tokens DONE
Real logout DONE
Active-device management DONE
Remote session revocation DONE
Rotating opaque refresh tokens DONE
Refresh-token replay detection DONE
Password and social authentication DONE
Account-security interface DONE
Metaverse automatic renewal DONE
Concurrent refresh deduplication DONE
Origin-based CSRF protection DONE
Structured authentication errors DONE
Focused automated testing DONE
What is still missing?
Renewal outside the Metaverse
Some Marketplace, Zorgax, profile and administrative pages still use direct fetch calls and manually constructed bearer headers.
They need to be migrated to authenticatedFetch.
Removal of localStorage authentication
Browser storage remains as a temporary migration bridge.
The final architecture should avoid exposing reusable credentials to frontend JavaScript.
Before removing it, we must migrate:

  • all authenticated API clients;
  • protected React pages;
  • realtime connections;
  • embedded or mobile clients;
  • legacy user sessions.
    Production trusted-origin validation
    The public deployment must verify:
  • exact frontend origins;
  • preview domains;
  • reverse-proxy host and protocol forwarding;
  • cookie paths;
  • cross-site rejection;
  • same-origin refresh;
  • session revocation;
  • deliberate replay detection.
    Strict session enforcement
    After legacy tokens have expired, we can enable:
    REQUIRE_SERVER_SESSION=true
    JWTs without a valid persistent session will then be rejected.
    Passkeys and magic links
    MYZ-71 still includes passwordless authentication.
    Passkeys require complete WebAuthn support for:
  • challenge generation;
  • relying-party configuration;
  • allowed origins;
  • credential counters;
  • credential revocation;
  • recovery.
    Magic links require short-lived, single-use verification tokens.
    Step-up authentication
    Sensitive operations should require recent stronger authentication.
    Possible examples include:
  • changing an email address;
  • changing a password;
  • registering or deleting passkeys;
  • deleting an account;
  • wallet operations;
  • payment approval;
  • sensitive Marketplace changes.
    Production OAuth and database validation
    Google, GitHub and Facebook authentication must still be tested using the production callback URLs and secrets.
    MongoDB Atlas must be checked for:
  • TTL-index creation;
  • refresh-token indexes;
  • document growth;
  • expired-session cleanup;
  • concurrent rotation;
  • rolling-deployment compatibility.
    Recommended next steps
    1. Review the Draft pull request
    2. Configure production trusted origins
    3. Deploy with migration compatibility enabled
    4. Verify cookies and database indexes
    5. Test automatic Metaverse renewal
    6. Test concurrent refresh requests
    7. Test cross-site rejection
    8. Test deliberate refresh-token replay
    9. Migrate the remaining frontend API clients
    10. Allow legacy tokens to expire
    11. Remove tokens from localStorage
    12. Enable strict session enforcement
    13. Remove bearer migration support
    14. Add passkeys, magic links and step-up authentication
      Final thoughts
      Authentication security is not just about issuing a JWT.
      A complete system must manage the entire lifecycle:
      Authenticate
      Create
      Validate
      Renew
      Observe
      Revoke
      Recover
      Monitor
      MyZubster now has the foundations for secure multi-device sessions, refresh-token rotation, replay detection, automatic Metaverse renewal and browser-origin protection.
      The next challenge is deployment and migration: validate the public environment, move the remaining clients to the shared authentication layer and eliminate browser-accessible credentials.
      Draft pull request:
      https://github.com/danieldirimini-myzubster/myzubster/pull/10

MyZubster #Metaverse #CyberSecurity #Authentication #NodeJS #ReactJS #MongoDB #JWT #CSRF #WebSecurity #SoftwareArchitecture #OpenSource

3 Comments

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

More Posts

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

Dharanidharan - Feb 9

The Sovereign Vault — A Comprehensive Guide to Protocol-Driven AI

Ken W. Algerverified - Jun 4

From Stateless JWTs to Revocable Sessions: Securing the MyZubster Metaverse

Myzubster - Sep 27

5 Web Dev Pitfalls That Are Silently Killing Your Projects (With Real Fixes)

Dharanidharan - Mar 3

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

Myzubster - Sep 27
chevron_left
3.5k Points • 127 Badges
Rimini
90Posts
8Comments
32Connections

Related Jobs

View all jobs →

Commenters (This Week)

1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!