OAuth 2.0 integration guide
The whole flow, from the authorization request through the token exchange to the UserInfo call.
놀아 Account supports OAuth 2.0 with the Authorization Code + PKCE flow. This document walks through the whole flow from start to finish. If you would rather try it right away, Integrate in 5 minutes is faster.
The whole flow
[Your service] [놀아 Account] [User]
│ │ │
1. Create verifier/challenge │ │
│──── 2. /oauth/authorize ──────▶│ │
│ │──── 3. Sign in · consent ─▶│
│ │◀─── 4. Consent ────────────│
│◀─── 5. code + state callback ──│ │
6. Verify state │ │
│──── 7. /oauth/token ──────────▶│ (server → server) │
│◀─── 8. access/refresh token ───│ │
│──── 9. /oauth/userinfo ───────▶│ │
│◀─── 10. User info ─────────────│ │
From step 7 on, always do it on your server, because client_secret is involved.
Endpoints
| Role | URL |
|---|---|
| Authorization request | https://account.nola.kr/oauth/authorize |
| Token exchange and refresh | https://account.nola.kr/oauth/token |
| User info | https://account.nola.kr/oauth/userinfo |
| Service domain info | https://account.nola.kr/oauth/domain-info |
Parameters and response fields are laid out in tables in the API reference.
1. Prepare the PKCE values
code_challenge is rejected with
pkce_required, and only S256 is accepted.// Before you send the user away, on your server $verifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '='); $challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '='); $state = bin2hex(random_bytes(16)); $_SESSION['nolaa_verifier'] = $verifier; // used at step 7 of the flow $_SESSION['nolaa_state'] = $state; // used at step 6 of the flow
2. Authorization request
Send the user's browser to the URL below.
https://account.nola.kr/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=YOUR_REDIRECT_URI &scope=profile.basic%20profile.avatar &state=RANDOM_STATE &code_challenge=CODE_CHALLENGE &code_challenge_method=S256
client_id, redirect_uri, state, scope and code_challenge
are all required. If any one is missing, the request is rejected.
redirect_uri must match the callback registered in My account character for character.
A different trailing slash, http/https or port gives invalid_redirect_uri.Choosing the screen language — ui_locales (optional)
Add this and the sign-in, account-confirmation and consent screens appear in that language. It exists so that a user arriving from a Vietnamese service doesn't land on a Korean screen.
&ui_locales=vi
- Supported values are
ko,enandvi. Anything else is ignored silently and the default rules apply. - Separate several with spaces and the first supported one wins. For example:
ui_locales=vi en - A language the user has chosen themselves in 놀아 Account takes priority. This only applies to users who have never chosen one — a service cannot override the user's choice.
- If omitted, the browser language (
Accept-Language) is used, falling back to Korean. - It survives the trip through the sign-in screen, and is dropped once consent is finished.
redirect_uri check.
Users can switch language themselves from the top of any screen, and doing so preserves state, code_challenge and every other parameter.3. Sign-in and consent
If the user is not signed in, the sign-in screen appears first. Even if they are already signed in, an account confirmation screen appears once, giving them a chance to switch to a different account.
Then the consent screen shows the user the scopes you requested, as they are.
| Requested scope | Text shown to the user |
|---|---|
profile.basic | UUID, nickname, email address |
profile.avatar | Profile picture URL |
service.domain.read | Domain details of the connected service |
Once a combination has been consented to, it is remembered and the consent screen is skipped from then on. If you request a new scope in addition, consent is asked for again at that point.
4–5. Callback and state verification
When the user consents, they return to the registered callback URL.
https://your-domain.com/oauth/callback?code=abcd1234…&state=RANDOM_STATE
state before the token exchange.
If you send it but never check it, you have no CSRF protection.if (!hash_equals($_SESSION['nolaa_state'] ?? '', $_GET['state'] ?? '')) {
// abort
}
If the user denied the request, or there is a problem with the request, ?error=... comes back instead.
The cause of each code is in the Error code dictionary.
6. Token exchange
curl -X POST https://account.nola.kr/oauth/token \ -d "grant_type=authorization_code" \ -d "code=RECEIVED_CODE" \ -d "redirect_uri=YOUR_REDIRECT_URI" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "code_verifier=SAVED_VERIFIER"
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…",
"scope": "profile.avatar profile.basic"
}
An authorization code is valid for 10 minutes and single-use. Retrying does not let you use the same code twice
(invalid_grant).
The redirect_uri in the token request must be the same value as in the authorization request.
7. UserInfo
curl https://account.nola.kr/oauth/userinfo \ -H "Authorization: Bearer ACCESS_TOKEN"
{
"sub": "pws_xxxxxxxxxxxxxxxx",
"nickname": "놀아유저",
"scope": "profile.avatar profile.basic service.member_email.read",
"profile_image_url": "https://…/uploads/….jpg",
"email": "user@example.com"
}
sub is a different value for each service (pairwise).
The same person gets a different sub in a different service. Use it as the user key inside your own service.See the Scope reference for the conditions under which email is returned.
It may not be a scope problem but the service's email sharing setting.
8. Refreshing tokens
By default, access_token lasts 1 hour and refresh_token lasts 30 days.
curl -X POST https://account.nola.kr/oauth/token \ -d "grant_type=refresh_token" \ -d "refresh_token=SAVED_REFRESH_TOKEN" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET"
9. Disconnecting · signing out
놀아 Account has no public API for revoking tokens (no revoke endpoint). Right now, a “disconnect” button in your service cannot make a call that invalidates the token on the 놀아 side.
So build your service to sign the user out quietly when it receives 401 invalid_token.
When the user has cleaned things up on the 놀아 Account side, or the token has expired, that is the only signal your service can get.
Signing out of your own service only requires clearing your own session. There is no need to end the 놀아 Account session too.
Integration checklist
- Service registered, domain verified and callback URL registered (all three are needed for the status to become “Active”)
- Is
redirect_urifixed as a constant? - Do you create a new
stateandcode_verifierfor every request and store them in the session? - Do you check
statein the callback? - Do you do the token exchange on your server?
- Do you store the new
refresh_token? - Do you have handling for when you receive a
401?
Before you ship, look through the Security checklist once more.