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 |