Appearance
앱인토스 로그인 연동하기
앱인토스에서 토스 회원을 연동해보세요.
시작하기
계약하기
토스 로그인 사용을 위해서는 계약이 필요해요.
계약은 콘솔에서 진행하며, 대표관리자만 진행합니다.
토스 로그인 설정하기
로그인 연동을 위해서는 콘솔에 사전 셋팅이 필요해요.
콘솔에 입력한 정보를 바탕으로 사용자가 진행할 로그인 약관 화면을 구성합니다.
- 앱 정보 : 앱 로고, 홈페이지 주소 등을 설정해요.
- 사용자의 권한 목록 : 이름, 이메일 등 필요한 권한을 설정할 수 있어요.
- 약관 목록 : 로그인 약관을 등록할 수 있어요.
- 연결 끊기 콜백 정보 : 토스앱에서 로그인을 끊었을 경우 콜백을 받을 수 있어요.
등록하신 앱 선택 후 토스 로그인 탭에서 로그인 설정을 할 수 있어요.
+ 추가 를 눌러주세요.
사용자 권한 범위 설정하기
기본 정보는 앱 정보 탭에서 등록한 정보를 기본으로 반영합니다.
수정이 필요하다면 앱 정보를 수정해주세요.
토스 로그인을 통해 수집하려는 사용자의 권한 범위를 설정해주세요.
- 이름(USER_NAME) : 사용자의 이름
- 이메일(USER_EMAIL) : 사용자의 이메일로 토스 가입 시 필수사항이 아니기 때문에 값이 존재하지 않을 수 있어요. 그럴 경우 null로 전달드려요.
- 성별(USER_GENDER) : 사용자의 성별
- 생일(USER_BIRTHDAY) : 사용자의 생년월일
- 국적(USER_NATIONALITY) : 사용자의 국적
- 전화번호(USER_PHONE) : 사용자의 전화번호
- CI(USER_CI) : 사용자를 식별하는 KEY값인 CI(Connection Information)
연결 끊기 콜백 정보 설정하기
사용자가 토스앱에서 로그인을 끊었을 경우 콜백을 받을 수 있어요.
🚨 잠시만요
사용자가 토스앱에서 로그인 끊기를 할 경우, 토스에서는 사용자의 동의 약관 정보나 로그인 정보 등을 모두 삭제합니다.
약관 설정하기
앱인토스에서 서비스를 운영하기 위한 약관을 등록해주세요.
토스 로그인 서비스의 약관과 개인정보 제 3자 제공동의는 자동으로 등록됩니다.
다만, 파트너사의 서비스 약관 및 개인정보 수집약관 그리고 필요시 마케팅 정보 수신 동의는 추가로 등록해주셔야 합니다.
활용하고자 하는 목적에 따라 정확한 동의문 링크를 첨부해 주세요.
🚨 잠시만요
간편 로그인 검토 가이드라인을 참고해주세요.
가이드라인에 부합하지 않을 경우 반려될 수 있어요.
우측에 사용자가 진행할 로그인 약관 화면을 볼 수 있어요.
모든 설정을 완료하셨다면 토스 로그인 검토 요청하기 로 검토를 요청하세요.
영업일 기준 3일 이내 승인해드려요.
개발을 위한 키 확인하기
토스 로그인 연동이 승인되었다면 개발을 위한 키 확인을 할 수 있어요.
복호화 키는 이메일로 키 받기 버튼을 통해 이메일로 받을 수 있어요.
- Client ID
- 복호화 키
연동 프로세스 이해하기
통신 방화벽 확인하기
In/Out Bound 방화벽 관리를 하신다면 아래 IP를 방화벽에 등록해주세요.
가맹점이 허용해야하는 inbound IP 목록(앱인토스 -> 가맹점)
IP | port |
---|---|
117.52.3.11 | 443 |
211.115.96.11 | 443 |
106.249.5.11 | 443 |
117.52.3.80~87 | 443 |
211.115.96.80~87 | 443 |
106.249.5.80~87 | 443 |
가맹점이 허용해야하는 outbound IP 목록(가맹점 -> 앱인토스)
도메인 | IP | port |
---|---|---|
apps-in-toss-api-toss.im | 117.52.3.192, 211.115.96.192, 106.249.5.192 | 443 |
API 공통규격 확인하기
도메인 정보
https://apps-in-toss-api.toss.im
API 공통 응답
성공
json
// 성공일 경우 resultType이 SUCCESS로 설정되며 해당 API의 응답이 success 하위에 적재됩니다.
{
"resultType":"SUCCESS",
"success":{
"sample":"data"
}
}
실패
json
// 실패일 경우 resultType 이 FAIL로 설정되며 해당 실패 사유가 error 하위에 적재됩니다.
{
"resultType":"FAIL",
"error":{
"errorCode":"INVALID_PARAMETER",
"reason":"요청에 실패했습니다."
}
}
개발하기
1. 인가 코드 받기
SDK를 통해 연동해주세요.
사용자의 인증을 요청하고, 사용자가 인증에 성공하면 인가 코드를 메소드 응답으로 전달드려요.appLogin
함수를 사용해서 인가 코드(authorizationCode
)와 referrer
를 받을 수 있어요.
- 샌드박스앱에서는
referrer
가sandbox
가 반환돼요 - 토스앱에서는
referrer
가DEFAULT
가 반환돼요
🚨 잠시만요
인가코드의 유효시간은 10분입니다.
토스 로그인을 처음 진행할 때appLogin
함수를 호출하면 토스 로그인 창이 열리고, 앱인토스 콘솔에서 등록한 약관 동의 화면이 노출되요.
사용자가 필수 약관에 동의하면 인가 코드가 반환돼요.
토스 로그인을 이미 진행했을 때appLogin
함수를 호출하면 별도의 로그인 창 없이 바로 인가 코드가 반환돼요.
tsx
import { useCallback } from "react";
import { Button } from "react-native";
import { appLogin } from "@apps-in-toss/framework";
function Page() {
const handlePress = useCallback(async () => {
/**
* appLogin을 호출하면
* - 토스 로그인을 처음 진행하는 토스 로그인 창이 열리고, 앱인토스 콘솔에서 등록한 약관 동의 화면이 표시돼요. 사용자가 필수 약관에 동의하면 인가 코드가 반환돼요.
* - 토스 로그인을 이미 진행한 경우 별도의 로그인 창 없이 바로 인가 코드가 반환돼요.
*/
const { authorizationCode, referrer } = await appLogin();
/**
* 획득한 authorizationCode 와 referrer 값을 서버로 전달해요.
*/
}, []);
return <Button label="회원가입" onPress={handlePress} />;
}
2. AccessToken 받기
사용자 정보 조회 API 를 사용하기 위한 접근 토큰을 발급합니다.
- Content-type : application/json
- Method :
POST
- URL :
/api-partner/v1/apps-in-toss/user/oauth2/generate-token
🚨 잠시만요
AccessToken 유효시간은 1시간이에요.
요청
이름 | 타입 | 필수값 여부 | 설명 |
---|---|---|---|
authorizationCode | string | Y | 인가코드 |
referrer | string | Y | referrer |
성공 응답
이름 | 타입 | 필수값 여부 | 설명 |
---|---|---|---|
tokenType | string | Y | bearer 로 고정 |
accessToken | string | Y | accessToken |
refreshToken | string | Y | refreshToken |
expiresIn | string | Y | 만료시간(초) |
scope | string | Y | 인가된 scope(구분) |
json
{
"resultType":"SUCCESS",
"success":{
"accessToken":"eyJraWQiOiJjZXJ0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJtMHVmMmhaUmpJTnNEQTdLNHVuVHhMb3IwcWNSa2JNPSIsImF1ZCI6IjNlenQ2ZTF0aDg2b2RheTlwOWN1eTg0dTRvdm5nNnNzIiwibmJmIjoxNzE4MjU0ODM2LCJzY29wZSI6WyJ1c2VyX2NpIiwidXNlcl9iaXJ0aGRheSIsInVzZXJfbmF0aW9uYWxpdHkiLCJ1c2VyX25hbWUiLCJ1c2VyX3Bob25lIiwidXNlcl9nZW5kZXIiXSwiaXNzIjoiaHR0cHM6Ly9jZXJ0LnRvc3MuaW0iLCJleHAiOjE3MTgyNTg0MzYsImlhdCI6MTcxODI1NDgzNiwianRpIjoiMTJkYjYwZjYtMjEzYS00NWQ3LTllOTItODBjMzBdseY2JkMGQ3In0.W1cjoeMN8pd3Jqgh6h8YzSVQ1PUNldulJJgy6bgH1AoDbv5xFTlBLzz9Slb_u52zUpyZbhglwblQmNJs7GT6-us7XtfxSGxTUY3ORqIhF_PPGQ6soi_Qgsi-hmX165CCAilf8cltSTTuTt8xOiEbLuSTY-cecxo7SkPUonQ_0v4_Ik0kwOiOBuYZyuch3KmlYQZTqsJmxlwJAPB8M9tZTtDpLOv9MEPU35YS7CZyN0l7lwn1EKrDHJdzA5CnstqEdz2I0eREmMgZoG9mSEybgD4NtPmVJos6AJerUGgSmzP_TwwlybVATuGpnAUmH1idaZJ-MHZJhUhR82z4zTn3bw",
"refreshToken":"xNEYPASwWw0n1AxZUHU9KeGj8BitDyYo4wi8rpfkUcJwByVxpAdUzwtIaWGVL6vHdrXLCxIlHAQRPF9hHnFleTsHkqUXzc-_78sD_r1Uh5Ff9UCYfArx8LTn1Vk99dDb",
"scope":"user_ci user_birthday user_nationality user_name user_phone user_gender",
"tokenType":"Bearer",
"expiresIn":3599
}
}
실패 응답
인가 코드가 만료되었거나 동일한 인가 코드로 AccessToken 을 중복으로 요청할 경우
json
{
"error":"invalid_grant"
}
json
{
"resultType":"FAIL",
"error":{
"errorCode":"INTERNAL_ERROR",
"reason":"요청을 처리하는 도중에 문제가 발생했습니다."
}
}
3. AccessToken 재발급 받기
사용자 정보 조회 API 를 사용하기 위한 접근 토큰을 재발급합니다.
- Content-type : application/json
- Method :
POST
- URL :
/api-partner/v1/apps-in-toss/user/oauth2/refresh-token
🚨 잠시만요
refreshToken 유효시간은 14일이에요.
요청
이름 | 타입 | 필수값 여부 | 설명 |
---|---|---|---|
refreshToken | string | Y | 획득한 RefreshToken |
성공 응답
이름 | 타입 | 필수값 여부 | 설명 |
---|---|---|---|
tokenType | string | Y | bearer 로 고정 |
accessToken | string | Y | accessToken |
refreshToken | string | Y | refreshToken |
expiresIn | string | Y | 만료시간(초) |
scope | string | Y | 인가된 scope(구분) |
실패 응답
이름 | 타입 | 필수값 여부 | 설명 |
---|---|---|---|
errorCode | string | Y | 에러 코드 |
reason | string | Y | 에러 메시지 |
4. 사용자 정보 받기
사용자 정보를 조회합니다.
DI는 null 로 내려가며 횟수 제한없이 호출이 가능해요.
개인정보 보호를 위해 암호화된 형태로 제공합니다.
- Content-type : application/json
- Method :
GET
- URL :
/api-partner/v1/apps-in-toss/user/oauth2/login-me
요청 헤더
이름 | 타입 | 필수값 여부 | 설명 |
---|---|---|---|
Authorization | string | Y | AccessToken으로 인증 요청 Authorization: Bearer ${AccessToken} |
성공 응답
이름 | 타입 | 필수값 여부 | 암호화 여부 | 설명 |
---|---|---|---|---|
userKey | number | Y | N | 사용자 식별자 |
scope | string | Y | N | 인가된 scope(구분) |
agreedTerms | list | Y | N | 사용자가 동의한 약관 목록 |
policy | string | Y | N | OAuth 로그인 연동 동선 구분 = Login 으로 값 고정 필요 |
ci | string | N | Y | 사용자 CI |
name | string | N | Y | 사용자 이름 |
phone | string | N | Y | 사용자 휴대전화번호 |
gender | string | N | Y | 사용자 성별(MALE/FEMALE) |
nationality | string | N | Y | 사용자 내/외국인 여부(LOCAL/FOREIGNER) |
birthday | string | N | Y | 사용자 생년월일(yyyyMMdd) |
string | N | Y | 사용자 이메일(점유인증 하지 않은 이메일 정보) |
json
{
"resultType":"SUCCESS",
"success":{
"userKey":443731104,
"scope":"user_ci,user_birthday,user_nationality,user_name,user_phone,user_gender",
"agreedTerms":["terms_tag1","terms_tag2"],
"policy":"AUTO_SELECT",
"certTxId":"ad052b57-dc8f-4cdb-a6e2-7b494e28b5ec",
"name":"ENCRYPTED_VALUE",
"phone":"ENCRYPTED_VALUE",
"birthday":"ENCRYPTED_VALUE",
"ci":"ENCRYPTED_VALUE",
"di":null,
"gender":"ENCRYPTED_VALUE",
"nationality":"ENCRYPTED_VALUE",
"email":null
}
}
실패 응답
유효하지 않은 토큰을 사용할 경우, 현재 사용 중인 access_token의 유효시간을 확인하고 재발급을 진행해주세요.
json
{
"error":"invalid_grant"
}
서버 에러 응답 예시
errorCode | 설명 |
---|---|
INTERNAL_ERROR | 내부 서버 에러 |
USER_KEY_NOT_FOUND | 로그인 서비스에 접속한 유저 키 값을 찾을 수 없음 |
USER_NOT_FOUND | 토스 유저 정보를 찾을 수 없음 |
BAD_REQUEST_RETRIEVE_CERT_RESULT_EXCEEDED_LIMIT | 조회 가능 횟수 초과 동일한 토큰으로 /api/login/user/me/without-di API 조회하면 정상적으로 조회되나, di 필드는 null 값으로 내려감 |
json
{
"resultType":"FAIL",
"error":{
"errorCode":"INTERNAL_ERROR",
"reason":"요청을 처리하는 도중에 문제가 발생했습니다."
}
}
5. 사용자 정보 복호화하기
콘솔을 통해 이메일로 받은 복호화 키
와 AAD(Additional Authenticated DATA)
로 진행해주세요.
암호화 알고리즘
- AES 대칭키 암호화
- 키 길이 : 256비트
- 모드 : GCM
- AAD : 복호화 키와 함께 이메일로 전달드립니다.
데이터 교환방식
- 암호화된 데이터의 앞 부분에 IV/NONCE 값을 첨부하여 드립니다.
- 복호화할 때는 암호화된 데이터의 앞 부분에서 IV/NONCE 값을 추출하여 사용하시길 바랍니다.
복호화 샘플 코드
Kotlin 예제
kotlin
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
class Test {
fun decrypt(
encryptedText: String,
base64EncodedAesKey: String,
add: String,
): String {
val IV_LENGTH = 12
val decoded = Base64.getDecoder().decode(encryptedText)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val keyByteArray = Base64.getDecoder().decode(base64EncodedAesKey)
val key = SecretKeySpec(keyByteArray, "AES")
val iv = decoded.copyOfRange(0, IV_LENGTH)
val nonceSpec = GCMParameterSpec(16 * Byte.SIZE_BITS, iv)
cipher.init(Cipher.DECRYPT_MODE, key, nonceSpec)
cipher.updateAAD(add.toByteArray())
return String(cipher.doFinal(decoded, IV_LENGTH, decoded.size - IV_LENGTH))
}
}
PHP 예제
php
<?php
class Test {
public function decrypt($encryptedText, $base64EncodedAesKey, $add) {
$IV_LENGTH = 12;
$decoded = base64_decode($encryptedText);
$keyByteArray = base64_decode($base64EncodedAesKey);
$iv = substr($decoded, 0, $IV_LENGTH);
$ciphertext = substr($decoded, $IV_LENGTH);
$tag = substr($ciphertext, -16);
$ciphertext = substr($ciphertext, 0, -16);
$decrypted = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$keyByteArray,
OPENSSL_RAW_DATA,
$iv,
$tag,
$add
);
return $decrypted;
}
}
// 사용 예제
$test = new Test();
$encryptedText = "Encrypted Text"; // Encrypted Text 입력
$base64EncodedAesKey = "Key"; // Key 입력
$add = "TOSS";
$result = $test->decrypt($encryptedText, $base64EncodedAesKey, $add);
echo $result;
?>
JAVA 예제
java
public class Test {
public String decrypt(
String encryptedText,
String base64EncodedAesKey,
String add
) throws Exception {
final int IV_LENGTH = 12;
byte[] decoded = Base64.getDecoder().decode(encryptedText);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] keyByteArray = Base64.getDecoder().decode(base64EncodedAesKey);
SecretKeySpec key = new SecretKeySpec(keyByteArray, "AES");
byte[] iv = new byte[IV_LENGTH];
System.arraycopy(decoded, 0, iv, 0, IV_LENGTH);
GCMParameterSpec nonceSpec = new GCMParameterSpec(16 * Byte.SIZE, iv);
cipher.init(Cipher.DECRYPT_MODE, key, nonceSpec);
cipher.updateAAD(add.getBytes());
byte[] decrypted = cipher.doFinal(decoded, IV_LENGTH, decoded.length - IV_LENGTH);
return new String(decrypted);
}
}
6. 로그인 끊기
발급받은 AccessToken을 더 이상 사용하지 않거나 사용자의 요구에 의해 만료시킬 경우 토큰을 삭제(만료)해주세요.
- Content-type : application/json
- Method :
POST
- URL :
- accessToken 으로 연결 끊기 :
/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-access-token
- userKey 로 연결 끊기 :
/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-user-key
- accessToken 으로 연결 끊기 :
AccessToken 으로 로그인 연결 끊기
// 포맷
curl --request POST 'https://apps-in-toss-api.toss.im/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-access-token' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $access_token'
// 예시
curl --request POST 'https://apps-in-toss-api.toss.im/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-access-token' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJraWQiOiJjZXJ0IizzYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJtMHVmMmhaUmpJTnNEQTdLNHVuVHhMb3IwcWNSa2JNPSIsImF1ZCI6IjNlenQ2ZTF0aDg2b2RheTlwOWN1eTg0dTRvdm5nNnNzIiwibmJmIjoxNzE4MjU0ODM2LCJzY29wZSI6WyJ1c2VyX2NpIiwidXNlcl9iaXJ0aGRheSIsInVzZXJfbmF0aW9uYWxpdHkiLCJ1c2VyX25hbWUiLCJ1c2VyX3Bob25lIiwidXNlcl9nZW5kZXIiXSwiaXNzIjoiaHR0cHM6Ly9jZXJ0LnRvc3MuaW0iLCJleHAiOjE3MTgyNTg0MzYsImlhdCI6MTcxODI1NDgzNiwianRpIjoiMTJkYjYwZjYtMjEzYS00NWQ3LTllOTItODBjMzBmY2JkMGQ3In0.W1cjoeMN8pd3Jqgh6h8YzSVQ1PUNldulJJgy6bgH1AoDbv5xFTlBLwk9Slb_u52zUpyZbhglwblQmNJs7GT6-us7XtfxSGxTUY3ORqIhF_PPGQ6soi_Qgsi-hmX165CCAilf8cltSTTuTt8xOiEbLuSTY-cecxo7SkPUonQ_0v4_Ik0kwOiOBuYZyuch3KmlYQZTqsJmxlwJAPB8M9tZTtDpLOv9MEPU35YS7CZyN0l7lwn1EKrDHJdzA5CnstqEdz2I0eREmMgZoG9mSEybgD4NtPmVJos6AJerUGgSmzP_TwwlybVATuGpnAUmH1idaZJ-MHZJhUhR82z4zTn3bw'
userKey 로 로그인 연결 끊기
🚨 잠시만요
userKey에 맵핑된 AccessToken이 많을 경우 readTimeout 이 발생할 수 있어요
readTimeout 은 3초 입니다
// 포맷
curl --request POST 'https://apps-in-toss-api.toss.im/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-user-key' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $access_token' \
--data '{"userKey": $user_key}'
// 예시
curl --request POST 'https://apps-in-toss-api.toss.im/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-user-key' \
--header 'Content-Type: application/json' \
--data '{"userKey": 443731103}'
json
{
"resultType": "SUCCESS",
"success": {
"userKey": 443731103
}
}
7. 콜백을 통해 로그인 끊기
사용자가 토스앱 내에서 서비스와의 연결을 해제한 경우 가맹점 서버로 알려드려요.
서비스에서 연결이 끊긴 사용자에 대한 처리가 필요한 경우 활용할 수 있어요. 콜백을 받을 URL과 basic Auth 헤더는 콘솔에서 입력할 수 있어요.
서비스에서 직접 로그인 연결 끊기 요청을 호출한 경우 콜백이 호출되지 않아요.
GET 방식
- 요청 requestParam에
userKey
와referrer
을 포함합니다.
// 포맷
curl --request GET '$callback_url?userKey=$userKey&referrer=$referrer'
// 예시
curl --request GET '$callback_url?userKey=443731103&referrer=UNLINK'
POST 방식
- 요청 body에
userKey
와referrer
을 포함합니다.
// 포맷
curl --request POST '$callback_url' \
--header 'Content-Type: application/json' \
--data '{"userKey": $user_key, "referrer": $referrer}'
// 예시
curl --request POST '$callback_url' \
--header 'Content-Type: application/json' \
--data '{"userKey": 443731103, "referrer": "UNLINK"}'
referrer 은 연결 끊기 요청 경로에요.
referrer | 설명 |
---|---|
UNLINK | 사용자가 앱에서 연결 끊기 |
WITHDRAWAL_TERMS | 로그인 서비스 약관 철회 |
WITHDRAWAL_TOSS | 토스 회원 탈퇴 |
트러블슈팅
로컬 개발 중 인증 에러가 발생할 때
로컬에서 개발할 때 인증 에러가 발생하는 원인은 주로 두가지예요.
인증 토큰이 만료됨
기존에 발급받은 인증 토큰이 만료되었을 수 있어요. 새로운 토큰을 발급받아 다시 시도해보세요.개발자 로그인이 되지 않음 샌드박스 환경에서 개발자 계정으로 로그인하지 않은 상태일 수 있어요. 여기를 참고해 로그인을 진행한 뒤 다시 시도해보세요.