> ## Documentation Index
> Fetch the complete documentation index at: https://docs.baanx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Complete guide to user login, OTP verification, session management, and logout flows

## Overview

The authentication system provides secure user login with optional OTP (One-Time Password) for enhanced security. After successful authentication, users receive an access token valid for 6 hours that authorizes all authenticated API calls.

<Note>
  All authentication endpoints require the `x-client-key` header. For US environment routing, include `x-us-env: true` header or `region=us` query parameter.
</Note>

## Authentication Methods

The platform supports multiple authentication patterns:

<CardGroup cols={2}>
  <Card title="Standard Login" icon="key">
    Username/password authentication for direct API access
  </Card>

  <Card title="OTP-Enhanced Login" icon="shield">
    Multi-factor authentication with SMS verification codes
  </Card>

  <Card title="OAuth 2.0" icon="lock">
    Delegated authorization for third-party applications
  </Card>

  <Card title="Session Management" icon="clock">
    Token-based sessions with 6-hour expiration
  </Card>
</CardGroup>

## Login Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Client as App/Client
    participant API as Backend/API

    Note over User,API: Standard Login Flow (No OTP)
    User->>Client: Enter email & password
    Client->>API: POST /v1/auth/login<br/>{email, password}
    alt OTP Not Required
        API-->>Client: 200 OK<br/>{accessToken, isOtpRequired: false}
        Note over Client: Store accessToken<br/>Valid for 6 hours
        Client->>API: Authenticated API calls<br/>Authorization: Bearer {accessToken}
    else OTP Required (2FA Enabled)
        API-->>Client: 200 OK<br/>{userId, isOtpRequired: true, phoneNumber (masked)}
        Note over Client: Display OTP input form
        Client->>API: POST /v1/auth/login/otp<br/>{userId}
        API->>User: SMS with OTP code
        User->>Client: Enter OTP code
        Client->>API: POST /v1/auth/login<br/>{email, password, otpCode}
        API-->>Client: 200 OK<br/>{accessToken, isOtpRequired: false}
        Note over Client: Store accessToken<br/>Valid for 6 hours
        Client->>API: Authenticated API calls<br/>Authorization: Bearer {accessToken}
    end

    Note over Client,API: Access token is different from OAuth tokens<br/>For long-lived access (>6 hours), use OAuth flow
```

## Standard Login

Direct authentication using email and password credentials.

### Endpoint

```bash theme={null}
POST /v1/auth/login
```

**API Reference:** [`POST /v1/auth/login`](/api-reference/auth/login)

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/login \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "email": "user@example.com",
      "password": "SecurePassword123!"
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  const response = await fetch('https://dev.api.baanx.com/v1/auth/login', {
    method: 'POST',
    headers: {
      'x-client-key': 'YOUR_CLIENT_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email: 'user@example.com',
      password: 'SecurePassword123!'
    })
  });

  const data = await response.json();

  if (data.isOtpRequired) {
    console.log('OTP required for user:', data.userId);
  } else {
    console.log('Access token:', data.accessToken);
  }
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://dev.api.baanx.com/v1/auth/login',
      headers={
          'x-client-key': 'YOUR_CLIENT_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'email': 'user@example.com',
          'password': 'SecurePassword123!'
      }
  )

  data = response.json()

  if data.get('isOtpRequired'):
      print(f"OTP required for user: {data['userId']}")
  else:
      print(f"Access token: {data['accessToken']}")
  ```
</CodeGroup>

### Response (Standard Login)

When OTP is **not** required:

```json theme={null}
{
  "accessToken": "US_b6b9168a-bb56-4c6a-9c0d-4650ea74f5f9",
  "userId": "b6b9168c-bb56-4c6a-9c0d-4650ea74f5f9",
  "isOtpRequired": false,
  "phoneNumber": null,
  "phase": null,
  "verificationState": "VERIFIED",
  "isLinked": false
}
```

### Response (OTP Required)

When OTP **is** required:

```json theme={null}
{
  "accessToken": "US_b6b9168a-bb56-4c6a-9c0d-4650ea74f5f9",
  "userId": "b6b9168c-bb56-4c6a-9c0d-4650ea74f5f9",
  "isOtpRequired": true,
  "phoneNumber": "+445*****225",
  "phase": null,
  "verificationState": "VERIFIED",
  "isLinked": false
}
```

<Warning>
  When `isOtpRequired: true`, you must complete the OTP flow before the access token becomes valid for API calls. See the [OTP Authentication](#otp-authentication) section below.
</Warning>

### Response Fields

Each field in the login response provides critical information about the user's authentication state and onboarding progress:

| Field               | Type           | Description                                                                                                                                                                                            |
| ------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `accessToken`       | string \| null | Bearer token for authenticated API calls (valid 6 hours). **null** when user is onboarding or OTP is required                                                                                          |
| `userId`            | string         | Unique user identifier in UUID format. Always returned                                                                                                                                                 |
| `isOtpRequired`     | boolean        | Indicates if 2FA is enabled. When `true`, call `/v1/auth/login/otp` to send OTP, then retry login with `otpCode`                                                                                       |
| `phoneNumber`       | string \| null | Masked phone number (e.g., "+445\*\*\*\*\*225"). Only returned when `isOtpRequired` is `true`                                                                                                          |
| `phase`             | string \| null | User's onboarding progress. Non-null values (ACCOUNT, PHONE\_NUMBER, PERSONAL\_INFORMATION, PHYSICAL\_ADDRESS, MAILING\_ADDRESS) mean registration is incomplete. **null** when onboarding is complete |
| `verificationState` | string \| null | KYC verification status: UNVERIFIED (0), PENDING (1), VERIFIED (3), REJECTED (2)                                                                                                                       |
| `isLinked`          | boolean        | Indicates if your OAuth client already has permission to access this user's account                                                                                                                    |

<Accordion title="Detailed Field Explanations">
  **phase (string | null)**

  * Indicates where the user is in the onboarding process
  * If **null**: User has completed all registration steps
  * If **non-null**: User needs to complete registration at the indicated phase
    * `ACCOUNT`: Basic account creation step
    * `PHONE_NUMBER`: Phone verification step
    * `PERSONAL_INFORMATION`: Personal details collection
    * `PHYSICAL_ADDRESS`: Physical address information
    * `MAILING_ADDRESS`: Mailing address information

  **userId (string)**

  * User's unique identifier in UUID format
  * Consistent across all sessions and API calls
  * Use this to track user sessions and for OTP requests

  **isOtpRequired (boolean)**

  * When `true`: User has 2FA enabled and must verify with OTP code
  * When `false`: Login is complete, use the `accessToken` for API calls
  * OTP flow: Call `/v1/auth/login/otp` → User receives SMS → Retry `/login` with `otpCode`

  **phoneNumber (string | null)**

  * Only present when `isOtpRequired` is `true`
  * Masked for security (e.g., "+445\*\*\*\*\*225")
  * Shows where the OTP code will be sent
  * Display this to users so they know which device to check

  **accessToken (string | null)**

  * Bearer token for Authorization header: `Authorization: Bearer {token}`
  * Valid for 6 hours (21,600 seconds)
  * **null** in these scenarios:
    * User is still onboarding (`phase` is non-null)
    * OTP verification is required (`isOtpRequired` is `true`)
    * Initial login step before OTP completion
  * **non-null** when authentication is complete and user can access API

  **verificationState (string | null)**

  * Maps to KYC verification status:
    * `UNVERIFIED` (0): User has not yet been verified
    * `PENDING` (1): Verification is in progress
    * `VERIFIED` (3): User is successfully verified
    * `REJECTED` (2): Verification was rejected
  * Check this before allowing access to features requiring verified users

  **isLinked (boolean)**

  * Indicates OAuth connection status between your client and this user
  * `false`: Your OAuth client needs to complete OAuth flow for long-lived access
  * `true`: Your client already has OAuth tokens and can refresh them
  * Important for determining whether to initiate OAuth flow after login
</Accordion>

## Edge Cases and Response Scenarios

Understanding different response scenarios helps you handle all authentication states correctly:

<Tabs>
  <Tab title="User Still Onboarding">
    When a user hasn't completed registration, the response indicates their current phase:

    ```json theme={null}
    {
      "phase": "PHONE_NUMBER",
      "userId": "b6b9168c-bb56-4c6a-9c0d-4650ea74f5f9",
      "isOtpRequired": false,
      "phoneNumber": null,
      "accessToken": null,
      "verificationState": null,
      "isLinked": false
    }
    ```

    **What this means:**

    * User is stuck at the `PHONE_NUMBER` verification phase
    * No `accessToken` provided because onboarding is incomplete
    * `verificationState` is null because KYC hasn't started yet

    **What to do:**

    * Direct user to continue registration flow
    * Guide them to the phone verification step
    * Don't attempt to use the API until `phase` is `null`
  </Tab>

  <Tab title="OTP Required">
    When 2FA is enabled, the initial login response requires OTP verification:

    ```json theme={null}
    {
      "phase": null,
      "userId": "b6b9168c-bb56-4c6a-9c0d-4650ea74f5f9",
      "isOtpRequired": true,
      "phoneNumber": "+445*****225",
      "accessToken": null,
      "verificationState": "VERIFIED",
      "isLinked": false
    }
    ```

    **What this means:**

    * User has completed onboarding (`phase` is `null`)
    * 2FA is enabled (`isOtpRequired` is `true`)
    * OTP will be sent to the masked phone number
    * `accessToken` is null until OTP is verified

    **What to do:**

    1. Call [`POST /v1/auth/login/otp`](/api-reference/auth/login-otp) with the `userId`
    2. Display OTP input field to user
    3. Show the masked phone number so user knows where to check
    4. Retry [`POST /v1/auth/login`](/api-reference/auth/login) with the `otpCode` parameter

    ```javascript theme={null}
    // Step 1: Send OTP
    await fetch('/v1/auth/login/otp', {
      method: 'POST',
      body: JSON.stringify({ userId: response.userId })
    });

    // Step 2: Get OTP from user
    const otpCode = await promptUserForOtp();

    // Step 3: Complete login
    const finalResponse = await fetch('/v1/auth/login', {
      method: 'POST',
      body: JSON.stringify({
        email: email,
        password: password,
        otpCode: otpCode
      })
    });
    ```
  </Tab>

  <Tab title="Successful Login">
    When authentication is complete without additional steps:

    ```json theme={null}
    {
      "phase": null,
      "userId": "b6b9168c-bb56-4c6a-9c0d-4650ea74f5f9",
      "isOtpRequired": false,
      "phoneNumber": null,
      "accessToken": "US_b6b9168a-bb56-4c6a-9c0d-4650ea74f5f9",
      "verificationState": "VERIFIED",
      "isLinked": false
    }
    ```

    **What this means:**

    * Onboarding complete (`phase` is `null`)
    * No 2FA required (`isOtpRequired` is `false`)
    * Valid `accessToken` provided
    * User is verified and ready to use the API

    **What to do:**

    * Store the `accessToken` securely
    * Use it in the `Authorization: Bearer {token}` header
    * Access token is valid for 6 hours
    * Consider initiating OAuth flow if `isLinked` is `false` and you need long-lived access
  </Tab>

  <Tab title="OAuth Linking Status">
    The `isLinked` field determines whether OAuth setup is needed:

    **When `isLinked: false`:**

    ```json theme={null}
    {
      "accessToken": "US_b6b9168a-bb56-4c6a-9c0d-4650ea74f5f9",
      "isLinked": false,
      "verificationState": "VERIFIED"
    }
    ```

    * Your OAuth client doesn't have permission yet
    * If you need long-lived access (beyond 6 hours), initiate OAuth flow
    * Use the access token for short-term API access
    * See [OAuth Flow Comparison](#login-access-token-vs-oauth-tokens) below

    **When `isLinked: true`:**

    ```json theme={null}
    {
      "accessToken": "US_b6b9168a-bb56-4c6a-9c0d-4650ea74f5f9",
      "isLinked": true,
      "verificationState": "VERIFIED"
    }
    ```

    * Your OAuth client already has permission
    * You have existing OAuth tokens that can be refreshed
    * No need to complete OAuth flow again
    * Use refresh token to get new access tokens without re-login
  </Tab>
</Tabs>

### Using the Access Token

After successful login, use the access token in the `Authorization` header for all authenticated API calls:

```bash theme={null}
curl -X GET https://dev.api.baanx.com/v1/user \
  -H "x-client-key: YOUR_CLIENT_KEY" \
  -H "Authorization: Bearer US_b6b9168a-bb56-4c6a-9c0d-4650ea74f5f9"
```

<Info>
  Access tokens expire after 6 hours. Implement token refresh logic or require re-authentication when tokens expire.
</Info>

### Common Errors

<AccordionGroup>
  <Accordion title="Invalid Credentials">
    ```json theme={null}
    {
      "message": "Invalid email or password"
    }
    ```

    **Resolution**: Verify credentials are correct. Implement password recovery if user has forgotten password.
  </Accordion>

  <Accordion title="Account Not Found">
    ```json theme={null}
    {
      "message": "User not found"
    }
    ```

    **Resolution**: User may need to complete registration first.
  </Accordion>

  <Accordion title="Account Locked">
    ```json theme={null}
    {
      "message": "Account is temporarily locked"
    }
    ```

    **Resolution**: Account may be locked due to multiple failed login attempts. User should contact support or wait for automatic unlock.
  </Accordion>
</AccordionGroup>

## OTP Authentication

For users with OTP enabled, an additional verification step is required after initial login.

### OTP Flow

```mermaid theme={null}
sequenceDiagram
    participant User
    participant App
    participant API

    User->>App: Submit email/password
    App->>API: POST /v1/auth/login
    API->>App: isOtpRequired: true, userId

    App->>API: POST /v1/auth/login/otp (userId)
    API->>User: SMS with OTP code

    User->>App: Enter OTP code
    App->>API: POST /v1/auth/login (with otpCode)
    API->>App: accessToken (valid)
```

### Step 1: Check OTP Requirement

After initial login, check the `isOtpRequired` field:

```javascript theme={null}
const loginResponse = await login(email, password);

if (loginResponse.isOtpRequired) {
  console.log('OTP required. Phone:', loginResponse.phoneNumber);
  await sendOtpCode(loginResponse.userId);
}
```

### Step 2: Send OTP Code

Request an OTP code to be sent via SMS to the user's registered phone number.

#### Endpoint

```bash theme={null}
POST /v1/auth/login/otp
```

**API Reference:** [`POST /v1/auth/login/otp`](/api-reference/auth/login-otp)

#### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/login/otp \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "userId": "b6b9168c-bb56-4c6a-9c0d-4650ea74f5f9"
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  async function sendOtpCode(userId: string): Promise<void> {
    const response = await fetch('https://dev.api.baanx.com/v1/auth/login/otp', {
      method: 'POST',
      headers: {
        'x-client-key': 'YOUR_CLIENT_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ userId })
    });

    if (!response.ok) {
      throw new Error('Failed to send OTP code');
    }

    const data = await response.json();
    console.log('OTP code sent:', data.success);
  }
  ```

  ```python Python theme={null}
  def send_otp_code(user_id: str) -> bool:
      response = requests.post(
          'https://dev.api.baanx.com/v1/auth/login/otp',
          headers={
              'x-client-key': 'YOUR_CLIENT_KEY',
              'Content-Type': 'application/json'
          },
          json={'userId': user_id}
      )

      response.raise_for_status()
      data = response.json()
      print(f"OTP code sent: {data['success']}")
      return data['success']
  ```
</CodeGroup>

#### Response

```json theme={null}
{
  "success": true
}
```

<Note>
  OTP codes are typically valid for 5-10 minutes. Users should enter the code promptly after receiving it.
</Note>

### Step 3: Submit OTP Code

Complete login by submitting the OTP code along with the original credentials.

#### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/login \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "email": "user@example.com",
      "password": "SecurePassword123!",
      "otpCode": "123456"
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  async function loginWithOtp(
    email: string,
    password: string,
    otpCode: string
  ): Promise<string> {
    const response = await fetch('https://dev.api.baanx.com/v1/auth/login', {
      method: 'POST',
      headers: {
        'x-client-key': 'YOUR_CLIENT_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        email,
        password,
        otpCode
      })
    });

    if (!response.ok) {
      throw new Error('OTP login failed');
    }

    const data = await response.json();
    return data.accessToken;
  }
  ```

  ```python Python theme={null}
  def login_with_otp(email: str, password: str, otp_code: str) -> str:
      response = requests.post(
          'https://dev.api.baanx.com/v1/auth/login',
          headers={
              'x-client-key': 'YOUR_CLIENT_KEY',
              'Content-Type': 'application/json'
          },
          json={
              'email': email,
              'password': password,
              'otpCode': otp_code
          }
      )

      response.raise_for_status()
      data = response.json()
      return data['accessToken']
  ```
</CodeGroup>

#### Response

```json theme={null}
{
  "accessToken": "US_b6b9168a-bb56-4c6a-9c0d-4650ea74f5f9",
  "userId": "b6b9168c-bb56-4c6a-9c0d-4650ea74f5f9",
  "isOtpRequired": false,
  "phoneNumber": null,
  "phase": null,
  "verificationState": "VERIFIED",
  "isLinked": false
}
```

<Info>
  After successful OTP verification, `isOtpRequired` will be `false` and the `accessToken` is fully valid for API calls.
</Info>

### Common OTP Errors

<AccordionGroup>
  <Accordion title="Invalid OTP Code">
    ```json theme={null}
    {
      "message": "Invalid OTP code",
      "isOtpRequired": true
    }
    ```

    **Resolution**: Ask user to check the code and try again, or request a new OTP code
  </Accordion>

  <Accordion title="OTP Code Expired">
    ```json theme={null}
    {
      "message": "OTP code has expired",
      "isOtpRequired": true
    }
    ```

    **Resolution**: Request a new OTP code via [`POST /v1/auth/login/otp`](/api-reference/auth/login-otp)
  </Accordion>

  <Accordion title="Too Many OTP Attempts">
    ```json theme={null}
    {
      "message": "Too many failed OTP attempts. Please try again later."
    }
    ```

    **Resolution**: Wait for rate limit to reset (typically 15-30 minutes) or contact support
  </Accordion>
</AccordionGroup>

## Complete OTP Login Example

Here's a complete implementation of the OTP login flow:

<CodeGroup>
  ```javascript JavaScript/TypeScript theme={null}
  class AuthService {
    private apiBase = 'https://dev.api.baanx.com';
    private clientKey = 'YOUR_CLIENT_KEY';

    async login(email: string, password: string): Promise<LoginResponse> {
      const response = await fetch(`${this.apiBase}/v1/auth/login`, {
        method: 'POST',
        headers: {
          'x-client-key': this.clientKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ email, password })
      });

      if (!response.ok) {
        throw new Error(`Login failed: ${response.statusText}`);
      }

      return response.json();
    }

    async sendOtpCode(userId: string): Promise<void> {
      const response = await fetch(`${this.apiBase}/v1/auth/login/otp`, {
        method: 'POST',
        headers: {
          'x-client-key': this.clientKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ userId })
      });

      if (!response.ok) {
        throw new Error('Failed to send OTP code');
      }
    }

    async loginWithOtp(
      email: string,
      password: string,
      otpCode: string
    ): Promise<string> {
      const response = await fetch(`${this.apiBase}/v1/auth/login`, {
        method: 'POST',
        headers: {
          'x-client-key': this.clientKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ email, password, otpCode })
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.message || 'OTP login failed');
      }

      const data = await response.json();
      return data.accessToken;
    }

    async handleLogin(email: string, password: string): Promise<string> {
      const loginResult = await this.login(email, password);

      if (loginResult.isOtpRequired) {
        console.log('OTP required. Sending code to:', loginResult.phoneNumber);
        await this.sendOtpCode(loginResult.userId);

        const otpCode = await this.promptUserForOtpCode();

        return await this.loginWithOtp(email, password, otpCode);
      }

      return loginResult.accessToken;
    }

    private async promptUserForOtpCode(): Promise<string> {
      return new Promise((resolve) => {
        console.log('Please enter the OTP code:');
      });
    }
  }

  interface LoginResponse {
    accessToken: string;
    userId: string;
    isOtpRequired: boolean;
    phoneNumber?: string;
    verificationState: string;
  }

  const authService = new AuthService();

  authService.handleLogin('user@example.com', 'SecurePassword123!')
    .then(accessToken => {
      console.log('Login successful! Token:', accessToken);
      localStorage.setItem('accessToken', accessToken);
    })
    .catch(error => {
      console.error('Login failed:', error);
    });
  ```

  ```python Python theme={null}
  import requests
  from typing import Dict, Optional

  class AuthService:
      def __init__(self, api_base: str, client_key: str):
          self.api_base = api_base
          self.client_key = client_key
          self.headers = {
              'x-client-key': client_key,
              'Content-Type': 'application/json'
          }

      def login(self, email: str, password: str) -> Dict:
          response = requests.post(
              f'{self.api_base}/v1/auth/login',
              headers=self.headers,
              json={'email': email, 'password': password}
          )
          response.raise_for_status()
          return response.json()

      def send_otp_code(self, user_id: str) -> None:
          response = requests.post(
              f'{self.api_base}/v1/auth/login/otp',
              headers=self.headers,
              json={'userId': user_id}
          )
          response.raise_for_status()

      def login_with_otp(
          self,
          email: str,
          password: str,
          otp_code: str
      ) -> str:
          response = requests.post(
              f'{self.api_base}/v1/auth/login',
              headers=self.headers,
              json={
                  'email': email,
                  'password': password,
                  'otpCode': otp_code
              }
          )
          response.raise_for_status()
          data = response.json()
          return data['accessToken']

      def handle_login(self, email: str, password: str) -> str:
          login_result = self.login(email, password)

          if login_result.get('isOtpRequired'):
              print(f"OTP required. Sending code to: {login_result['phoneNumber']}")
              self.send_otp_code(login_result['userId'])

              otp_code = input('Please enter the OTP code: ')

              return self.login_with_otp(email, password, otp_code)

          return login_result['accessToken']

  auth_service = AuthService(
      'https://dev.api.baanx.com',
      'YOUR_CLIENT_KEY'
  )

  try:
      access_token = auth_service.handle_login(
          'user@example.com',
          'SecurePassword123!'
      )
      print(f'Login successful! Token: {access_token}')
  except requests.exceptions.HTTPError as error:
      print(f'Login failed: {error}')
  ```
</CodeGroup>

## Session Management

### Token Expiration

Access tokens are valid for **6 hours** from the time of issuance. After expiration, users must re-authenticate.

<Tip>
  Store the token creation time and implement automatic re-authentication when approaching expiration.
</Tip>

### Token Storage

Store access tokens securely based on your application type:

**Web Applications:**

```javascript theme={null}
sessionStorage.setItem('accessToken', token);

localStorage.setItem('accessToken', token);
```

**Mobile Applications:**

* Use secure storage mechanisms (iOS Keychain, Android Keystore)
* Never store tokens in plain text files or shared preferences

**Backend Applications:**

* Store in memory or secure session stores (Redis, database)
* Use encryption for persistent storage

### Token Validation

Before making API calls, validate the token hasn't expired:

```javascript theme={null}
function isTokenValid(token: string, issuedAt: number): boolean {
  const SIX_HOURS_MS = 6 * 60 * 60 * 1000;
  const now = Date.now();
  return (now - issuedAt) < SIX_HOURS_MS;
}

const issuedAt = Date.now();
localStorage.setItem('tokenIssuedAt', issuedAt.toString());

if (!isTokenValid(token, parseInt(issuedAt))) {
  await reauthenticate();
}
```

## Logout

Terminate the current user session and invalidate the access token.

### Endpoint

```bash theme={null}
POST /v1/auth/logout
```

**API Reference:** [`POST /v1/auth/logout`](/api-reference/auth/logout)

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/logout \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Authorization: Bearer US_b6b9168a-bb56-4c6a-9c0d-4650ea74f5f9"
  ```

  ```javascript JavaScript/TypeScript theme={null}
  async function logout(accessToken: string): Promise<void> {
    const response = await fetch('https://dev.api.baanx.com/v1/auth/logout', {
      method: 'POST',
      headers: {
        'x-client-key': 'YOUR_CLIENT_KEY',
        'Authorization': `Bearer ${accessToken}`
      }
    });

    if (!response.ok) {
      throw new Error('Logout failed');
    }

    localStorage.removeItem('accessToken');
    localStorage.removeItem('tokenIssuedAt');
    console.log('Logout successful');
  }
  ```

  ```python Python theme={null}
  def logout(access_token: str) -> None:
      response = requests.post(
          'https://dev.api.baanx.com/v1/auth/logout',
          headers={
              'x-client-key': 'YOUR_CLIENT_KEY',
              'Authorization': f'Bearer {access_token}'
          }
      )

      response.raise_for_status()
      print('Logout successful')
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "success": true
}
```

<Info>
  After logout, the access token is immediately invalidated and cannot be used for further API calls.
</Info>

## Login Access Token vs OAuth Tokens

Understanding the difference between login access tokens and OAuth tokens helps you choose the right authentication method for your use case.

### Why Exchange Tokens at the OAuth Authorize Endpoint?

Even though the login `accessToken` works for API calls, OAuth tokens provide significant advantages for production applications:

<CardGroup cols={2}>
  <Card title="Login Access Token" icon="clock">
    **Short-Term Direct Access**

    **Limitations:**

    * Expires in 6 hours (21,600 seconds)
    * Cannot be refreshed
    * Cannot be revoked independently
    * No granular permission scopes

    **Best for:**

    * Quick authentication for first-party apps
    * Short user sessions
    * Testing and development
    * Simple integrations
  </Card>

  <Card title="OAuth Tokens" icon="shield-check">
    **Long-Lived Delegated Access**

    **Advantages:**

    * Refresh tokens last 7 days (604,800 seconds) for security compliance with financial operations
    * Can obtain new access tokens without re-login
    * Can be revoked via `/oauth/revoke` endpoint
    * PKCE security (S256 code challenge) prevents interception
    * Proper delegation model for third-party apps
    * Granular permission management

    **Best for:**

    * Long-lived access
    * Third-party integrations
    * Mobile applications
    * Multi-tenant applications
  </Card>
</CardGroup>

### Token Comparison Table

| Feature                    | Login Access Token          | OAuth Access Token             |
| -------------------------- | --------------------------- | ------------------------------ |
| **Lifetime**               | 6 hours                     | 6 hours                        |
| **Obtained From**          | `POST /v1/auth/login`       | `POST /v1/auth/oauth/token`    |
| **Refresh Capability**     | None                        | Via refresh token              |
| **Refresh Token Lifetime** | N/A                         | 7 days                         |
| **Revocation**             | Expires only                | Can be revoked                 |
| **Security Model**         | Direct credentials          | PKCE + OAuth 2.0               |
| **Use Case**               | First-party, short sessions | Third-party, long-lived access |
| **Best Practice**          | Development & testing       | Production applications        |

### Use Case Examples

<Tabs>
  <Tab title="Use Login Token When">
    **Scenario 1: First-Party Web App**

    ```javascript theme={null}
    // Simple login for your own application
    const { accessToken } = await login(email, password);

    // Use for the next 6 hours
    const userData = await fetch('/v1/users/me', {
      headers: { 'Authorization': `Bearer ${accessToken}` }
    });
    ```

    **Scenario 2: Short Testing Session**

    ```bash theme={null}
    # Quick API testing
    curl -X GET "https://api.example.com/v1/wallets" \
      -H "Authorization: Bearer US_b6b9168a-bb56-4c6a-9c0d-4650ea74f5f9"
    ```

    **Scenario 3: Admin Tools**

    * Internal admin dashboards
    * Short-lived debugging sessions
    * One-time data exports
  </Tab>

  <Tab title="Use OAuth Tokens When">
    **Scenario 1: Mobile Application**

    ```javascript theme={null}
    // Initial OAuth flow (once)
    const { access_token, refresh_token } = await completeOAuthFlow();

    // Use access token for 6 hours
    await makeAPICall(access_token);

    // After 6 hours, refresh without re-login
    const newTokens = await refreshToken(refresh_token);

    // Continue for up to 7 days without user interaction
    ```

    **Scenario 2: Third-Party Integration**

    ```javascript theme={null}
    // Partner application accessing user data
    const oauthTokens = await getOAuthTokensForUser(userId);

    // Can be revoked by user at any time
    // Proper permission scopes
    // Audit trail of access
    ```

    **Scenario 3: Long-Running Background Services**

    * Scheduled reports sent via email
    * Data synchronization services
    * Monitoring and alerting systems
    * Automated financial operations
  </Tab>
</Tabs>

<Warning>
  **Important Future Change:** The platform will eventually require OAuth tokens for all API endpoints. The login `accessToken` will only work for initial OAuth authorization steps. Plan your implementation accordingly.
</Warning>

### Migration Strategy

If you're currently using login access tokens:

<Steps>
  <Step title="Identify Long-Lived Sessions">
    Determine which parts of your application need access beyond 6 hours
  </Step>

  <Step title="Implement OAuth Flow">
    Add OAuth 2.0 authorization for those long-lived sessions
  </Step>

  <Step title="Implement Token Refresh">
    Use refresh tokens to maintain sessions without re-authentication
  </Step>

  <Step title="Test Revocation">
    Ensure your app handles token revocation gracefully
  </Step>

  <Step title="Monitor Token Expiry">
    Track refresh token expiration (7 days) and prompt re-authentication
  </Step>
</Steps>

## OAuth 2.0 Authentication

For third-party applications, OAuth 2.0 provides delegated authorization without exposing user credentials.

### OAuth Flow Overview

<Tabs>
  <Tab title="Hosted UI Mode (4 Steps)">
    ```mermaid theme={null}
    sequenceDiagram
        participant User
        participant Client as Your App
        participant API as Baanx API
        participant UI as Hosted UI

        Note over Client: Generate PKCE parameters<br/>code_verifier (random 43-128 chars)<br/>code_challenge = BASE64URL(SHA256(code_verifier))

        Note over Client,API: Step 1: Initiate Authorization
        Client->>API: GET /v1/auth/oauth/authorize/initiate<br/>?response_type=code&redirect_uri=...&state=...&code_challenge=...
        API-->>Client: 302 Redirect to Hosted UI<br/>(JWT token embedded in URL)

        Note over User,UI: Step 2: User Authentication (Automatic)
        Client->>UI: User redirected to Hosted UI
        User->>UI: Enter credentials & approve
        UI->>User: Redirect to callback URL<br/>https://yourapp.com/callback?code=...&state=...

        Note over Client,API: Step 3: Token Exchange
        Client->>API: POST /v1/auth/oauth/token<br/>{grant_type: "authorization_code", code, code_verifier}
        API-->>Client: 200 OK<br/>{access_token, refresh_token}
        Note over Client: Access token: 6 hours<br/>Refresh token: 7 days

        Note over Client,API: Step 4: API Calls & Refresh
        Client->>API: API calls with Bearer token
        Note over Client,API: When access token expires (6 hours)
        Client->>API: POST /v1/auth/oauth/token<br/>{grant_type: "refresh_token", refresh_token}
        API-->>Client: 200 OK<br/>{new access_token, new refresh_token}
    ```
  </Tab>

  <Tab title="API Mode (5 Steps)">
    ```mermaid theme={null}
    sequenceDiagram
        participant User
        participant Client as Your App
        participant API as Baanx API

        Note over Client: Generate PKCE parameters<br/>code_verifier (random 43-128 chars)<br/>code_challenge = BASE64URL(SHA256(code_verifier))

        Note over Client,API: Step 1: Initiate Authorization (mode=api)
        Client->>API: GET /v1/auth/oauth/authorize/initiate?mode=api<br/>&code_challenge=...&response_type=code&redirect_uri=...
        API-->>Client: 200 OK<br/>{token: JWT (10 min expiry)}
        Note over Client: Store JWT token for Step 3

        Note over Client,API: Step 2: Authenticate User
        User->>Client: Enter credentials in your app
        Client->>API: POST /v1/auth/login<br/>{email, password}
        API-->>Client: 200 OK<br/>{accessToken: LOGIN_ACCESS_TOKEN}
        Note over Client: Store login access token for Step 3

        Note over Client,API: Step 3: Generate Authorization Code
        Client->>API: POST /v1/auth/oauth/authorize<br/>Authorization: Bearer LOGIN_ACCESS_TOKEN<br/>Body: {token: JWT_FROM_STEP_1}
        API-->>Client: 200 OK<br/>{code, state, url}
        Note over Client: Single-use authorization code

        Note over Client,API: Step 4: Token Exchange
        Client->>API: POST /v1/auth/oauth/token<br/>{grant_type: "authorization_code", code, code_verifier}
        API-->>Client: 200 OK<br/>{access_token: OAUTH_ACCESS_TOKEN, refresh_token}
        Note over Client: OAuth access token: 6 hours<br/>Refresh token: 7 days

        Note over Client,API: Step 5: API Calls & Refresh
        Client->>API: API calls with Bearer OAUTH_ACCESS_TOKEN
        Note over Client,API: When access token expires (6 hours)
        Client->>API: POST /v1/auth/oauth/token<br/>{grant_type: "refresh_token", refresh_token}
        API-->>Client: 200 OK<br/>{new access_token, new refresh_token}
    ```
  </Tab>

  <Tab title="Token Types Explained">
    ### Token Types in OAuth Flow

    The OAuth flow uses different tokens for different purposes:

    | Token Type             | Used In       | Purpose                   | Lifetime   | Where Used                                             |
    | ---------------------- | ------------- | ------------------------- | ---------- | ------------------------------------------------------ |
    | **JWT Token**          | API Mode only | OAuth session tracking    | 10 minutes | Request body in Step 3 (POST /oauth/authorize)         |
    | **Login Access Token** | API Mode only | User authentication proof | 6 hours    | Authorization header in Step 3 (POST /oauth/authorize) |
    | **OAuth Access Token** | Both modes    | API authentication        | 6 hours    | Authorization header for all API calls                 |
    | **Refresh Token**      | Both modes    | Token renewal             | 7 days     | Request body when refreshing (POST /oauth/token)       |

    ### Important Distinctions:

    **JWT Token (Step 1 in API mode)**:

    * Only for OAuth flow session
    * Expires in 10 minutes
    * Never used for API calls
    * Sent in request body to `POST /oauth/authorize`

    **Login Access Token (Step 2 in API mode)**:

    * Proves user authenticated in your app
    * Used in `Authorization: Bearer` header for `POST /oauth/authorize`
    * Different from the OAuth access token you'll get in Step 4
    * Only needed to complete OAuth flow

    **OAuth Access Token (Step 4)**:

    * THIS is what you use for all API calls
    * Expires in 6 hours
    * Renewable via refresh token
    * Sent in `Authorization: Bearer {token}` header

    **Refresh Token (Step 4)**:

    * Long-lived (7 days)
    * Used to get new access tokens without re-login
    * Send to `POST /oauth/token` with `grant_type=refresh_token`

    <Warning>
      Don't confuse the login access token (Step 2, API mode) with the OAuth access token (Step 4). The login token is only for completing the OAuth flow. The OAuth access token is for actual API calls.
    </Warning>
  </Tab>

  <Tab title="PKCE Implementation">
    ### Proof Key for Code Exchange (PKCE)

    PKCE is **mandatory** for security. Here's how to implement it:

    **1. Generate Code Verifier** (random 43-128 characters):

    ```javascript theme={null}
    function generateCodeVerifier() {
      const array = new Uint8Array(32);
      crypto.getRandomValues(array);
      return base64URLEncode(array);
    }
    ```

    **2. Create Code Challenge** (SHA256 + Base64URL):

    ```javascript theme={null}
    async function generateCodeChallenge(verifier) {
      const encoder = new TextEncoder();
      const data = encoder.encode(verifier);
      const hash = await crypto.subtle.digest('SHA-256', data);
      return base64URLEncode(new Uint8Array(hash));
    }
    ```

    **3. Full Implementation**:

    ```javascript theme={null}
    // Step 1: Generate and store
    const codeVerifier = generateCodeVerifier();
    const codeChallenge = await generateCodeChallenge(codeVerifier);
    sessionStorage.setItem('code_verifier', codeVerifier); // Store safely!

    // Step 1: Send challenge
    const initResponse = await fetch(
      `/v1/auth/oauth/authorize/initiate?` +
      `code_challenge=${codeChallenge}&` +
      `code_challenge_method=S256&...`
    );

    // Step 4: Send verifier
    const tokenResponse = await fetch('/v1/auth/oauth/token', {
      method: 'POST',
      body: JSON.stringify({
        grant_type: 'authorization_code',
        code: authCode,
        code_verifier: sessionStorage.getItem('code_verifier'), // Retrieve stored value
        redirect_uri: 'https://yourapp.com/callback'
      })
    });
    ```

    **Python Implementation**:

    ```python theme={null}
    import hashlib
    import base64
    import secrets

    def generate_code_verifier():
        return base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')

    def generate_code_challenge(verifier):
        digest = hashlib.sha256(verifier.encode('utf-8')).digest()
        return base64.urlsafe_b64encode(digest).decode('utf-8').rstrip('=')
    ```

    <Warning>
      **Security**: Never expose `code_verifier` in URLs, logs, or client-side code. Store it securely (sessionStorage, secure cookie, or backend session) between Steps 1 and 4.
    </Warning>
  </Tab>
</Tabs>

### When to Use OAuth

Use OAuth 2.0 when:

* Building third-party integrations
* Providing API access to external partners
* Creating multi-tenant applications
* Implementing "Login with \[Your Service]" buttons

### Quick Start

OAuth 2.0 implementation involves 4 steps:

<Steps>
  <Step title="Initiate Authorization">
    Request authorization from the user via `GET /v1/auth/oauth/authorize/initiate`
  </Step>

  <Step title="User Authentication">
    User authenticates on hosted UI (or via API-mode login)
  </Step>

  <Step title="Generate Authorization Code">
    Receive authorization code after user approval
  </Step>

  <Step title="Exchange for Tokens">
    Exchange code for access and refresh tokens via `POST /v1/auth/oauth/token`
  </Step>
</Steps>

<Note>
  For complete OAuth 2.0 implementation details, see the [OAuth 2.0 Guide](/guides/oauth) and API reference documentation.
</Note>

### OAuth vs Standard Login

| Feature              | Standard Login      | OAuth 2.0                        |
| -------------------- | ------------------- | -------------------------------- |
| **Use Case**         | First-party apps    | Third-party integrations         |
| **User Credentials** | Handled by your app | Never exposed to your app        |
| **Token Lifetime**   | 6 hours             | Access: 6 hours, Refresh: 7 days |
| **Token Refresh**    | Re-authenticate     | Use refresh token                |
| **Delegation**       | Direct access       | Scoped permissions               |

## Best Practices

<CardGroup cols={2}>
  <Card title="Secure Storage" icon="vault">
    Always store access tokens securely. Never expose tokens in URLs, logs, or client-side code repositories.
  </Card>

  <Card title="HTTPS Only" icon="lock">
    All authentication requests must use HTTPS in production. Never send credentials over unencrypted connections.
  </Card>

  <Card title="Rate Limiting" icon="gauge-high">
    Implement exponential backoff for failed login attempts to prevent brute force attacks.
  </Card>

  <Card title="Token Rotation" icon="rotate">
    Rotate tokens periodically and after sensitive operations. Invalidate tokens on password changes.
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation">
    Don't leak sensitive information in error messages. Use generic messages like "Invalid credentials" instead of "User not found" or "Wrong password".
  </Card>

  <Card title="OTP Timeout" icon="clock">
    Implement countdown timers for OTP codes to improve UX. Show "Resend code" option after timeout.
  </Card>
</CardGroup>

## Security Considerations

### Password Security

* Never store passwords in plain text
* Use secure password hashing (bcrypt, Argon2)
* Implement password strength requirements
* Enforce password rotation policies
* Prevent password reuse

### Multi-Factor Authentication

OTP provides an additional security layer:

* SMS-based codes (current implementation)
* Consider supporting authenticator apps (TOTP)
* Implement backup codes for account recovery
* Allow users to enable/disable MFA

### Account Protection

* Lock accounts after multiple failed login attempts
* Implement CAPTCHA after failed attempts
* Send security alerts for suspicious logins
* Log all authentication events for audit

### Token Security

* Use short-lived access tokens (6 hours)
* Implement token rotation for refresh tokens
* Invalidate all tokens on password change
* Provide "Log out all devices" functionality

## Troubleshooting

### Login Failures

<AccordionGroup>
  <Accordion title="Credentials Work in One Environment But Not Another">
    **Cause**: Trying to use production credentials in sandbox or vice versa

    **Resolution**: Ensure you're using the correct `x-us-env` header and credentials for each environment
  </Accordion>

  <Accordion title="Token Rejected by API">
    **Cause**: Token expired, invalid format, or wrong environment

    **Resolution**:

    * Check token expiration (6-hour limit)
    * Verify token format includes environment prefix (e.g., "US\_...")
    * Ensure `x-client-key` matches the environment that issued the token
  </Accordion>

  <Accordion title="OTP Not Received">
    **Cause**: Phone number issues, carrier blocking, or delay

    **Resolution**:

    * Verify phone number is correct and can receive SMS
    * Check spam/blocked messages on device
    * Wait 2-3 minutes for delivery delays
    * Request a new code if needed
  </Accordion>
</AccordionGroup>

### Integration Issues

Common issues when integrating authentication:

**Missing Headers:**

```bash theme={null}
# Wrong
curl -X POST https://dev.api.baanx.com/v1/auth/login

# Correct
curl -X POST https://dev.api.baanx.com/v1/auth/login \
  -H "x-client-key: YOUR_CLIENT_KEY" \
  -H "Content-Type: application/json"
```

**Wrong Token Format:**

```javascript theme={null}
// Wrong
headers: { 'Authorization': accessToken }

// Correct
headers: { 'Authorization': `Bearer ${accessToken}` }
```

**Environment Mismatch:**

```javascript theme={null}
// Production credentials with sandbox endpoint
const response = await fetch('https://dev.api.baanx.com/v1/auth/login', {
  headers: { 'x-client-key': 'PRODUCTION_KEY' }
});

// Ensure credentials match environment
```

## Complete Authentication Example

Full implementation with error handling and token management:

<CodeGroup>
  ```javascript JavaScript/TypeScript theme={null}
  class AuthManager {
    private apiBase: string;
    private clientKey: string;
    private tokenKey = 'accessToken';
    private tokenTimeKey = 'tokenIssuedAt';

    constructor(apiBase: string, clientKey: string) {
      this.apiBase = apiBase;
      this.clientKey = clientKey;
    }

    private get headers() {
      return {
        'x-client-key': this.clientKey,
        'Content-Type': 'application/json'
      };
    }

    private get authHeaders() {
      const token = this.getToken();
      return {
        ...this.headers,
        'Authorization': `Bearer ${token}`
      };
    }

    async login(email: string, password: string): Promise<string> {
      try {
        const response = await fetch(`${this.apiBase}/v1/auth/login`, {
          method: 'POST',
          headers: this.headers,
          body: JSON.stringify({ email, password })
        });

        if (!response.ok) {
          throw new Error('Login failed');
        }

        const data = await response.json();

        if (data.isOtpRequired) {
          await this.sendOtpCode(data.userId);
          const otpCode = await this.getOtpFromUser();
          return await this.loginWithOtp(email, password, otpCode);
        }

        this.storeToken(data.accessToken);
        return data.accessToken;
      } catch (error) {
        console.error('Login error:', error);
        throw error;
      }
    }

    async sendOtpCode(userId: string): Promise<void> {
      const response = await fetch(`${this.apiBase}/v1/auth/login/otp`, {
        method: 'POST',
        headers: this.headers,
        body: JSON.stringify({ userId })
      });

      if (!response.ok) {
        throw new Error('Failed to send OTP');
      }
    }

    async loginWithOtp(
      email: string,
      password: string,
      otpCode: string
    ): Promise<string> {
      const response = await fetch(`${this.apiBase}/v1/auth/login`, {
        method: 'POST',
        headers: this.headers,
        body: JSON.stringify({ email, password, otpCode })
      });

      if (!response.ok) {
        throw new Error('OTP verification failed');
      }

      const data = await response.json();
      this.storeToken(data.accessToken);
      return data.accessToken;
    }

    async logout(): Promise<void> {
      try {
        await fetch(`${this.apiBase}/v1/auth/logout`, {
          method: 'POST',
          headers: this.authHeaders
        });
      } finally {
        this.clearToken();
      }
    }

    storeToken(token: string): void {
      localStorage.setItem(this.tokenKey, token);
      localStorage.setItem(this.tokenTimeKey, Date.now().toString());
    }

    getToken(): string | null {
      const token = localStorage.getItem(this.tokenKey);
      if (!token) return null;

      if (!this.isTokenValid()) {
        this.clearToken();
        return null;
      }

      return token;
    }

    clearToken(): void {
      localStorage.removeItem(this.tokenKey);
      localStorage.removeItem(this.tokenTimeKey);
    }

    isTokenValid(): boolean {
      const issuedAt = localStorage.getItem(this.tokenTimeKey);
      if (!issuedAt) return false;

      const SIX_HOURS_MS = 6 * 60 * 60 * 1000;
      const elapsed = Date.now() - parseInt(issuedAt);
      return elapsed < SIX_HOURS_MS;
    }

    isAuthenticated(): boolean {
      return this.getToken() !== null;
    }

    private async getOtpFromUser(): Promise<string> {
      return prompt('Enter OTP code:') || '';
    }
  }

  const authManager = new AuthManager(
    'https://dev.api.baanx.com',
    'YOUR_CLIENT_KEY'
  );

  authManager.login('user@example.com', 'SecurePassword123!')
    .then(() => console.log('Login successful'))
    .catch(error => console.error('Login failed:', error));
  ```

  ```python Python theme={null}
  import time
  from typing import Optional
  import requests

  class AuthManager:
      TOKEN_LIFETIME_MS = 6 * 60 * 60 * 1000

      def __init__(self, api_base: str, client_key: str):
          self.api_base = api_base
          self.client_key = client_key
          self.token: Optional[str] = None
          self.token_issued_at: Optional[int] = None

      @property
      def headers(self):
          return {
              'x-client-key': self.client_key,
              'Content-Type': 'application/json'
          }

      @property
      def auth_headers(self):
          return {
              **self.headers,
              'Authorization': f'Bearer {self.token}'
          }

      def login(self, email: str, password: str) -> str:
          try:
              response = requests.post(
                  f'{self.api_base}/v1/auth/login',
                  headers=self.headers,
                  json={'email': email, 'password': password}
              )
              response.raise_for_status()
              data = response.json()

              if data.get('isOtpRequired'):
                  self.send_otp_code(data['userId'])
                  otp_code = input('Enter OTP code: ')
                  return self.login_with_otp(email, password, otp_code)

              self.store_token(data['accessToken'])
              return data['accessToken']
          except requests.exceptions.HTTPError as error:
              print(f'Login error: {error}')
              raise

      def send_otp_code(self, user_id: str) -> None:
          response = requests.post(
              f'{self.api_base}/v1/auth/login/otp',
              headers=self.headers,
              json={'userId': user_id}
          )
          response.raise_for_status()

      def login_with_otp(
          self,
          email: str,
          password: str,
          otp_code: str
      ) -> str:
          response = requests.post(
              f'{self.api_base}/v1/auth/login',
              headers=self.headers,
              json={
                  'email': email,
                  'password': password,
                  'otpCode': otp_code
              }
          )
          response.raise_for_status()
          data = response.json()
          self.store_token(data['accessToken'])
          return data['accessToken']

      def logout(self) -> None:
          try:
              requests.post(
                  f'{self.api_base}/v1/auth/logout',
                  headers=self.auth_headers
              )
          finally:
              self.clear_token()

      def store_token(self, token: str) -> None:
          self.token = token
          self.token_issued_at = int(time.time() * 1000)

      def get_token(self) -> Optional[str]:
          if not self.token:
              return None

          if not self.is_token_valid():
              self.clear_token()
              return None

          return self.token

      def clear_token(self) -> None:
          self.token = None
          self.token_issued_at = None

      def is_token_valid(self) -> bool:
          if not self.token_issued_at:
              return False

          elapsed = (int(time.time() * 1000) - self.token_issued_at)
          return elapsed < self.TOKEN_LIFETIME_MS

      def is_authenticated(self) -> bool:
          return self.get_token() is not None

  auth_manager = AuthManager(
      'https://dev.api.baanx.com',
      'YOUR_CLIENT_KEY'
  )

  try:
      auth_manager.login('user@example.com', 'SecurePassword123!')
      print('Login successful')
  except Exception as error:
      print(f'Login failed: {error}')
  ```
</CodeGroup>

## Next Steps

After successful authentication:

1. **Make Authenticated Requests**: Use the access token for all API calls
2. **Check Verification Status**: Verify user's KYC status via [Profile Management](/guides/user/profile)
3. **Implement Token Refresh**: Plan for token expiration and re-authentication

<CardGroup cols={2}>
  <Card title="Profile Management" icon="user-gear" href="/guides/user/profile">
    View and update user information after authentication
  </Card>

  <Card title="Registration Guide" icon="user-plus" href="/guides/user/registration">
    Learn about the user registration process
  </Card>

  <Card title="OAuth 2.0 Guide" icon="lock" href="/guides/oauth">
    Implement OAuth 2.0 for third-party integrations
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Complete endpoint documentation
  </Card>
</CardGroup>
