> ## 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.

# Initiate OAuth Authorization

> First step of OAuth 2.0 Authorization Code Flow with PKCE

## Overview

Initiates the OAuth authorization process and returns a hosted UI URL where users authenticate. This is the first step in the OAuth 2.0 Authorization Code Flow with PKCE (Proof Key for Code Exchange).

The `redirect_uri` provided must be whitelisted in your environment configuration. Authorization sessions expire after 10 minutes, so you must complete the flow before expiration.

## When to Use

* Starting an OAuth 2.0 authorization flow for third-party access
* Implementing secure delegated authentication
* Need users to grant permission to your application

## PKCE Requirements

PKCE (Proof Key for Code Exchange) is **mandatory** for security. You must:

1. **Generate `code_verifier`**: 43-128 random characters from `[A-Za-z0-9-._~]`
2. **Create `code_challenge`**: `BASE64URL(SHA256(code_verifier))`
3. **Send `code_challenge`** in this request
4. **Store `code_verifier`** securely for use in Step 4 (token exchange)

<CodeGroup>
  ```javascript Generate PKCE theme={null}
  import crypto from 'crypto';

  function generateCodeVerifier() {
    return crypto.randomBytes(32).toString('base64url');
  }

  function generateCodeChallenge(verifier) {
    return crypto
      .createHash('sha256')
      .update(verifier)
      .digest('base64url');
  }

  const codeVerifier = generateCodeVerifier();
  const codeChallenge = generateCodeChallenge(codeVerifier);

  console.log('Store this:', codeVerifier);
  console.log('Send this:', codeChallenge);
  ```

  ```python Generate PKCE theme={null}
  import base64
  import hashlib
  import os

  def generate_code_verifier():
      return base64.urlsafe_b64encode(os.urandom(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('=')

  code_verifier = generate_code_verifier()
  code_challenge = generate_code_challenge(code_verifier)

  print(f'Store this: {code_verifier}')
  print(f'Send this: {code_challenge}')
  ```
</CodeGroup>

## Flow Types

### Hosted UI Flow (Default)

When not using `mode=api`, this endpoint redirects to a hosted UI where users authenticate:

1. Call this endpoint to get hosted UI URL (302 redirect or JSON with URL)
2. Redirect user to hosted UI for authentication
3. User authenticates and grants permission automatically in UI
4. User is redirected back to your `redirect_uri` with authorization code
5. Exchange authorization code for tokens via `POST /v1/auth/oauth/token`

### API Mode Flow

When using `mode=api`, you handle authentication in your application:

1. Call this endpoint with `mode=api` to get JWT token
2. Authenticate user via `POST /v1/auth/login` to get access token
3. Call `POST /v1/auth/oauth/authorize` with both tokens to obtain authorization code
4. Exchange authorization code for tokens via `POST /v1/auth/oauth/token`

## Request

### Query Parameters

<ParamField query="client_id" type="string" required>
  Your public API client key (same as x-client-key header)

  **Format**: UUID

  **Example**: `100a99cf-f4d3-4fa1-9be9-2e9828b20ebb`
</ParamField>

<ParamField query="response_type" type="string" required>
  Must be `code` (authorization code flow only)

  **Allowed values**: `code`
</ParamField>

<ParamField query="redirect_uri" type="string" required>
  Your callback URL where user will be redirected after authorization

  **Must be whitelisted** in environment settings

  **Example**: `https://yourapp.com/callback`
</ParamField>

<ParamField query="state" type="string" required>
  **CSRF protection token.** Client-defined random string returned unchanged in callback

  **Minimum length**: 8 characters

  Verify this matches your original value when receiving the callback

  **Example**: `random_csrf_protection_string_12345`
</ParamField>

<ParamField query="code_challenge" type="string" required>
  **PKCE code challenge.** `BASE64URL(SHA256(code_verifier))`

  **Pattern**: 43-128 characters from `[A-Za-z0-9-._~]`

  **Example**: `M5iYUROLfJajkMMUkn3gpEquAVQpnK4m55NCeK40nls`
</ParamField>

<ParamField query="code_challenge_method" type="string" required>
  **PKCE challenge method.** Must be `S256` (SHA256 hashing)

  **Allowed values**: `S256`
</ParamField>

<ParamField query="client_secret" type="string">
  Client secret key - only required if secret key is issued for your environment

  **Example**: `100a99cf-f4d3-4fa1-9be9-2e9828b20eaa`
</ParamField>

<ParamField query="region" type="string">
  Set to `us` to route requests to the US backend environment (if available for your client)

  **Allowed values**: `us`

  **Default**: international environment
</ParamField>

<ParamField query="mode" type="string">
  Set to `api` to return JSON instead of 302 redirect. Useful for API-only integrations

  **Allowed values**: `api`
</ParamField>

### Headers

<ParamField header="x-client-key" type="string" required>
  Your public API client key
</ParamField>

<ParamField header="x-secret-key" type="string">
  Your secret API key (required only if secret key is issued)
</ParamField>

## Response

<ResponseField name="token" type="string">
  JWT session token (valid for 10 minutes) - only returned when `mode=api`

  **Format**: JWT

  **Example**: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`
</ResponseField>

<ResponseField name="url" type="string">
  Hosted UI URL where user should authenticate

  **Format**: URI

  **Example**: `https://app.example.com/account/login?token=eyJhbGc...`
</ResponseField>

### Success Responses

<Tabs>
  <Tab title="200 OK (API Mode)">
    **Returned when**: `mode=api` parameter is provided

    ```json theme={null}
    {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "url": "https://app.example.com/account/login?token=eyJhbGc..."
    }
    ```
  </Tab>

  <Tab title="302 Found (Default)">
    **Returned when**: Default behavior (no `mode` parameter)

    **Redirects to**: Hosted UI URL

    **Location header**: `https://app.example.com/account/login?token=eyJhbGc...`
  </Tab>
</Tabs>

### Error Responses

<Accordion title="400 Bad Request - Invalid Parameters">
  ```json theme={null}
  {
    "error": "invalid_request",
    "error_description": "PKCE is required. Missing code_challenge or code_challenge_method parameter"
  }
  ```

  **Common causes**:

  * Missing or invalid PKCE parameters
  * Invalid `response_type` (must be `code`)
  * Missing required parameters (`client_id`, `redirect_uri`, `state`)
  * Invalid parameter formats
</Accordion>

<Accordion title="422 Validation Error">
  ```json theme={null}
  {
    "message": "redirect_uri is not allowed"
  }
  ```

  **Common causes**:

  * `redirect_uri` not whitelisted in environment configuration
  * Invalid URL format
  * Parameter validation failures
</Accordion>

<Accordion title="429 Too Many Requests">
  ```json theme={null}
  {
    "error": "invalid_request",
    "error_description": "Too many authorization requests. Please try again later."
  }
  ```

  **Cause**: Rate limit exceeded. Wait before retrying.
</Accordion>

<Accordion title="498 Invalid Client Key">
  ```json theme={null}
  {
    "message": "Invalid client key"
  }
  ```

  **Cause**: The `x-client-key` header contains an invalid or unknown key.
</Accordion>

<Accordion title="499 Missing Client Key">
  ```json theme={null}
  {
    "message": "Missing client key"
  }
  ```

  **Cause**: The required `x-client-key` header is missing.
</Accordion>

## Code Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://dev.api.baanx.com/v1/auth/oauth/authorize/initiate?client_id=100a99cf-f4d3-4fa1-9be9-2e9828b20ebb&response_type=code&redirect_uri=https://yourapp.com/callback&state=random_csrf_protection_string_12345&code_challenge=M5iYUROLfJajkMMUkn3gpEquAVQpnK4m55NCeK40nls&code_challenge_method=S256" \
    -H "x-client-key: 100a99cf-f4d3-4fa1-9be9-2e9828b20ebb"
  ```

  ```javascript JavaScript (API Mode) theme={null}
  const crypto = require('crypto');

  function generatePKCE() {
    const verifier = crypto.randomBytes(32).toString('base64url');
    const challenge = crypto
      .createHash('sha256')
      .update(verifier)
      .digest('base64url');
    return { verifier, challenge };
  }

  const { verifier, challenge } = generatePKCE();
  const state = crypto.randomBytes(16).toString('hex');

  const params = new URLSearchParams({
    client_id: 'your-client-key',
    response_type: 'code',
    redirect_uri: 'https://yourapp.com/callback',
    state: state,
    code_challenge: challenge,
    code_challenge_method: 'S256',
    mode: 'api'
  });

  const response = await fetch(
    `https://dev.api.baanx.com/v1/auth/oauth/authorize/initiate?${params}`,
    {
      headers: {
        'x-client-key': 'your-client-key'
      }
    }
  );

  const data = await response.json();
  console.log('JWT Token:', data.token);
  console.log('Hosted UI URL:', data.url);

  sessionStorage.setItem('code_verifier', verifier);
  sessionStorage.setItem('oauth_state', state);
  ```

  ```python Python theme={null}
  import base64
  import hashlib
  import os
  import secrets
  import requests

  def generate_pkce():
      verifier = base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8').rstrip('=')
      digest = hashlib.sha256(verifier.encode('utf-8')).digest()
      challenge = base64.urlsafe_b64encode(digest).decode('utf-8').rstrip('=')
      return verifier, challenge

  code_verifier, code_challenge = generate_pkce()
  state = secrets.token_urlsafe(16)

  params = {
      'client_id': 'your-client-key',
      'response_type': 'code',
      'redirect_uri': 'https://yourapp.com/callback',
      'state': state,
      'code_challenge': code_challenge,
      'code_challenge_method': 'S256',
      'mode': 'api'
  }

  response = requests.get(
      'https://dev.api.baanx.com/v1/auth/oauth/authorize/initiate',
      params=params,
      headers={'x-client-key': 'your-client-key'}
  )

  data = response.json()
  print('JWT Token:', data['token'])
  print('Hosted UI URL:', data['url'])

  # Store for later use
  session['code_verifier'] = code_verifier
  session['oauth_state'] = state
  ```

  ```typescript TypeScript theme={null}
  import crypto from 'crypto';

  interface PKCEPair {
    verifier: string;
    challenge: string;
  }

  function generatePKCE(): PKCEPair {
    const verifier = crypto.randomBytes(32).toString('base64url');
    const challenge = crypto
      .createHash('sha256')
      .update(verifier)
      .digest('base64url');
    return { verifier, challenge };
  }

  interface InitiateResponse {
    token: string;
    url: string;
  }

  async function initiateOAuth(): Promise<InitiateResponse> {
    const { verifier, challenge } = generatePKCE();
    const state = crypto.randomBytes(16).toString('hex');

    const params = new URLSearchParams({
      client_id: 'your-client-key',
      response_type: 'code',
      redirect_uri: 'https://yourapp.com/callback',
      state: state,
      code_challenge: challenge,
      code_challenge_method: 'S256',
      mode: 'api'
    });

    const response = await fetch(
      `https://dev.api.baanx.com/v1/auth/oauth/authorize/initiate?${params}`,
      {
        headers: {
          'x-client-key': 'your-client-key'
        }
      }
    );

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

    const data: InitiateResponse = await response.json();

    sessionStorage.setItem('code_verifier', verifier);
    sessionStorage.setItem('oauth_state', state);

    return data;
  }
  ```
</CodeGroup>

## Edge Cases and Important Notes

<Note>
  **Session Expiration**: Authorization sessions expire after **10 minutes**. If your user doesn't complete authentication within this window, you must restart the flow from Step 1.
</Note>

<Warning>
  **Redirect URI Whitelist**: The `redirect_uri` parameter MUST be whitelisted in your environment configuration. Contact your account manager to add URIs to the whitelist.
</Warning>

<Tip>
  **State Parameter Security**: Always verify the `state` parameter returned in the callback matches the one you sent. This prevents CSRF attacks.
</Tip>

### PKCE Code Verifier Storage

The `code_verifier` must be stored securely and retrieved during token exchange (Step 4):

* **Web apps**: Use `sessionStorage` or secure session cookies
* **Mobile apps**: Use secure device storage (Keychain on iOS, Keystore on Android)
* **Server-side apps**: Store in secure session or database with expiration

### Multiple Simultaneous Sessions

Each authorization session is independent. You can have multiple concurrent sessions with different `state` values, but each requires its own PKCE pair.

### Rate Limiting

The endpoint implements rate limiting to prevent abuse. If you receive a 429 error, implement exponential backoff:

```javascript theme={null}
async function initiateWithRetry(maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await initiateOAuth();
    } catch (error) {
      if (error.status === 429 && i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
        continue;
      }
      throw error;
    }
  }
}
```

## Next Steps

After initiating authorization:

<Card title="Step 2: User Authentication" icon="user-check" href="/api-reference/auth/login">
  If using API mode, authenticate the user to get an access token
</Card>

<Card title="Step 3: Generate Authorization Code" icon="key" href="/api-reference/auth/oauth-authorize">
  Generate the authorization code using the JWT token
</Card>

<Card title="Step 4: Token Exchange" icon="arrows-rotate" href="/api-reference/auth/oauth-token">
  Exchange the authorization code for access and refresh tokens
</Card>

## Related Documentation

* [OAuth 2.0 Overview](/api-reference/introduction#oauth-20)
* [Token Exchange](/api-reference/auth/oauth-token)
* [Refresh Tokens](/api-reference/auth/oauth-token#refresh-token-flow)
* [Revoke Authorization](/api-reference/auth/oauth-revoke)
