Standard OAuth 2.0 / OpenID Connect. If you've integrated "Sign in with Google", you already know how this works.
CAGE is open source — inspect the server code, verify the claims, or contribute on GitHub.
QUICK START
CAGE implements standard OAuth 2.0 with OpenID Connect extensions. The flow is identical to any OAuth provider: redirect, callback, token exchange, claims read.
Contact us at partners@cageid.app to get your credentials. Self-serve registration is coming soon.
You'll receive:
client_id: a1b2c3d4-5678-9abc-def0-1234567890ab
client_secret: csk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKeep your client_secret secure — treat it like a password. Never expose it in client-side code.
When a user needs to verify their age, redirect them to the CAGE authorization endpoint:
GET https://api.cageid.app/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://yoursite.com/callback
&response_type=code
&state=RANDOM_STATE_STRING
&scope=openid%20age_verificationclient_idYour partner client ID (UUID) from registrationredirect_uriMust be pre-registered and match exactlyresponse_typeAlways codestateRandom string — verify on callback to prevent CSRFscopeopenid age_verification — recommended, but optionalAfter the user authenticates, CAGE redirects back to your redirect_uri with a short-lived auth code. Exchange it server-side:
POST https://api.cageid.app/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE_FROM_CALLBACK
&redirect_uri=https://yoursite.com/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRETResponse:
{
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer"
}Auth codes expire after 60 seconds and are single-use.
Decode and verify the ID token (see Verify tokens below). The decoded payload:
{
"iss": "https://api.cageid.app",
"sub": "anon_partner_scoped_hash",
"aud": "YOUR_CLIENT_ID",
"age_verified": true,
"age_floor": 18,
"iat": 1741910400,
"exp": 1741996800
}subAnonymous user ID — unique per partner. Cannot be correlated across partners.age_verifiedAlways true if the token was issued.age_floor18 or 21 — matches your registered age requirement.issToken issuer — always https://api.cageid.appaudYour client_idiat / expStandard issued-at and expiry timestamps.SECURITY
Always verify the JWT signature before trusting the claims. Use any standard OIDC/JWT library (jose, jsonwebtoken, etc.) with CAGE's public keys.
JWKS endpointhttps://api.cageid.app/oauth/.well-known/jwks.jsonDiscovery dochttps://api.cageid.app/oauth/.well-known/openid-configurationVerify: signature, iss, aud (must equal your client_id), and exp.
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://api.cageid.app/oauth/.well-known/jwks.json')
);
const { payload } = await jwtVerify(idToken, JWKS, {
issuer: 'https://api.cageid.app',
audience: 'YOUR_CLIENT_ID',
});
console.log(payload.age_verified); // true
console.log(payload.age_floor); // 18 or 21OVERVIEW
User clicks ‘Verify Age’ on your site
Browser redirects to CAGE authorization endpoint
User logs in and consents — or the CAGE browser extension handles it silently
CAGE redirects back to your redirect_uri with an auth code
Your server exchanges the code for an ID token
Your server reads the age_verified and age_floor claims
REFERENCE
Redirect URIs
Must be pre-registered with CAGE and match exactly — including trailing slashes and protocol. No wildcards.
http://localhost URIs are allowed for development and testing. Register them the same way as production URIs.
Age floor
Set during partner registration. Either 18 or 21. Contact us to change it.
Refresh tokens
Not supported at this time. Users re-authenticate when their session expires.
Auth code expiry
60 seconds. Single-use. Exchange immediately after receiving.
REFERENCE
If the OAuth flow fails, CAGE redirects to your redirect_uri with error parameters per the OAuth 2.0 spec:
https://yoursite.com/callback
?error=access_denied
&error_description=User+denied+consent
&state=YOUR_ORIGINAL_STATECommon error codes:
access_deniedUser clicked Deny on the consent screeninvalid_requestMissing or malformed OAuth parametersinvalid_clientUnrecognized client_idinvalid_redirect_uriredirect_uri doesn’t match registered URIserver_errorSomething went wrong on CAGE’s endYour callback handler should check for the error query parameter before attempting to exchange the code.
Email us at partners@cageid.app — we respond to integration questions within one business day.
The server source code is open source on GitHub if you want to inspect the exact endpoint behavior.