Skip to main content
Understand the differences between session tokens and JWTs to choose the right approach for your application. MoonKey provides two types of session credentials after successful authentication: session tokens and session JWTs. Both represent the same authenticated session but serve different use cases and have distinct characteristics. When a user successfully authenticates through any method (email OTP, OAuth, wallet signature, etc.), MoonKey returns both credentials. The MoonKey SDK automatically stores these in your browser’s IndexedDB, and you can choose which one to use based on your application’s needs.

Overview

Each session contains:
  • User information and identifiers
  • Authentication factors used (email, OAuth provider, wallet, etc.)
  • Device and browser information
  • Session metadata (creation time, expiration, etc.)
Session Token: A standard opaque token that requires API verification on every use. Session JWT: A cryptographically signed JSON Web Token that can be verified independently.

Session Token

A session token is an opaque, unique string that doesn’t contain any user or session information. It must be verified through the MoonKey API to retrieve session details.

Format

session_vy9YGpubKjVn98cw1nT25Msj7jaIpHBinUfD45KLdAOgn9NqEuE4qGHOEchEG5Ue

Characteristics

  • Opaque: The token itself contains no user information
  • Requires verification: Must call /sessions/verify API on every request
  • Easily revocable: Can be instantly invalidated via /sessions/delete API
  • No expiration in token: Expiration is managed server-side

When to use session tokens

Session tokens are ideal for applications that prioritize security and real-time session control:

High-security applications

Since session tokens can be instantly revoked, there’s no risk of using a token after the session has been invalidated. With JWTs, even if the underlying session is revoked, the JWT might still be considered valid until its 5-minute expiration.

Privacy-focused applications

Session tokens don’t expose any user information if inspected. Unlike JWTs (which can be decoded to reveal user_id, session_id, and other claims), session tokens remain completely opaque.

Real-time session management

Applications that need immediate session revocation (e.g., admin panels, financial apps) benefit from the server-side verification model where session state is always current.

Verification example

// Session token must be verified via API
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    session_token: sessionToken
  })
});

const { session, user } = await response.json();

Advantages

✅ Instant revocation - Session is checked server-side on every request
✅ Privacy - No user information exposed in the token
✅ Simplicity - No JWT verification logic needed
✅ Security - Server maintains complete control over session state

Disadvantages

❌ Performance - Requires an API call for every verification
❌ Availability - Dependent on MoonKey API being reachable
❌ Latency - Adds network round-trip time to each request

Session JWT

A session JWT is a JSON Web Token that contains the full session object, cryptographically signed using the RS256 algorithm. It can be verified independently without calling the MoonKey API.

Format

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNlcl8xMjMiLCJzZXNzaW9uX2lkIjoic2Vzc2lvbl94eXoiLCJleHAiOjE3MDQwNjcyMDB9.signature

Characteristics

  • Self-contained: Contains full session and user information
  • Cryptographically signed: Uses RS256 (RSA Signature with SHA-256)
  • Independently verifiable: Can be verified using public keys from JWKS endpoint
  • Short-lived: Expires after 5 minutes (300 seconds)

How JWT verification works

MoonKey uses RS256 (RSA Signature with SHA-256), an asymmetric signing algorithm:
  1. Signing: MoonKey signs JWTs with a private key
  2. Verification: You verify JWTs using a public key
  3. Public keys: Available at https://api.moonkey.fun/v1/auth/jwks/{app_id}
This means anyone with the public key can verify the JWT’s authenticity without needing the private key or calling the MoonKey API.
MoonKey leverages the JSON Web Key (JWK) spec to represent the cryptographic keys used for signing tokens. Learn more about accessing your public keys in the GetJWKsByApp API reference.

JWT expiration and refresh

Each JWT expires after 5 minutes (specified in the exp claim). However, you can refresh the JWT by calling the /sessions/verify API:
// JWT has expired (past 5 minutes) but underlying session is still valid
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    session_jwt: expiredJWT
  })
});

const { session_jwt: newJWT, session, user } = await response.json();
// Returns a fresh JWT with a new 5-minute expiration
Important: If the underlying session has been revoked or expired, even a valid (not-yet-expired) JWT will fail verification when checked against the MoonKey API.

When to use session JWTs

Session JWTs are ideal for performance-critical and distributed systems:

Performance-critical applications

Since JWTs can be verified locally without an API call, you can eliminate network latency from your authentication flow. This is especially valuable for high-traffic applications.

Distributed systems

When your backend services are distributed across multiple servers or regions, JWTs allow each service to verify sessions independently without coordinating with a central authentication service.

External integrations

If you’re integrating with external systems that rely on JWT standards (e.g., third-party APIs, microservices), session JWTs provide a standardized authentication mechanism.

Offline or low-connectivity scenarios

Applications that may operate with intermittent internet connectivity can verify JWTs locally without depending on API availability.

Verification example

import { createRemoteJWKSet, jwtVerify } from 'jose';

// Fetch public keys from MoonKey
const JWKS = createRemoteJWKSet(
  new URL(`https://api.moonkey.fun/v1/auth/jwks/${appId}`)
);

// Verify JWT locally without API call
try {
  const { payload } = await jwtVerify(sessionJWT, JWKS, {
    algorithms: ['RS256']
  });
  
  // JWT is valid, payload contains session data
  const userId = payload.user_id;
  const sessionId = payload.session_id;
  // ... use session data
} catch (error) {
  // JWT is invalid, expired, or signature doesn't match
  console.error('JWT verification failed:', error);
}

Advantages

✅ Performance - No API call needed for verification
✅ Offline capability - Can verify without internet connection
✅ Standards-based - Compatible with JWT ecosystem and libraries
✅ Distributed - Each service can verify independently

Disadvantages

❌ Delayed revocation - Valid JWTs remain usable until 5-minute expiration
❌ Information exposure - JWT payload can be decoded (though not modified)
❌ Complexity - Requires JWT verification logic and key management
❌ Size - JWTs are larger than session tokens

Comparison table

FeatureSession TokenSession JWT
VerificationAPI call requiredLocal verification with public key
RevocationInstantUp to 5-minute delay
PerformanceSlower (API call)Faster (no API call)
PrivacyOpaque tokenUser info visible (but not modifiable)
ExpirationServer-side managed5-minute token expiration
SizeSmall (~80 chars)Large (~500+ chars)
Offline supportNoYes (during 5-min window)
ComplexitySimpleRequires JWT verification setup
StandardsMoonKey-specificJWT standard (RFC 7519)

Choosing the right approach

Use Session Tokens if:

  • Security and instant revocation are top priorities
  • You need real-time session control
  • Privacy is important (no user data exposure)
  • Simplicity is preferred over performance
  • Your application is low to moderate traffic

Use Session JWTs if:

  • Performance is critical (high-traffic applications)
  • You have distributed services that need to verify sessions
  • You’re integrating with JWT-based external systems
  • Offline or low-connectivity operation is needed
  • You can accept a 5-minute revocation delay

Hybrid approach

You can also use both:
// Store both credentials
const { session_token, session_jwt } = authResponse;

// Use JWT for normal requests (fast)
const isValidJWT = await verifyJWTLocally(session_jwt);

// Use session token for sensitive operations (secure)
const { session, user } = await verifySessionToken(session_token);
This approach gives you performance benefits for most requests while maintaining security for critical operations.

Best practices

General

  • Store securely: The MoonKey SDK uses IndexedDB for secure client-side storage
  • Never log tokens: Don’t include session tokens or JWTs in application logs
  • Use HTTPS: Always transmit credentials over secure connections
  • Clear on logout: Remove both tokens when users sign out

For session tokens

  • Verify on every request: Always check validity before processing requests
  • Handle expiration gracefully: Redirect users to login when sessions expire
  • Cache minimally: Session tokens should be verified fresh on each request

For session JWTs

  • Refresh proactively: Refresh JWTs before the 5-minute expiration
  • Validate claims: Check exp, iat, and other claims during verification
  • Use reputable libraries: Use well-maintained JWT libraries like jose or jsonwebtoken
  • Cache public keys: Cache JWKS responses to avoid fetching on every verification
  • Re-verify for sensitive actions: Use the API to double-check session status for critical operations

Implementation examples

Using both with fallback

async function verifySession(sessionToken, sessionJWT) {
  // Try JWT first (fast)
  try {
    const jwtPayload = await verifyJWTLocally(sessionJWT);
    
    // Check if JWT is about to expire
    const expiresAt = jwtPayload.exp;
    const now = Math.floor(Date.now() / 1000);
    const timeRemaining = expiresAt - now;
    
    if (timeRemaining < 60) {
      // Less than 1 minute remaining, refresh via API
      const refreshed = await refreshSessionViaAPI(sessionToken);
      return refreshed;
    }
    
    return jwtPayload;
  } catch (error) {
    // JWT verification failed or expired, fallback to API
    return await verifySessionViaAPI(sessionToken);
  }
}

Automatic JWT refresh

class SessionManager {
  constructor(sessionToken, sessionJWT) {
    this.sessionToken = sessionToken;
    this.sessionJWT = sessionJWT;
    this.refreshInterval = null;
  }

  startAutoRefresh() {
    // Refresh JWT every 4 minutes (before 5-min expiration)
    this.refreshInterval = setInterval(async () => {
      try {
        const response = await this.refreshJWT();
        this.sessionJWT = response.session_jwt;
      } catch (error) {
        // Session expired or invalid
        this.stopAutoRefresh();
        this.handleSessionExpired();
      }
    }, 4 * 60 * 1000); // 4 minutes
  }

  stopAutoRefresh() {
    if (this.refreshInterval) {
      clearInterval(this.refreshInterval);
    }
  }

  async refreshJWT() {
    const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        session_jwt: this.sessionJWT
      })
    });
    return response.json();
  }

  handleSessionExpired() {
    // Clear session and redirect to login
    localStorage.clear();
    window.location.href = '/login';
  }
}