Authentication
How PlayaOS auth works for external camp SDK clients.
External camps authenticate through PlayaOS using OAuth 2.0 with PKCE. The flow is:
- Login redirect — your app redirects the applicant to
https://auth.playaos.app/c/{your-camp}/login - Code exchange — the redirect back carries a
?code=...parameter. Your server exchanges it for tokens. - Session persistence — a refresh token is stored in an HTTP-only cookie and rotated automatically.
Token lifecycle
| Token | TTL | Purpose |
|---|---|---|
idToken | 5–15 minutes | Short-lived identity token for API access |
refreshToken | 30 days | Long-lived, single-use, rotating. Used to get fresh idTokens. |
When a refresh token is redeemed, a new one is issued and the old one is invalidated. If a refresh token is reused (theft signal), the entire token chain is revoked.
Confidential vs public clients
| Type | Secret | Refresh tokens | Use case |
|---|---|---|---|
| Public | None | No | Browser-only PKCE (login/logout) |
| Confidential | PLAYAOS_CLIENT_SECRET | Yes | Server-side exchange — enables session persistence |
External camps MUST provision a confidential client to get refresh tokens. Without refresh tokens, the idToken expires and the user is logged out after 5–15 minutes.
Generate your SDK client secret →
Server-side exchange
The PLAYAOS_CLIENT_SECRET must never reach the browser. Use server-side API routes for the code exchange and refresh:
POST /api/auth/exchange— receives{ code, code_verifier, client_id, redirect_uri }, calls the PlayaOS auth server withPLAYAOS_CLIENT_SECRET, returns{ idToken }and sets the refresh cookie.GET /api/auth/refresh— reads the refresh cookie, calls the auth server to rotate the token, returns a fresh{ idToken }and new refresh cookie.
Implement these routes in your own app. In both exchange and refresh, use NEXT_PUBLIC_PLAYAOS_CONFIDENTIAL_CLIENT_ID with its matching PLAYAOS_CLIENT_SECRET; do not pair a public client ID with a confidential client's secret. Validate client_id and redirect_uri against your configured client and registered callback before exchanging codes.
exchangeCode from @playaos/api-client targets https://auth.playaos.app/api/auth/v1/exchange. Refresh targets /api/auth/v1/refresh on that same auth host. Store the rotating refresh token in an HttpOnly, Secure cookie and send responses with Cache-Control: no-store.
These SDK exchange routes return a PlayaOS idToken for the legacy embed auth flow. It is not a Supabase session JWT: do not send it as X-PlayaOS-Member-Token to /api/v1. Current REST member calls require a Supabase session obtained through Supabase Auth. Read the REST authentication guide before choosing the auth flow for your app.
BRC Pickleball Club
BRC uses the confidential client flow. After login, their admin portal (app.brcpickleballclub.com) keeps session alive via refresh tokens, so admins don't get booted to the login page every few minutes.
The PLAYAOS_CLIENT_SECRET is set in their Vercel environment variables and used in the server-side exchange route.