놀아 계정
목차

OAuth 2.0 연동 가이드

Authorization 요청부터 토큰 교환, UserInfo 조회까지 전체 흐름을 설명하는 연동 가이드입니다.

놀아 계정은 Authorization Code + PKCE 방식의 OAuth 2.0을 지원합니다. 이 문서는 전체 흐름을 처음부터 끝까지 설명합니다. 바로 붙여보고 싶으시면 5분 만에 붙이기가 더 빠릅니다.

전체 흐름

[내 서비스]                      [놀아 계정]                    [사용자]
    │                                │                            │
 1. verifier/challenge 생성          │                            │
    │──── 2. /oauth/authorize ──────▶│                            │
    │                                │──── 3. 로그인·동의 ───────▶│
    │                                │◀─── 4. 동의 ───────────────│
    │◀─── 5. code + state 콜백 ──────│                            │
 6. state 검증                       │                            │
    │──── 7. /oauth/token ──────────▶│  (서버 → 서버)             │
    │◀─── 8. access/refresh token ───│                            │
    │──── 9. /oauth/userinfo ───────▶│                            │
    │◀─── 10. 사용자 정보 ───────────│                            │

7번부터는 반드시 서버에서 합니다. client_secret이 들어가기 때문입니다.

엔드포인트

역할주소
인증 요청https://account.nola.kr/oauth/authorize
토큰 교환·갱신https://account.nola.kr/oauth/token
사용자 정보https://account.nola.kr/oauth/userinfo
서비스 도메인 정보https://account.nola.kr/oauth/domain-info

파라미터와 응답 필드는 API 레퍼런스에 표로 정리돼 있습니다.

1. PKCE 값 준비

PKCE는 필수입니다. code_challenge 없이 요청하면 pkce_required로 거부되고, 방식은 S256만 받습니다.
// 사용자를 보내기 전에, 서버에서
$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;   // 7번에서 씀
$_SESSION['nolaa_state']    = $state;      // 6번에서 씀

2. 인증 요청

사용자의 브라우저를 아래 주소로 보냅니다.

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·code_challenge가 모두 필수입니다. 하나라도 빠지면 거부됩니다.

redirect_uri는 마이페이지에 등록한 콜백과 문자 하나까지 같아야 합니다. 끝 슬래시, http/https, 포트가 다르면 invalid_redirect_uri입니다.

화면 언어 지정 — ui_locales (선택)

이 값을 붙이면 로그인·계정 확인·동의 화면이 그 언어로 나옵니다. 베트남어 서비스에서 넘어온 사용자가 한국어 화면을 마주하지 않게 하는 용도입니다.

  &ui_locales=vi
  • 지원하는 값은 ko·en·vi입니다. 그 밖의 값은 조용히 무시되고 기본 규칙을 따릅니다.
  • 공백으로 여러 개를 넣으면 지원하는 첫 번째를 씁니다. 예: ui_locales=vi en
  • 사용자가 놀아 계정에서 직접 고른 언어가 있으면 그쪽이 우선합니다. 고른 적이 없는 사용자에게만 적용됩니다 — 사용자의 선택을 서비스가 덮어쓰지 않습니다.
  • 생략하면 브라우저 언어(Accept-Language)로 자동 판별하고, 그래도 모르면 한국어입니다.
  • 로그인 화면을 거쳐 돌아와도 유지됩니다. 동의가 끝나면 지정은 해제됩니다.
화면 언어에만 쓰입니다. 인증·권한·redirect_uri 판단에는 전혀 영향을 주지 않습니다. 사용자는 어느 화면에서든 상단에서 언어를 직접 바꿀 수 있고, 바꿔도 state·code_challenge 같은 값은 그대로 유지됩니다.

3. 로그인과 동의

사용자가 로그인돼 있지 않으면 로그인 화면이 먼저 뜹니다. 이미 로그인돼 있어도 계정 확인 화면이 한 번 나와서, 다른 계정으로 바꿀 기회를 줍니다.

그다음 동의 화면에서 요청한 스코프가 사용자에게 그대로 보여집니다.

요청한 스코프사용자에게 보이는 문구
profile.basicUUID, 닉네임, 이메일
profile.avatar프로필 사진 URL
service.domain.read연결된 서비스의 도메인 상세정보

한 번 동의한 조합은 기억되어, 다음부터는 동의 화면을 건너뜁니다. 새 스코프를 추가로 요청하면 그때 다시 동의를 받습니다.

4~5. 콜백과 state 검증

동의하면 등록된 콜백 주소로 돌아옵니다.

https://내도메인/oauth/callback?code=abcd1234…&state=RANDOM_STATE
토큰 교환 전에 state부터 확인하세요. 보내기만 하고 검사하지 않으면 CSRF 방어가 되지 않습니다.
if (!hash_equals($_SESSION['nolaa_state'] ?? '', $_GET['state'] ?? '')) {
    // 중단
}

사용자가 거부했거나 요청에 문제가 있으면 ?error=...가 대신 옵니다. 코드별 원인은 에러 코드 사전에 있습니다.

6. 토큰 교환

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"
}

인증 코드는 10분 유효 · 1회용입니다. 재시도해도 같은 코드는 두 번 쓸 수 없습니다 (invalid_grant).

토큰 요청의 redirect_uri는 인증 요청 때와 같은 값이어야 합니다.

7. 사용자 정보

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는 서비스마다 다른 값입니다(pairwise). 같은 사람이라도 다른 서비스에서는 다른 sub가 나갑니다. 여러분 서비스 안에서의 사용자 키로 쓰세요.

email이 오는 조건은 스코프 레퍼런스를 보세요. 스코프 문제가 아니라 서비스의 이메일 제공 설정일 수 있습니다.

8. 토큰 갱신

access_token은 기본 1시간, refresh_token은 30일입니다.

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"
갱신하면 refresh_token도 새로 발급되고 기존 것은 폐기됩니다(로테이션). 응답에 담긴 새 값을 반드시 저장하세요. 옛 값을 계속 쓰면 다음 갱신이 실패합니다.

9. 연결 해제 · 로그아웃

놀아 계정에는 토큰을 폐기하는 공개 API(revoke 엔드포인트)가 없습니다. 서비스에서 «연결 끊기» 버튼을 눌러 놀아 쪽 토큰까지 무효화하는 호출은 지금 불가능합니다.

그래서 서비스는 401 invalid_token을 받으면 조용히 로그아웃 처리하도록 만들어 두어야 합니다. 사용자가 놀아 계정 쪽에서 정리했거나 토큰이 만료된 상황에서 서비스가 알 수 있는 유일한 신호입니다.

서비스 자체 로그아웃은 여러분 세션만 지우면 됩니다. 놀아 계정 세션까지 끊을 필요는 없습니다.

연동 체크리스트

  • 서비스 등록 · 도메인 인증 완료 · 콜백 URL 등록 (셋 다 돼야 상태가 «운영중»이 됩니다)
  • redirect_uri를 상수로 고정했는가
  • 요청마다 새 state·code_verifier를 만들고 세션에 저장하는가
  • 콜백에서 state를 검사하는가
  • 토큰 교환을 서버에서 하는가
  • 새 refresh_token을 저장하는가
  • 401을 받았을 때의 처리가 있는가

배포 전에는 보안 체크리스트를 한 번 더 훑어보세요.