Token Verification & Refresh

How to verify access_token, rotate via refresh_token, and handle expiry.

Token types

TokenTTLFormatRenewal
access_token60 minJWT (ES256-signed)Reissue via refresh_token
refresh_token30 daysOpaque (v1.<random>)Rotated on every use

3 ways to verify access_token (pick one)

1. JWKS signature verification (recommended, server-side)

Fastest and most idiomatic. Fetch the public key from the JWKS endpoint and verify the ES256 signature.

GEThttps://oefybpntdguqyjrhurng.supabase.co/auth/v1/.well-known/jwks.json
Authentication
No authentication required
your-app/lib/verify-jwt.ts
import { jwtVerify, createRemoteJWKSet } from "jose";
 
const JWKS = createRemoteJWKSet(
  new URL("https://oefybpntdguqyjrhurng.supabase.co/auth/v1/.well-known/jwks.json"),
);
 
export async function verify(token: string) {
  const { payload } = await jwtVerify(token, JWKS, {
    algorithms: ["ES256"],
  });
  return payload as {
    sub: string;     // user_id
    email?: string;
    exp: number;
    iat: number;
  };
}

2. /auth/verify endpoint (simple but adds latency)

If integrating JWKS is too heavy, delegate the verification to CP-Auth.

POSThttps://auth.cp-platform.com/auth/verify
Authentication
No authentication required
Rate Limit
600 / min
curl -X POST https://auth.cp-platform.com/auth/verify \
  -H "Content-Type: application/json" \
  -d '{"access_token": "eyJhbGc..."}'
200 response
{
  "valid": true,
  "user_id": "uuid",
  "email": "user@example.com",
  "exp": 1741200000
}

3. Decode only (no verification — never do this)

JSON.parse(atob(token.split('.')[1])) does not verify the signature. Use only for debugging.

Refreshing with refresh_token

When the access_token is (or is about to) expire, swap it for a new pair using the refresh_token.

POSThttps://auth.cp-platform.com/auth/refresh
Authentication
No authentication required
Rate Limit
300 / min
your-app/api/auth/refresh.ts
export async function POST(req: Request) {
  const cookies = req.headers.get("cookie") ?? "";
  const rt = cookies.match(/refresh_token=([^;]+)/)?.[1];
  if (!rt) return new Response(null, { status: 401 });
 
  const res = await fetch("https://auth.cp-platform.com/auth/refresh", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ refresh_token: rt }),
  });
 
  if (!res.ok) return new Response(null, { status: 401 });
  const tokens = await res.json();
 
  const response = Response.json({ ok: true });
  response.headers.append(
    "Set-Cookie",
    `access_token=${tokens.access_token}; HttpOnly; Secure; SameSite=Lax; Max-Age=3600; Path=/`,
  );
  response.headers.append(
    "Set-Cookie",
    `refresh_token=${tokens.refresh_token}; HttpOnly; Secure; SameSite=Lax; Max-Age=2592000; Path=/`,
  );
  return response;
}

Logout (blacklist)

To invalidate a token immediately, add it to the blacklist. TTL is 24 hours.

POSThttps://auth.cp-platform.com/auth/logout
Authentication
User token
Rate Limit
300 / min
curl -X POST https://auth.cp-platform.com/auth/logout \
  -H "Authorization: Bearer <access_token>"

After this, also clear the access_token / refresh_token cookies on your own domain.

Fetching user info

Look up user metadata with a verified token.

GEThttps://auth.cp-platform.com/auth/me
Authentication
User token
Rate Limit
3000 / min
curl https://auth.cp-platform.com/auth/me \
  -H "Authorization: Bearer <access_token>"
Response
{
  "user_id": "uuid",
  "email": "user@example.com",
  "name": "Hong Gildong",
  "is_admin": false
}