> 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, please refer to [Toss Login Introduction Document](https://developers-apps-in-toss.toss.im/guide/authentication/intro#undefined-4).

### 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 from the partner server to the Apps in Toss server. For security, configure an mTLS certificate on the server before calling it. How to issue the certificate is [mTLS certificate issuance method](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 Toss app authentication flow to log in, and when login succeeds, an authorization code (`authorizationCode`) to release resources.

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

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

**When you first proceed with Toss Login** `appLogin` when you call the function, the Toss login window opens and the terms consent screen registered in the Apps in Toss console is displayed. When the user agrees to the required terms, an authorization code is returned.

**When Toss Login has already been completed** `appLogin` when 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 which environment the login request occurred in. DEFAULT: real Toss app environment, SANDBOX: sandbox environment

**Example: an 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 example 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. Get AccessToken

For calling the user information lookup API, **an access token is issued.**

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

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

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

**Request**

| Name              | Type   | Required value 여부 | Description        |
| ----------------- | ------ | ----------------- | ------------------ |
| authorizationCode | string | Y                 | authorization code |
| referrer          | string | Y                 | referrer           |

**Success response**

| Name         | Type   | Required value 여부 | Description                  |
| ------------ | ------ | ----------------- | ---------------------------- |
| tokenType    | string | Y                 | fixed to bearer              |
| accessToken  | string | Y                 | accessToken                  |
| refreshToken | string | Y                 | refreshToken                 |
| expiresIn    | string | Y                 | expiration time (seconds)    |
| scope        | string | Y                 | authorized scope (separator) |

```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 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. Reissue AccessToken

Reissue the 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" %}
**Note**

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

**Request**

| Name         | Type   | Required value 여부 | Description         |
| ------------ | ------ | ----------------- | ------------------- |
| refreshToken | string | Y                 | Issued RefreshToken |

**Success response**

| Name         | Type   | Required value 여부 | Description                  |
| ------------ | ------ | ----------------- | ---------------------------- |
| tokenType    | string | Y                 | fixed to bearer              |
| accessToken  | string | Y                 | accessToken                  |
| refreshToken | string | Y                 | refreshToken                 |
| expiresIn    | string | Y                 | expiration time (seconds)    |
| scope        | string | Y                 | authorized scope (separator) |

**Failure response**

| Name      | Type   | Required value 여부 | Description   |
| --------- | ------ | ----------------- | ------------- |
| errorCode | string | Y                 | Error code    |
| reason    | string | Y                 | Error message |

### 4. Get user information

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

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

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

`scope` The parameter is **Only the values the user agreed to among the items selected in the console** are returned. **Starting January 2, 2026 `scope` to the value `user_key` an item is added.** Due to the addition of new scopes **values not previously defined may be included, so** please be careful to avoid exceptions when handling scope.
{% endhint %}

**Request headers**

| Name          | Type   | Required value 여부 | Description                                                                   |
| ------------- | ------ | ----------------- | ----------------------------------------------------------------------------- |
| Authorization | string | Y                 | Authenticate requests with AccessToken `Authorization: Bearer ${AccessToken}` |

**Success response**

| Name        | Type   | Required value 여부 | Whether encrypted | Description                                                                                                                                      |
| ----------- | ------ | ----------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| userKey     | number | Y                 | N                 | A unique user identifier usable only in that app. Even for the same user, the userKey may differ if the app is different.                        |
| scope       | string | Y                 | N                 | This is the list of authorized scopes. Among the items selected in the console, the values the user agreed to and `user_key` items are included. |
| 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 the user's local/foreigner status. (LOCAL/FOREIGNER)                                                                                     |
| email       | string | N                 | Y                 | This is the user's email information. It is a value that has not been ownership-verified.                                                        |

{% hint style="info" %}
**userKey is issued per app**

userKey is an identifier valid only in that app. Even for the same user, different userKeys are issued if the app differs.
{% 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 currently used access\_token and reissue it.

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

**Example server error responses**

| errorCode                                             | Description                                                                                                                                                                       |
| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| INTERNAL\_ERROR                                       | Internal server error                                                                                                                                                             |
| USER\_KEY\_NOT\_FOUND                                 | Could not find the user key value connected to the login service                                                                                                                  |
| USER\_NOT\_FOUND                                      | Could not find Toss user information                                                                                                                                              |
| BAD\_REQUEST\_RETRIEVE\_CERT\_RESULT\_EXCEEDED\_LIMIT | Exceeded the number of available lookups with the same token `/api/login/user/me/without-di` When querying the API, it is retrieved 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: sent by email together with the decryption key.

**Data exchange method**

* The front part of the encrypted data contains the IV (NONCE).
* When decrypting, you must extract the IV from the ciphertext and use it to decrypt correctly.

**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 by accessToken: `/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-access-token`
  * Disconnect by userKey: `/api-partner/v1/apps-in-toss/user/oauth2/access/remove-by-user-key`

**Disconnect login 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 by userKey**

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

If there are many AccessTokens linked 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 through callback

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

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

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

**GET method**

* to the request requestParam `userKey`and `referrer`is included.

```
// 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`is included.

```
// 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`           | 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` | Called when the user withdraws consent to the login service terms. (Path: Toss app → Settings → Legal information and others → Terms and privacy consent → Service-specific consent details: "Toss Login" → 'Withdraw consent') |
| `WITHDRAWAL_TOSS`  | Called when the user withdraws from Toss membership.                                                                                                                                                                            |

### Troubleshooting

**When authentication errors occur during local development**

There are usually two main causes of authentication errors when developing locally.

1. Authentication token expired The previously issued authentication token may have expired. Get a new token and try again.
2. Developer login not available You may not be logged in with a developer account in the sandbox environment. Please refer to the sandbox app download, log in, and 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.
