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.