Skip to main content

Token Management

YorAuth uses a hybrid token model: short-lived JWT access tokens for stateless API authentication combined with long-lived opaque refresh tokens for session persistence. This gives you the performance of stateless validation with the security of revocable sessions.

Token Types

Access Token (JWT)

The access token is a signed JSON Web Token (JWT) that your application uses to authenticate API requests. It is validated by verifying the RS256 signature against YorAuth's public key — no database lookup required.

Properties:

  • Format: RS256-signed JWT
  • Lifetime: 15 minutes (expires_in: 900)
  • Storage: Client-side memory (do not store in localStorage in browser environments)
  • Revocation: Not directly revocable (short lifetime limits exposure)

Refresh Token (Opaque)

The refresh token is a cryptographically random opaque string, prefixed with ref_. It is stored as a SHA-256 hash in the database. Use it to obtain a new access token when the current one expires.

Properties:

  • Format: ref_ followed by 64 random characters
  • Lifetime: 7 days standard, 30 days with remember_me: true
  • Storage: Secure client-side storage (httpOnly cookie recommended for web apps)
  • Revocation: Immediate via database flag
  • Rotation: A new refresh token is issued on every use

JWT Payload

The access token payload contains the following claims:

json
{
  "jti": "550e8400-e29b-41d4-a716-446655440000",
  "sub": "user-uuid-here",
  "app": "application-uuid-here",
  "email": "jane@example.com",
  "iat": 1708875300,
  "exp": 1708876200
}
ClaimDescription
jtiUnique token ID (UUID). Used for blacklisting on logout.
subThe authenticated user's ID (UUID).
appThe application ID this token belongs to.
emailThe user's email address.
iatIssued at (Unix timestamp).
expExpiration time (Unix timestamp). iat + 900 seconds.

If your application has custom JWT claims configured, additional claims are merged into the payload. Standard claims always take precedence over custom claims.

Impersonation claim: When an admin impersonates a user, the token includes an act claim per RFC 8693:

json
{
  "sub": "impersonated-user-uuid",
  "act": {
    "sub": "admin-user-uuid"
  }
}

Using the Access Token

Include the access token in the Authorization header on every request to a protected endpoint:

text
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
bash
curl https://api.yorauth.com/api/v1/applications/your-application-id/users/550e8400-e29b-41d4-a716-446655440000/profile \
  -H "Authorization: Bearer <access_token>"
javascript
const response = await fetch(
  `https://api.yorauth.com/api/v1/applications/your-application-id/users/${userId}/profile`,
  {
    headers: { 'Authorization': `Bearer ${accessToken}` }
  }
);

When the access token expires, the API returns 401 with error code AUTH_TOKEN_EXPIRED. At that point, use the refresh token to obtain a new pair.


Refresh Token Rotation

Obtain a new access token (and a new refresh token) by presenting the current refresh token. The old refresh token is immediately revoked.

Endpoint: POST /api/v1/applications/{applicationId}/users/token/refresh

Request Body

FieldTypeRequiredDescription
refresh_tokenstringYesThe current refresh token.
bash
curl -X POST https://api.yorauth.com/api/v1/applications/your-application-id/users/token/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "ref_a1b2c3d4e5f6..."}'
javascript
const response = await fetch(
  'https://api.yorauth.com/api/v1/applications/your-application-id/users/token/refresh',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refresh_token: refreshToken })
  }
);
const data = await response.json();

// Store the new tokens — the old refresh token is now invalid
storeAccessToken(data.data.access_token);
storeRefreshToken(data.data.refresh_token);

Response 200 OK

json
{
  "data": {
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refresh_token": "ref_newtoken1234567890...",
    "token_type": "Bearer",
    "expires_in": 900,
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "email": "jane@example.com",
      "name": "Jane Doe",
      "email_verified": true,
      "created_at": "2026-02-25T10:00:00Z",
      "metadata": null
    }
  }
}

Always replace both the access token and the refresh token after a refresh. The old refresh token is revoked and cannot be used again. Storing stale refresh tokens causes AUTH_INVALID_REFRESH_TOKEN errors.

The is_remember_me flag from the original login is carried forward through token rotation. A remember-me session remains a 30-day session through all rotations.

Error Responses

HTTP StatusError CodeDescription
401AUTH_INVALID_REFRESH_TOKENToken is invalid, expired, already revoked, or a reuse was detected.

Token Rotation and Reuse Detection

Every time a refresh token is used, it is rotated:

  1. The old token is marked as revoked with reason rotated.
  2. A new refresh token is generated and linked to the old one via parent_token_id.
  3. The new token is returned to the client.

If a revoked refresh token is used, this indicates potential token theft. YorAuth responds by immediately revoking all active sessions for the affected user. The error returned is still AUTH_INVALID_REFRESH_TOKEN, but the user will find all their sessions terminated and must re-authenticate.

This automatic family revocation limits the damage window even if a refresh token was exfiltrated.


Remember Me

When logging in with remember_me: true, the refresh token lifetime is extended from 7 days to 30 days:

json
{
  "email": "jane@example.com",
  "password": "Str0ng!Passw0rd",
  "remember_me": true
}

The is_remember_me flag is stored on the session record and is carried forward through all subsequent token rotations. You can identify remember-me sessions by the is_remember_me: true field in the sessions list.


Token Lifetime Reference

TokenStandardWith remember_me
Access token15 minutes15 minutes (unchanged)
Refresh token7 days30 days

Implementing Auto-Refresh

A well-behaved client proactively refreshes the access token before it expires, or retries with a refresh on receiving a 401. The following pattern handles concurrent requests safely:

javascript
class TokenManager {
  constructor() {
    this.accessToken = null;
    this.refreshToken = null;
    this.refreshPromise = null;
    this.applicationId = 'your-application-id';
  }

  async request(url, options = {}) {
    const response = await this.makeRequest(url, options);

    if (response.status === 401) {
      // Prevent concurrent refresh calls
      await this.refresh();
      return this.makeRequest(url, options);
    }

    return response;
  }

  async makeRequest(url, options = {}) {
    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${this.accessToken}`
      }
    });
  }

  async refresh() {
    // De-duplicate concurrent refresh calls
    if (this.refreshPromise) {
      return this.refreshPromise;
    }

    this.refreshPromise = (async () => {
      const response = await fetch(
        `https://api.yorauth.com/api/v1/applications/${this.applicationId}/users/token/refresh`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ refresh_token: this.refreshToken })
        }
      );

      if (!response.ok) {
        // Session expired — redirect to login
        this.accessToken = null;
        this.refreshToken = null;
        throw new Error('Session expired');
      }

      const data = await response.json();
      this.accessToken = data.data.access_token;
      this.refreshToken = data.data.refresh_token;
    })();

    try {
      await this.refreshPromise;
    } finally {
      this.refreshPromise = null;
    }
  }
}

Public Key for Verification

YorAuth publishes its current RSA public keys at the JWKS endpoint. Use this to verify access token signatures in your own services without calling YorAuth on every request:

text
GET /.well-known/jwks.json

Response

json
{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "alg": "RS256",
      "n": "xjlCRBqkgU...",
      "e": "AQAB"
    }
  ]
}

Standard JWT libraries can consume this endpoint directly for key discovery and automatic rotation support.