Authentication¶
Access to Milkyway is protected with OAuth 2.0 (Client Credentials Grant). The
sending bank obtains an access token from Planet9's Keycloak and passes it on
every request to Milkyway in the Authorization: Bearer <access_token> header.
This page describes how a sending bank obtains and uses the token in the stage and production environments.
Obtaining credentials¶
At onboarding, Planet9 creates an OAuth client for you in Keycloak and issues a
client_id / client_secret pair. Self-service client registration is not offered.
- Credentials are issued separately for stage and production — these are different realms.
client_secretis a secret: keep it in secure storage, do not commit it to a repository, and do not log it.- To rotate or revoke credentials, contact the Planet9 team.
Environments¶
There is a single Keycloak for both environments; they are separated by realms.
| Environment | Realm | Token endpoint |
|---|---|---|
| stage | planet9-stage |
https://keycloak.ac8o.planet9.ae/realms/planet9-stage/protocol/openid-connect/token |
| production | planet9 |
https://keycloak.ac8o.planet9.ae/realms/planet9/protocol/openid-connect/token |
The realm's full OIDC configuration (endpoints, supported algorithms, JWKS) is available at the discovery URL:
https://keycloak.ac8o.planet9.ae/realms/<realm>/.well-known/openid-configuration
Credentials are not portable between environments
Stage client_id / client_secret do not work in production and vice versa.
Use the realm and credentials of the environment you are calling.
Getting a token¶
Send a request to your environment's token endpoint with
grant_type=client_credentials (example for stage):
curl -s -X POST \
'https://keycloak.ac8o.planet9.ae/realms/planet9-stage/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials' \
-d 'client_id=<your client_id>' \
-d 'client_secret=<your client_secret>'
For production, replace the planet9-stage realm with planet9.
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI...",
"expires_in": 300,
"token_type": "Bearer",
"scope": "..."
}
Using the token¶
Pass the obtained access_token on every request to Milkyway in the
Authorization header:
curl 'https://<milkyway-api>/payments/v1/precheck' \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{ ... }'
Milkyway API base URL
The Milkyway API address for stage and production is agreed at onboarding and is not listed here — a token from a given realm is used against the API of the same environment.
Lifetime and renewal¶
- The access token is short-lived (see
expires_in, in seconds). Once it expires, request a new one with the same request — the Client Credentials Grant does not use a refresh token. - Cache the token and reuse it until it expires; do not request a new token on
every API call. It is recommended to refresh the token ahead of time, with a small
margin before
expires_in.
sequenceDiagram
autonumber
participant S as Sending bank
participant K as Keycloak
participant M as Milkyway
S->>K: POST token (client_credentials)
K-->>S: access_token (+ expires_in)
Note over S: Cache until expiry
S->>M: Request + Authorization: Bearer <token>
M-->>S: Response
Common errors¶
| Symptom | Likely cause |
|---|---|
401 invalid_client from Keycloak |
Wrong client_id / client_secret, or calling the wrong realm (environment mixed up). |
401 Unauthorized from Milkyway |
Token expired, missing Authorization header, or token issued in a different environment. |
400 unauthorized_client |
Client Credentials Grant is not enabled for the client — contact the Planet9 team. |