Authentication
App tokens: client credentials
App tokens come from POST /oauth/token with grant_type=client_credentials, your
client_id and client_secret, form-encoded. They last 300 seconds and only carry the scopes
your app was granted. Never put the client secret in a browser or a mobile app.
User authorisation: authorization code with PKCE
A player allows your app to act for them, with named scopes, on a consent screen on
africaone.io. Your app never sees the player's session, e-mail, player ID, balances or
location: it gets a single-use code, a 300-second access token whose sub is a per-app
pairwise ID, and a rotating refresh token.
What the player sees
The consent screen shows your app's name, each scope in plain words, the ID your app will see
for them, the host you registered for the return trip, and Allow / Not now. Nothing is shared
until they allow. The receipt records the scopes and the consent-text version. They can
withdraw it at any time; from then on every access token issued under it answers
403 CONSENT_REVOKED on its next use and every refresh token is dead.
1. Register a redirect URI
In the sandbox console, add the exact URL the browser should
return to: https:// with a host, or http://127.0.0.1, http://[::1] or
http://localhost (any port) for local development. No fragment, no credentials, no wildcard.
Matching is exact. At most five per app.
2. Send the player to the consent screen
Generate a PKCE verifier (43 to 128 unreserved characters) and its S256 challenge, keep the
verifier server-side with the state, then redirect the browser to /oauth/authorize on the
player client with response_type=code, client_id, redirect_uri, scope, state,
code_challenge and code_challenge_method=S256. Only S256 is accepted. The endpoints and the
scope list are published at GET /developer/v1/.well-known/oauth-authorization-server.
| Outcome | Query on your registered redirect URI |
|---|---|
| Player allowed | code=aoa_...&state=...&iss=... (120 seconds, once) |
| Player declined or the screen expired | error=access_denied |
| A scope your app is not granted | error=invalid_scope |
| Wrong response type, or no PKCE | error=unsupported_response_type / error=invalid_request |
An unknown client ID or an unregistered redirect URI never redirects: the player sees a refusal
on africaone.io and nothing is shared.
3. Redeem the code from your server
POST /developer/v1/oauth/token with HTTP Basic client authentication,
grant_type=authorization_code, code, the exact redirect_uri and code_verifier. The
answer carries access_token (300 seconds), scope and refresh_token. A code is redeemable
exactly once: a second redemption fails with invalid_grant and revokes the consent and every
token issued under it. Treat it as a theft signal and send the player through the consent
screen again.
4. Use the token
GET /developer/v1/me returns sub (stable for this player in this app, different in every
other app) and display_name with profile.basic.read. GET /developer/v1/cities works with
a user or an app token. An app token cannot call /me (403 USER_TOKEN_REQUIRED) and never
carries a user scope.
5. Refresh and revoke
grant_type=refresh_token rotates the refresh token: the old one stops working; keep the new
one. Presenting a refresh token that was already rotated revokes the whole family and the
consent. A refresh may narrow the scopes, never widen them. POST /developer/v1/oauth/revoke
with token= the refresh token ends the consent immediately (RFC 7009). Refresh tokens last 30
days.
Scopes that exist
| Scope | Audience | Grants |
|---|---|---|
city.public.read | app or user | GET /cities: the reviewed city registry projection |
profile.basic.read | user only | GET /me: the pairwise sub and the chosen display name |
Every other scope in the platform direction does not exist yet and is not grantable; commerce, entitlements, game sessions and regulated assets stay disabled and fail closed.
Why the session cookie never meets your app
The consent decision is a normal first-party call on africaone.io, with the same protections
as every other change a player makes there. The only thing that crosses to your app is the
return URL the server built from your registered redirect URI. The token endpoint takes no
idempotency header because standard OAuth clients cannot send one; the single-use code and the
rotating refresh token are the replay control.