Appearance
토스 로그인 API
토스 로그인 연동에 필요한 API를 설명합니다. 인가 코드 발급, 토큰 관리, 사용자 정보 조회, 로그인 연결 끊기, 약관 관리 순서로 구성되어 있습니다.
인가 코드 받기
GET /authorize
사용자를 토스 로그인 화면으로 보낸 후 로그인이 완료되면, 토스 서버가 인가 코드를 redirect_uri에 포함하여 리다이렉트합니다.
- 인가코드 유효시간: 10분
요청
요청 파라미터
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| grant_type | string | Y | authorization_code 로 고정 |
| client_id | string | Y | 사전에 발급 받은 client_id |
| redirect_uri | string | Y | 인가 코드를 받을 서버 URI |
| response_type | string | Y | code 로 고정 |
| scope | string | N | 획득하고자 하는 권한 (예: user_name) |
| state | string | N | CSRF 공격 방지 토큰. redirect_uri에 포함되어 리다이렉트됩니다. |
| policy | string | N | 로그인 동선 구분 (LOGIN / JOIN / AUTO_SELECT)- LOGIN/JOIN: 로그인과 회원가입을 구분하여 요청할 경우 사용 - AUTO_SELECT: 최초 1회 본인확인 후 로그인 프로세스로 진행 |
| app | string | N | 브라우저 세션이 있어도 토스앱을 경유하도록 강제. requires로 설정 시 강제됩니다. |
| flow | string | N | 브라우저 세션을 무시하고 동작. ignore_session으로 설정 시 세션이 무시됩니다. |
| intentUnsupported | string | N | 안드로이드 웹뷰에서 intent 파싱이 되지 않는 버전 구분 |
요청 예시
bash
https://oauth2.cert.toss.im/authorize?grant_type=authorization_code&client_id=wnjo5yivr4ftuwlea93n4zazzzszx22o&response_type=code&redirect_uri=https://toss.im성공 응답
성공 시 요청 파라미터의 redirect_uri로 HTTP 302 리다이렉트됩니다.
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| code | string | Y | AccessToken 획득을 위한 인가 코드 |
| state | string | N | 요청 파라미터로 전달한 state 값 |
실패 응답
실패 시 요청 파라미터의 redirect_uri로 HTTP 302 리다이렉트됩니다.
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| error | string | Y | 에러 코드 |
| error_description | string | Y | 에러 메시지 |
| state | string | N | 요청 파라미터로 전달한 state 값 |
액세스 토큰 받기
POST /token
인가 코드로 액세스 토큰(JWT, JSON Web Token)과 리프레시 토큰을 발급합니다.
- AccessToken 유효시간: 1시간
요청
요청 헤더
| 이름 | 필수 | 설명 |
|---|---|---|
| Content-Type | Y | application/x-www-form-urlencoded;charset=utf-8 |
요청 페이로드
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| grant_type | string | Y | authorization_code 로 고정 |
| client_id | string | Y | 사전에 발급 받은 client_id |
| client_secret | string | Y | 사전에 발급 받은 client_secret |
| code | string | Y | 인가 코드 받기에서 획득한 인가 코드 |
| redirect_uri | string | Y | 인가 코드 요청에 사용한 redirect_uri |
요청 예시
bash
curl --request POST 'https://oauth2.cert.toss.im/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=fKjvI8ILZcj3Q8gX-8lFM2weU-_0Z0tLsNK_OiPG6iCWh0wpQW5QPAL_KLGGj8br3gGeu3kyv37tf2iZPDhNGKzza72_4FoAf8vdRHk4oK8-U5Cyb-hos412457jG8Rk' \
--data-urlencode 'client_id=3ezt6e1th86oday9phzz84u4ovng6ss' \
--data-urlencode 'client_secret={Client_secret}' \
--data-urlencode 'redirect_uri={redirect_uri}'성공 응답
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| token_type | string | Y | Bearer 로 고정 |
| access_token | string | Y | AccessToken (JWT) |
| refresh_token | string | Y | RefreshToken |
| expires_in | number | Y | 만료시간 (초) |
| scope | string | Y | 인가된 scope |
json
{
"access_token": "eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ...",
"refresh_token": "xNEYPASwWw0n1A_",
"scope": "user_ci,user_birthday,user_name,user_phone,user_gender",
"token_type": "Bearer",
"expires_in": 3600
}실패 응답
인가 코드가 만료되었거나 중복 사용한 경우
json
{ "error": "invalid_grant" }액세스 토큰 재발급하기
POST /token
리프레시 토큰으로 액세스 토큰을 재발급합니다.
- RefreshToken 유효시간: 1일
요청
요청 헤더
| 이름 | 필수 | 설명 |
|---|---|---|
| Content-Type | Y | application/x-www-form-urlencoded;charset=utf-8 |
요청 페이로드
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| grant_type | string | Y | refresh_token 으로 고정 |
| client_id | string | Y | 사전에 발급 받은 client_id |
| client_secret | string | Y | 사전에 발급 받은 client_secret |
| refresh_token | string | Y | 액세스 토큰 받기에서 획득한 RefreshToken |
요청 예시
bash
curl --request POST 'https://oauth2.cert.toss.im/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=xNEYPASwWw0n1AxZUHU9KeGj8BitDyYo4wi8rpfkUcJwByVxpAdUzwtIaWGVL6vHdrXLCxIlHAQRPF9hHnFleTsHkqUXzc-_78sD_r1Uh5Ff9UCYfArx8LTn1Vk99dDb' \
--data-urlencode 'client_id=3ezt6e1th86oday9p9cuy84u4bbng6ss' \
--data-urlencode 'client_secret={Client_secret}'성공 응답
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| token_type | string | Y | Bearer 로 고정 |
| access_token | string | Y | 신규 AccessToken (JWT) |
| refresh_token | string | Y | 신규 RefreshToken |
| expires_in | number | Y | 만료시간 (초) |
| scope | string | Y | 인가된 scope |
실패 응답
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| error | string | Y | 에러 코드 |
| error_description | string | Y | 에러 메시지 |
사용자 정보 조회하기
두 엔드포인트가 있으며 DI 포함 여부와 호출 횟수 제한만 다릅니다.
| 엔드포인트 | DI 반환 | 횟수 제한 |
|---|---|---|
GET /oauth2/api/login/user/me | O (암호화) | 1회 |
GET /oauth2/api/login/user/me/without-di | null | 제한 없음 |
DI 조회 횟수를 초과한 경우 /oauth2/api/login/user/me/without-di를 사용하세요. (di 필드는 null로 반환됩니다.)
요청
요청 헤더
| 이름 | 필수 | 설명 |
|---|---|---|
| Content-Type | Y | application/json;charset=utf-8 |
| Authorization | Y | Authorization: Bearer {AccessToken} |
요청 예시
bash
# DI 포함 (1회 제한)
curl --request GET 'https://oauth2.cert.toss.im/oauth2/api/login/user/me' \
--header 'Authorization: Bearer $access_token'
# DI 미포함 (횟수 제한 없음)
curl --request GET 'https://oauth2.cert.toss.im/oauth2/api/login/user/me/without-di' \
--header 'Authorization: Bearer $access_token'성공 응답
| 이름 | 타입 | 필수 | 암호화 여부 | 설명 |
|---|---|---|---|---|
| userKey | number | Y | 유저 식별자 | |
| scope | string | Y | 인가된 scope | |
| agreedTerms | list | Y | 동의한 약관 목록 | |
| policy | string | Y | LOGIN / JOIN / AUTO_SELECT | |
| certTxId | string | N | 회원가입인 경우 본인확인 식별자 | |
| ci | string | N | Y | CI |
| di | string | N | Y | DI (1회 제한, 이후 null) |
| name | string | N | Y | 이름 |
| callingCode | string | N | Y | 국가 전화번호 코드 (예: 82 = 대한민국) |
| phone | string | N | Y | 휴대전화번호 |
| gender | string | N | Y | 성별 (MALE / FEMALE) |
| nationality | string | N | Y | 내/외국인 여부 (LOCAL / FOREIGNER) |
| birthday | string | N | Y | 생년월일 (yyyyMMdd) |
| string | N | Y | 이메일 (점유인증하지 않은 경우 null) |
응답 예시
json
{
"resultType": "SUCCESS",
"success": {
"userKey": 443731104,
"scope": "user_ci,user_birthday,user_nationality,user_name,user_phone,user_gender",
"agreedTerms": [],
"policy": "AUTO_SELECT",
"certTxId": "ad052b57-dc8f-4cdb-a6e2-7b494e28b5ec",
"name": "ENCRYPTED_VALUE",
"callingCode": "ENCRYPTED_VALUE",
"phone": "ENCRYPTED_VALUE",
"birthday": "ENCRYPTED_VALUE",
"ci": "ENCRYPTED_VALUE",
"di": null,
"gender": "ENCRYPTED_VALUE",
"nationality": "ENCRYPTED_VALUE",
"email": null
}
}실패 응답
| errorCode | 설명 |
|---|---|
INTERNAL_ERROR | 내부 서버 에러 |
USER_KEY_NOT_FOUND | 요청한 userKey에 해당하는 유저 없음 |
USER_NOT_FOUND | 토스 유저 정보를 찾을 수 없음 |
BAD_REQUEST_RETRIEVE_CERT_RESULT_EXCEEDED_LIMIT | DI 포함 조회 횟수 초과 → /without-di 엔드포인트 사용 |
json
{
"resultType": "FAIL",
"error": {
"errorCode": "INTERNAL_ERROR",
"reason": "요청을 처리하는 도중에 문제가 발생했습니다."
}
}사용자 정보 복호화
사용자 정보 응답에서 암호화된 필드(ci, di, name, phone 등)는 연동 시 발급받은 개인정보 암호키로 복호화해야 합니다. 아래 알고리즘과 샘플 코드를 참고하세요.
암호화 알고리즘
- 방식: AES-256-GCM(Galois/Counter Mode)
- IV/NONCE: 암호화된 데이터 앞 12바이트에 첨부
- AAD(Additional Authenticated Data): 암호화 키와 함께 별도 전달
복호화 샘플 코드
- Kotlin
kotlin
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
fun decrypt(encryptedText: String, base64EncodedAesKey: String, aad: String): String {
val IV_LENGTH = 12
val decoded = Base64.getDecoder().decode(encryptedText)
val key = SecretKeySpec(Base64.getDecoder().decode(base64EncodedAesKey), "AES")
val iv = decoded.copyOfRange(0, IV_LENGTH)
val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply {
init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(32 * Byte.SIZE_BITS, iv))
updateAAD(aad.toByteArray())
}
return String(cipher.doFinal(decoded, IV_LENGTH, decoded.size - IV_LENGTH))
}- Java
java
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public String decrypt(String encryptedText, String base64EncodedAesKey, String aad) throws Exception {
final int IV_LENGTH = 12;
byte[] decoded = Base64.getDecoder().decode(encryptedText);
byte[] iv = new byte[IV_LENGTH];
System.arraycopy(decoded, 0, iv, 0, IV_LENGTH);
SecretKeySpec key = new SecretKeySpec(Base64.getDecoder().decode(base64EncodedAesKey), "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(32 * Byte.SIZE, iv));
cipher.updateAAD(aad.getBytes());
return new String(cipher.doFinal(decoded, IV_LENGTH, decoded.length - IV_LENGTH));
}- PHP
php
function decrypt($encryptedText, $base64EncodedAesKey, $aad) {
$IV_LENGTH = 12;
$decoded = base64_decode($encryptedText);
$iv = substr($decoded, 0, $IV_LENGTH);
$ciphertext = substr($decoded, $IV_LENGTH, -16);
$tag = substr($decoded, -16);
return openssl_decrypt($ciphertext, 'aes-256-gcm', base64_decode($base64EncodedAesKey),
OPENSSL_RAW_DATA, $iv, $tag, $aad);
}- JavaScript (Node.js)
javascript
const crypto = require('crypto');
function decrypt(encryptedText, base64AesKey, aad) {
const IV_LENGTH = 12;
const decoded = Buffer.from(encryptedText, 'base64');
const key = Buffer.from(base64AesKey, 'base64');
const iv = decoded.slice(0, IV_LENGTH);
const authTag = decoded.slice(decoded.length - 16);
const ciphertext = decoded.slice(IV_LENGTH, decoded.length - 16);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(authTag);
decipher.setAAD(Buffer.from(aad));
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf-8');
}로그인 연결 끊기
POST /oauth2/api/login/access/remove
토스 로그인 연결을 끊습니다. access_token 사용 방식에 따라 두 가지 방식으로 나뉩니다.
Authorization Code Grant 방식
Authorization Code Grant 방식으로 획득한 access_token을 사용합니다.
요청 헤더
| 이름 | 필수 | 설명 |
|---|---|---|
| Content-Type | Y | application/json;charset=utf-8 |
| Authorization | Y | Authorization: Bearer {AccessToken} |
요청 예시
bash
curl --request POST 'https://oauth2.cert.toss.im/oauth2/api/login/access/remove' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $access_token'성공 응답
json
{
"resultType": "SUCCESS",
"success": {
"userKey": 443731103
}
}Client Credentials 방식
Client Credentials Grant 방식으로 획득한 access_token을 사용합니다. 권한이 있는 Client에서 요청 중임을 검증하기 위해 고객사에서 직접 access_token을 발급합니다.
WARNING
연결 끊기 요청의 키로 userKey를 사용하므로, 유저별 userKey를 사전에 보관해야 합니다. scope에 연결 끊기 권한(login:access_remove)이 설정되어 있어야 합니다. (사전 요청 필요)
1단계: client_credentials 방식으로 AccessToken 발급
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| grant_type | string | Y | client_credentials 로 고정 |
| client_id | string | Y | 사전에 발급 받은 client_id |
| client_secret | string | Y | 사전에 발급 받은 client_secret |
| scope | string | Y | login:access_remove |
bash
curl --request POST 'https://oauth2.cert.toss.im/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=56gnin234285rm8lq7tm6xl1jtlsevhm' \
--data-urlencode 'client_secret=7SzN7nMGlciRIt2MXndx534Dnskdi3v0po2h4Vunaf1ChP9L' \
--data-urlencode 'scope=login:access_remove'성공 응답
json
{
"access_token": "eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYi...",
"scope": "login:access_remove",
"token_type": "Bearer",
"expires_in": 3599
}에러 응답 (login:access_remove scope 권한이 없는 Client)
json
{ "error": "invalid_scope" }2단계: 특정 유저 연결 끊기
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| userKey | number | Y | 유저 식별자 |
bash
curl --request POST 'https://oauth2.cert.toss.im/oauth2/api/login/access/remove' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $access_token' \
--data '{"userKey": 443731103}'성공 응답
json
{
"resultType": "SUCCESS",
"success": {
"userKey": 443731103
}
}실패 응답
json
{
"resultType": "FAIL",
"error": {
"errorCode": "USER_KEY_NOT_FOUND",
"reason": "UserKeyNotFound"
}
}json
{
"resultType": "FAIL",
"error": {
"errorCode": "BAD_REQUEST_NOT_ALLOWED_SCOPE",
"reason": "잘못된 요청입니다."
}
}연결 끊기 콜백
유저가 토스 앱 내에서 서비스와의 연결을 해제하면 서비스 서버로 알림을 보냅니다.
서비스에서 직접 API를 호출하여 연결을 끊은 경우에는 콜백이 호출되지 않습니다.
GET 방식
bash
GET $callback_url?userKey=443731103&referrer=UNLINKPOST 방식
bash
curl --request POST '$callback_url' \
--header 'Content-Type: application/json' \
--data '{"userKey": 443731103, "referrer": "UNLINK"}'referrer 값
| 값 | 의미 |
|---|---|
UNLINK | 유저가 토스 앱에서 직접 연결 끊기 |
WITHDRAWAL_TERMS | 로그인 서비스 약관 철회 |
WITHDRAWAL_TOSS | 토스 회원 탈퇴 |
약관 조회하기
POST /oauth2/api/login/user/terms
제휴사 약관에 대한 사용자 동의 여부를 조회합니다. Authorization Code Grant 또는 Client Credentials Grant로 발급받은 액세스 토큰을 사용할 수 있습니다.
요청
요청 헤더
| 이름 | 필수 | 설명 |
|---|---|---|
| Content-Type | Y | application/json;charset=utf-8 |
| Authorization | Y | Authorization: Bearer {AccessToken} |
요청 페이로드
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| userKey | long | N | 사용자 식별자 (Client Credentials 방식 사용 시 필수) |
| tags | list<string> | N | 조회할 약관 태그 목록. null이면 전체 조회 |
| responseFilter | string | N | ALL_SERVICE_TERMS: 전체 / ONLY_USER_AGREED: 동의한 약관만. null이면 ALL_SERVICE_TERMS와 동일 |
요청 예시
bash
# Authorization Code Grant — 전체 약관 조회
curl --request POST 'https://oauth2.cert.toss.im/oauth2/api/login/user/terms' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $access_token'
# Authorization Code Grant — 동의한 약관만 조회
curl --request POST 'https://oauth2.cert.toss.im/oauth2/api/login/user/terms' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $access_token' \
--data '{"responseFilter":"ONLY_USER_AGREED"}'
# Client Credentials Grant — userKey로 약관 조회
curl --request POST 'https://oauth2.cert.toss.im/oauth2/api/login/user/terms' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $access_token' \
--data '{"userKey":709418781,"responseFilter":"ONLY_USER_AGREED"}'성공 응답
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| userKey | long | Y | 사용자 식별자 |
| terms | list<object> | Y | 약관 목록 |
| - tag | string | Y | 약관 태그 |
| - title | string | Y | 약관명 |
| - url | string | N | 약관 URL |
| - isRequired | boolean | Y | 필수 동의 여부 |
| - isRevocable | boolean | Y | 철회 가능 여부 |
| - isAgreed | boolean | Y | 동의 상태 |
json
{
"resultType": "SUCCESS",
"success": {
"userKey": 709418781,
"terms": [
{
"tag": "buyer_terms_policy",
"title": "구매회원 이용약관",
"url": "http://member2.gmarket.co.kr/TermsPolicy/BuyerTermsPolicy",
"isRequired": true,
"isRevocable": false,
"isAgreed": true
},
{
"tag": "marketingAgreed",
"title": "마케팅 동의",
"url": "https://toss.im",
"isRequired": false,
"isRevocable": true,
"isAgreed": true
}
]
}
}약관 동의 철회하기
POST /oauth2/api/login/user/terms/revoke
사용자가 동의한 약관의 동의를 철회합니다. 약관 조회 API에서 isRevocable: true인 약관만 철회 가능합니다. Authorization Code Grant 또는 Client Credentials Grant로 발급받은 액세스 토큰을 사용할 수 있습니다.
요청
요청 헤더
| 이름 | 필수 | 설명 |
|---|---|---|
| Content-Type | Y | application/json;charset=utf-8 |
| Authorization | Y | Authorization: Bearer {AccessToken} |
요청 페이로드
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| userKey | long | N | 사용자 식별자 (Client Credentials 방식 사용 시 필수) |
| tags | list<string> | Y | 동의 철회할 약관 태그 목록 |
요청 예시
bash
# Authorization Code Grant
curl --request POST 'https://oauth2.cert.toss.im/oauth2/api/login/user/terms/revoke' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $access_token' \
--data '{"tags":["marketingAgreed"]}'
# Client Credentials Grant — userKey로 약관 동의 철회
curl --request POST 'https://oauth2.cert.toss.im/oauth2/api/login/user/terms/revoke' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $access_token' \
--data '{"userKey":709418781,"tags":["marketingAgreed"]}'Client Credentials 방식의 경우 scope에 user:terms_revocation이 포함된 액세스 토큰이 필요합니다.
bash
curl --request POST 'https://oauth2.cert.toss.im/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=$client_id' \
--data-urlencode 'client_secret=$client_secret' \
--data-urlencode 'scope=user:terms_revocation'성공 응답
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| userKey | long | Y | 사용자 식별자 |
| terms | list<object> | Y | 철회된 약관 목록 |
| - tag | string | Y | 약관 태그 |
| - title | string | Y | 약관명 |
| - url | string | N | 약관 URL |
| - isRequired | boolean | Y | 필수 동의 여부 |
| - isRevocable | boolean | Y | 철회 가능 여부 |
| - isAgreed | boolean | Y | 동의 상태 (철회 후 false) |
json
{
"resultType": "SUCCESS",
"success": {
"userKey": 709418781,
"terms": [
{
"tag": "marketingAgreed",
"title": "마케팅 수신 동의",
"url": "http://toss.im",
"isRequired": false,
"isRevocable": true,
"isAgreed": false
}
]
}
}