Skip to content

Tokens & refresh

Using tokens on requests, refreshing them, and verifying them in your own backend.

Updated View as Markdown

All requests go to the Truememory Auth base URL (see Before you start for prerequisites: base URL, CORS whitelisting, OAuth client IDs).

Using Tokens

Include the id_token as a Bearer token in all requests to Truememory services. Note that the AI Gateway (your-tie-host) is a separate service from Truememory Auth (your-auth-host):

curl -X POST https://your-tie-host/v1/chat/completions \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"model": "anthropic/claude-sonnet-4-5", "messages": [{"role": "user", "content": "Hello"}]}'

The token is a JWT signed by Firebase. Truememory verifies the signature, checks expiry, and extracts the user identity.

Token Refresh

ID tokens expire after 1 hour. Use the refresh token to get a new one without re-authenticating:

POST https://your-auth-host/auth/refresh
Content-Type: application/json

{
  "refresh_token": "AMf-xB..."
}

Response:

{
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "AMf-xB...",
  "expires_in": "3600"
}

The refresh_token in the response is the same value you sent — Truememory does not rotate refresh tokens. Re-reading it from the response is still the right habit, so your client keeps working unchanged if rotation is introduced later.

Implement automatic refresh in your API client. Refresh before expiry rather than only on 401, and de-duplicate concurrent refreshes so N parallel requests don’t fire N refresh calls:

let idToken: string | null = null;
let expiresAt = 0;
let inFlight: Promise<string> | null = null;

async function refreshIdToken(): Promise<string> {
  // Posts to your own backend, which holds the refresh token in an httpOnly
  // cookie. See "Suggested implementation" for the route handler.
  const res = await fetch("/api/auth/refresh", { method: "POST" });
  if (!res.ok) {
    idToken = null;
    throw new Error("Session expired");
  }

  const data = await res.json();
  idToken = data.id_token;
  expiresAt = Date.now() + Number(data.expires_in) * 1000;
  return data.id_token;
}

async function getIdToken(): Promise<string> {
  // Refresh 5 minutes early so a request never expires mid-flight.
  if (idToken && Date.now() < expiresAt - 5 * 60 * 1000) return idToken;

  inFlight ??= refreshIdToken().finally(() => {
    inFlight = null;
  });
  return inFlight;
}

async function fetchWithAuth(url: string, options: RequestInit = {}) {
  let token = await getIdToken();

  let response = await fetch(url, {
    ...options,
    headers: { ...options.headers, Authorization: `Bearer ${token}` },
  });

  if (response.status === 401) {
    // Refresh once, retry once — never loop.
    idToken = null;
    token = await getIdToken();
    response = await fetch(url, {
      ...options,
      headers: { ...options.headers, Authorization: `Bearer ${token}` },
    });
  }

  return response;
}

Verifying Tokens in Your Backend

If your app has its own backend that needs to verify Truememory tokens (e.g. protecting your own API routes), this section explains how.

Truememory Auth issues Firebase ID tokens — JWTs signed by Firebase’s service account keys with the GCP project ID as the audience. These are not standard Google OAuth2 tokens.

Token structure

{
  "iss": "https://securetoken.google.com/<gcp-project-id>",
  "aud": "<gcp-project-id>",
  "sub": "5iexdslxx...",
  "tie_user_id": "4b822c30-...",
  "email": "user@example.com",
  "email_verified": false,
  "firebase": {
    "sign_in_provider": "password"
  }
}

Key claims:

  • aud — Always the GCP project ID (e.g. mv-stg-tie-core). This is Firebase’s design, not configurable.
  • sub — Firebase/GIP UID.
  • tie_user_id — Truememory’s canonical user UUID (custom claim). Use this when calling Truememory APIs. Set automatically on register, login, and OAuth sign-in.

Verification approaches

There are two valid approaches depending on your security requirements:

Approach Library Signature verified When to use
Full verification firebase-admin Yes Public-facing APIs, production backends
Trusted decode Manual JWT decode No Internal services that trust Truememory over HTTPS
Verify tokens within
$ npm install firebase-admin
import { initializeApp } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";

// Initialize once. Project ID must match the `aud` claim in TIE tokens.
// Staging: "mv-stg-tie-core". Ask the TIE team for the production project ID.
initializeApp({ projectId: "<gcp-project-id>" });

export async function verifyTieToken(idToken: string) {
  const decoded = await getAuth().verifyIdToken(idToken);
  return {
    uid: decoded.uid,                // Firebase/GIP UID (same as `sub`)
    tieUserId: decoded.tie_user_id,  // TIE canonical user UUID
    email: decoded.email,
  };
}
Navigation

Type to search…

↑↓ navigate↵ selectEsc close