> For the complete documentation index, see [llms.txt](https://developers-apps-in-toss.toss.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-en/common/authentication/toss-login.md).

# Toss Login

For service introduction and console setup instructions, [Toss Login introduction document](https://developers-apps-in-toss.toss.im/guide/authentication/intro#undefined-4)please refer to.

### Basic information

| Item                  | Value                              |
| --------------------- | ---------------------------------- |
| Base URL              | `https://apps-in-toss-api.toss.im` |
| Server authentication | mTLS (client certificate)          |
| Content-Type          | `application/json`                 |

{% hint style="info" %}
**mTLS certificate is required for server-to-server communication**

The Toss Login API is server-to-server communication called from the partner server to the Apps in Toss server. For security, set up an mTLS certificate on the server before making calls. How to issue a certificate is [How to issue an mTLS certificate](https://developers-apps-in-toss.toss.im/documentation/integration/getting-started)please refer to.
{% endhint %}

***

### 1. Get an authorization code

**SDK function:** `appLogin`

`appLogin`uses the authentication flow of the Toss app to perform login, and when login succeeds, an authorization code (`authorizationCode`).

{% hint style="info" %}
**Please note**

* This step **client (mini app)** only performs the role of obtaining the authorization code.
* After receiving the authorization code, **token exchange / AccessToken issuance / user information lookup**must be handled **on the server**.
* The validity period of the authorization code is **10 minutes**.
* The authorization code is **one-time**, and reuse will fail.
* `authorizationCode`Do not store it on the client for a long time.
* Sensitive information (`AccessToken`, `RefreshToken` ) such as these should be **stored safely on the server**.
  {% endhint %}

**When you perform Toss Login for the first time** `appLogin` If you call the function, the Toss Login window opens and the terms consent screen registered in the Apps in Toss console is shown. If the user agrees to the required terms, an authorization code is returned.

**If Toss Login has already been performed** `appLogin` If you call the function, an authorization code is returned immediately without a separate login window.

**Signature**

```typescript
function appLogin(): Promise<{
  authorizationCode: string;
  referrer: 'DEFAULT' | 'SANDBOX';
}>;
```

**Return value**

* authorizationCode string

  This is the authorization code issued after user authentication is completed. Send it to the server and exchange it for an AccessToken.
* referrer string

  Indicates the environment from which the login request occurred. DEFAULT: real Toss app environment, SANDBOX: sandbox environment

**Example: Example of logging in through Toss authentication**

{% tabs %}
{% tab title="js" %}

```js
import { appLogin } from '@apps-in-toss/web-framework';

async function handleLogin() {
  const { authorizationCode, referrer } = await appLogin();

  // Send the obtained authorization code (`authorizationCode`) and `referrer` to the server.
}
```

{% endtab %}

{% tab title="React" %}

```tsx
import { appLogin } from '@apps-in-toss/web-framework';
import { Button } from '@toss/tds-mobile';

function Page() {
  async function handleLogin() {
    const { authorizationCode, referrer } = await appLogin();

    // Send the obtained authorization code (`authorizationCode`) and `referrer` to the server.
  }

  return (
    <Button size="medium" onClick={handleLogin}>
      Login
    </Button>
  );
}
```

{% endtab %}

{% tab title="React Native" %}

```tsx
import { appLogin } from '@apps-in-toss/framework';
import { Button } from '@toss/tds-react-native';

function Page() {
  async function handleLogin() {
    const { authorizationCode, referrer } = await appLogin();

    // Send the obtained authorization code (`authorizationCode`) and `referrer` to the server.
  }

  return <Button onPress={handleLogin}>Login</Button>;
}
```

{% endtab %}
{% endtabs %}

**Try the sample app**

[apps-in-toss-examples](https://github.com/toss/apps-in-toss-examples) from the repository [with-app-login](https://github.com/toss/apps-in-toss-examples/tree/main/with-app-login) Download the code and try it out.

***

### 2. Receive AccessToken

For calling the user information lookup API **issue an access token.**

* Content-Type: `application/json`
* Method: `POST`
* URL: `/api-partner/v1/apps-in-toss/user/oauth2/generate-token`

{% hint style="info" %}
**See**

The validity period of AccessToken is 1 hour.
{% endhint %}

**Request**

| Name              | Type   | Whether required | Description        |
| ----------------- | ------ | ---------------- | ------------------ |
| authorizationCode | string | Y                | Authorization code |
| referrer          | string | Y                | referrer           |

**Success response**

| Name         | Type   | Whether required | Description                  |
| ------------ | ------ | ---------------- | ---------------------------- |
| tokenType    | string | Y                | fixed as bearer              |
| accessToken  | string | Y                | accessToken                  |
| refreshToken | string | Y                | refreshToken                 |
| expiresIn    | string | Y                | expiration time (seconds)    |
| scope        | string | Y                | authorized scope (delimiter) |

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

**Failure response** If the authorization code has expired or if you request AccessToken multiple times with the same authorization code

```json
{
  "error": "invalid_grant"
}
```

```json
{
  "resultType": "FAIL",
  "error": {
    "errorCode": "INTERNAL_ERROR",
    "reason": "A problem occurred while processing the request."
  }
}
```

### 3. Receive AccessToken again

Reissue an access token for calling the user information lookup API.

* Content-type : application/json
* Method : `POST`
* URL : `/api-partner/v1/apps-in-toss/user/oauth2/refresh-token`

{% hint style="info" %}
**See**

The refreshToken validity period is 14 days.
{% endhint %}

**Request**

| Name         | Type   | Whether required | Description         |
| ------------ | ------ | ---------------- | ------------------- |
| refreshToken | string | Y                | Issued RefreshToken |

**Success response**

| Name         | Type   | Whether required | Description                  |
| ------------ | ------ | ---------------- | ---------------------------- |
| tokenType    | string | Y                | fixed as bearer              |
| accessToken  | string | Y                | accessToken                  |
| refreshToken | string | Y                | refreshToken                 |
| expiresIn    | string | Y                | expiration time (seconds)    |
| scope        | string | Y                | authorized scope (delimiter) |

**Failure response**

| Name      | Type   | Whether required | Description   |
| --------- | ------ | ---------------- | ------------- |
| errorCode | string | Y                | Error code    |
| reason    | string | Y                | Error message |

### 4. Receive user information

Look up user information. `DI`is `null`is returned, and can be called without any limit on the number of times. For privacy protection, all personal information is **encrypted form**provided in.

* Content-type : application/json
* Method : `GET`
* URL : `/api-partner/v1/apps-in-toss/user/oauth2/login-me`

{% hint style="info" %}
**`scope` to `user_key` will be added**

`scope` The parameter is **Among the items selected in the console, only the values the user has consented to** are returned. **From January 2, 2026 `scope` to the values, `user_key` items will be added.** Due to the addition of new scopes, **values that were not previously defined may be included,** so please be careful to avoid exceptions when handling scope.
{% endhint %}

**Request headers**

| Name          | Type   | Whether required | Description                                                                    |
| ------------- | ------ | ---------------- | ------------------------------------------------------------------------------ |
| Authorization | string | Y                | Authentication request with AccessToken `Authorization: Bearer ${AccessToken}` |

**Success response**

| Name        | Type   | Whether required | Whether encrypted | Description                                                                                                                                                    |
| ----------- | ------ | ---------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| userKey     | number | Y                | N                 | This is a unique user identification value that can be used only in the corresponding app. Even for the same user, userKey may differ if the app is different. |
| scope       | string | Y                | N                 | This is the list of authorized scopes. It includes the values the user consented to among the items selected in the console and `user_key` items.              |
| agreedTerms | list   | Y                | N                 | This is the list of terms the user agreed to.                                                                                                                  |
| name        | string | N                | Y                 | This is the user's name.                                                                                                                                       |
| phone       | string | N                | Y                 | This is the user's mobile phone number.                                                                                                                        |
| birthday    | string | N                | Y                 | This is the user's date of birth. (yyyyMMdd)                                                                                                                   |
| ci          | string | N                | Y                 | This is the user's CI value.                                                                                                                                   |
| di          | string | N                | Y                 | Always `null` is returned as a value.                                                                                                                          |
| gender      | string | N                | Y                 | This is the user's gender information. (MALE/FEMALE)                                                                                                           |
| nationality | string | N                | Y                 | This is whether the user is local or foreign. (LOCAL/FOREIGNER)                                                                                                |
| email       | string | N                | Y                 | This is the user's email information. It is not a verified value.                                                                                              |

{% hint style="info" %}
**userKey is issued on an app basis**

userKey is an identifier valid only in the corresponding app. Even for the same user, if the app differs, different userKeys are issued.
{% endhint %}

```json
{
  "resultType": "SUCCESS",
  "success": {
    "userKey": 443731104,
    "scope": "user_ci,user_birthday,user_nationality,user_name,user_phone,user_gender, user_key",
    "agreedTerms": ["terms_tag1", "terms_tag2"],
    "name": "ENCRYPTED_VALUE",
    "phone": "ENCRYPTED_VALUE",
    "birthday": "ENCRYPTED_VALUE",
    "ci": "ENCRYPTED_VALUE",
    "di": null,
    "gender": "ENCRYPTED_VALUE",
    "nationality": "ENCRYPTED_VALUE",
    "email": null
  }
}
```

**Failure response** If you use an invalid token, check the validity period of the current access\_token and reissue it.

```json
{
  "error": "invalid_grant"
}
```

**Server error response example**

| errorCode                                             | Description                                                                                                                                                                      |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| INTERNAL\_ERROR                                       | Internal server error                                                                                                                                                            |
| USER\_KEY\_NOT\_FOUND                                 | Unable to find the user key value connected to the login service                                                                                                                 |
| USER\_NOT\_FOUND                                      | Unable to find Toss user information                                                                                                                                             |
| BAD\_REQUEST\_RETRIEVE\_CERT\_RESULT\_EXCEEDED\_LIMIT | Exceeded the number of queries available with the same token `/api/login/user/me/without-di` When querying the API, it is returned normally, but the di field comes back as null |

```json
{
  "resultType": "FAIL",
  "error": {
    "errorCode": "INTERNAL_ERROR",
    "reason": "A problem occurred while processing the request."
  }
}
```

### 5. Decrypt user information

Received by email through the console `decryption key`and `AAD (Additional Authenticated DATA)` please proceed with.

**Encryption algorithm**

* AES symmetric-key encryption
* Key length: 256 bits
* Mode: GCM
* AAD: We send it by email together with the decryption key.

**Data exchange method**

* The front part of the encrypted data includes the IV (NONCE).
* For decryption, you must extract the IV from the ciphertext and use it for successful decryption.

**Decryption sample code**

<details>

<summary>Kotlin example</summary>

```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))
    }
}
```

</details>

<details>

<summary>PHP example</summary>

```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;
    }
}


// Usage example
$test = new Test();
$encryptedText = "Encrypted Text"; // Enter encrypted text
$base64EncodedAesKey = "Key"; // Enter key
$add = "TOSS";

$result = $test->decrypt($encryptedText, $base64EncodedAesKey, $add);
echo $result;

?>
```

</details>

<details>

<summary>JAVA example</summary>

```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);
    }
}
```

</details>

### 6. Disconnect login

If you no longer use the issued AccessToken or need to expire the token at the user's request, delete(expire) the token.

* Content-type : application/json
* Method : `POST`
* URL :
  * Disconnect with accessToken: `/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-access-token`
  * Disconnect with userKey: `/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-user-key`

**Disconnect login connection with AccessToken**

```
// Format
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'

// Example
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'
```

**Disconnect login connection with userKey**

{% hint style="info" %}
**See**

If there are many AccessTokens connected to one userKey **readTimeout (3 seconds)** may occur. In this case, do not retry the request; try again after some time.
{% endhint %}

```
// Format
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}'

// Example
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. Disconnect login via callback

If the user disconnects from the service within the Toss app, we will inform the merchant server. You can use this when the service needs to handle users whose connection has been disconnected. The URL to receive the callback and the basic Auth header can be entered in the console.

{% hint style="info" %}
**Please be sure to check**

If the service directly calls the disconnect login API, **the callback is not called.**
{% endhint %}

**GET method**

* In the request requestParam `userKey`and `referrer`include it.

```
// Format
curl --request GET '$callback_url?userKey=$userKey&referrer=$referrer'

// Example
curl --request GET '$callback_url?userKey=443731103&referrer=UNLINK'
```

**POST method**

* in the request body `userKey`and `referrer`include it.

```
// Format
curl --request POST '$callback_url' \
--header 'Content-Type: application/json' \
--data '{"userKey": $user_key, "referrer": $referrer}'

// Example
curl --request POST '$callback_url' \
--header 'Content-Type: application/json' \
--data '{"userKey": 443731103, "referrer": "UNLINK"}'
```

referrer is the disconnect request path.

| referrer           | Description                                                                                                                                                                                                                            |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `UNLINK`           | It is called when the user disconnects directly in the Toss app. (Path: Toss app → Settings → Authentication and Security → Services logged in with Toss → 'Disconnect')                                                               |
| `WITHDRAWAL_TERMS` | It is called when the user withdraws consent to the login service terms. (Path: Toss app → Settings → Legal information and others → Terms and privacy policy consent → Consent details by service: "Toss Login" → 'Withdraw consent') |
| `WITHDRAWAL_TOSS`  | It is called when the user withdraws from Toss membership.                                                                                                                                                                             |

### Troubleshooting

**When an authentication error occurs during local development**

There are mainly two reasons why authentication errors occur when developing locally.

1. Authentication token expired The previously issued authentication token may have expired. Issue a new token and try again.
2. Unable to log in as a developer You may not be logged in with a developer account in the sandbox environment. Refer to the sandbox app download, log in, and then try again.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-en/common/authentication/toss-login.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
