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

# Authentication

> Learn how to authenticate with the Open Banking API using OAuth 2.0

# Authentication

**OAuth 2.0 Flows**
There are 4 different OAuth 2 authenticaiton mechanism available and each with specific use case:

* **Device Code Authorization**
  * For dumb terminal authentication
  * Use this when your target customer's authentication device has limited capability
  * Example includes customers with no sophisticated phones but have access to USSD or SMS
  * It allows customers to authentication and give consent over dumb channels
* **Authorization Code Flow**
  * For smart terminals
  * Use this for web applications where ther eis a backend that can exchange the generated `authorisation code` for a token
  * Bakend can securle store your cleint id and client secret away from the web browser
* **Authorization Code Flow with PKCE (Proof Key for Code Exchange)**
  * For browser/untrusted applications e.g. Single Page Application
  * Use this when you are developing a single page application (SPA) for enhanced security
* **Client Credentials**
  * Use this when calling API that does not require Customer consent
  * Example includes APIs like: `Open new Account`, `Get Biller Categoryies`, `Get Billers` etc

### API Authentication

After obtaining your acess token via the OAuth 2 flow described earlier, use the obtained acess token to access the APIs. All API calls require:

* **Authorization**: `Bearer <access_token>` - OAuth 2.0 access token
* **signature**: `<signature>` - Request signature for security
* **idempotency\_key**: `<idempotency_key>` - Unique identifier for request deduplication

## Authorization Endpoint

### Request

```http theme={null}
GET /oauth/authorize
```

### Parameters

| Parameter               | Type   | Required | Description                                                     |
| ----------------------- | ------ | -------- | --------------------------------------------------------------- |
| `client_id`             | string | Yes      | Your application's client ID                                    |
| `redirect_uri`          | string | Yes      | Your registered redirect URI during dynamic client registration |
| `response_type`         | string | Yes      | Must be `code`                                                  |
| `scope`                 | string | Yes      | Space-separated list of requested scopes                        |
| `state`                 | string | No       | Random string to prevent CSRF attacks                           |
| `code_challenge`        | string | Yes      | PKCE code challenge                                             |
| `code_challenge_method` | string | Yes      | Must be `S256`                                                  |

### Example Request

<CodeGroup>
  ```javascript JavaScript theme={null}

  // Authorisation via PKCE challenge
  const generatePKCE = () => {
    const codeVerifier = crypto.randomUUID();
    const codeChallenge = btoa(codeVerifier);
    return { codeVerifier, codeChallenge };
  };

  // Step 1: Redirect user to authorization URL
  const { codeVerifier, codeChallenge } = generatePKCE();
  const authUrl = `${AUTH_BASE_URL}/oauth/authorize?` +
    `client_id=${credentials.client_id}&` +
    `redirect_uri=${encodeURIComponent(credentials.redirect_urls[0])}&` +
    `response_type=code&` +
    `scope=accounts payments&` +
    `code_challenge=${codeChallenge}&` +
    `code_challenge_method=S256`;

  window.location.href = authUrl;

  // Step 2: Exchange authorization code for access token
  const exchangeCodeForToken = async (code) => {
    const response = await fetch('${AUTH_BASE_URL}/oauth/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        client_id: credentials.client_id,
        client_secret: credentials.client_secret,
        code: code,
        redirect_uri: credentials.redirect_urls[0],
        code_verifier: codeVerifier
      })
    });

    const tokenData = await response.json();
    return tokenData.access_token;
  };
  ```

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

  // Authorisation via PKCE challenge
  def generate_pkce():
      code_verifier = secrets.token_urlsafe(32)
      code_challenge = base64.urlsafe_b64encode(
          hashlib.sha256(code_verifier.encode()).digest()
      ).decode().rstrip('=')
      return code_verifier, code_challenge

  # Step 1: Generate authorization URL
  code_verifier, code_challenge = generate_pkce()
  auth_url = f"${AUTH_BASE_URL}/oauth/authorize?" + \
      f"client_id={credentials['client_id']}&" + \
      f"redirect_uri={credentials['redirect_urls'][0]}&" + \
      f"response_type=code&" + \
      f"scope=accounts payments&" + \
      f"code_challenge={code_challenge}&" + \
      f"code_challenge_method=S256"

  print(f"Redirect user to: {auth_url}")

  # Step 2: Exchange authorization code for access token
  def exchange_code_for_token(code):
      response = requests.post('${AUTH_BASE_URL}/oauth/token', data={
          'grant_type': 'authorization_code',
          'client_id': credentials['client_id'],
          'client_secret': credentials['client_secret'],
          'code': code,
          'redirect_uri': credentials['redirect_urls'][0],
          'code_verifier': code_verifier
      })
      
      token_data = response.json()
      return token_data['access_token']
  ```
</CodeGroup>

## Token Endpoint

### Request

```http theme={null}
POST /oauth/token
```

### Parameters

| Parameter       | Type   | Required | Description                              |
| --------------- | ------ | -------- | ---------------------------------------- |
| `grant_type`    | string | Yes      | Must be `authorization_code`             |
| `client_id`     | string | Yes      | Your application's client ID             |
| `client_secret` | string | Yes      | Your application's client secret         |
| `code`          | string | Yes      | Authorization code from previous step    |
| `redirect_uri`  | string | Yes      | Must match the one used in authorization |
| `code_verifier` | string | Yes      | PKCE code verifier                       |

### Example Request

<CodeGroup>
  ```javascript JavaScript theme={null}

  // Exchange authorization code for access token
  const exchangeCodeForToken = async (code) => {
    const response = await fetch('${AUTH_BASE_URL}/oauth/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        client_id: credentials.client_id,
        client_secret: credentials.client_secret,
        code: code,
        redirect_uri: credentials.redirect_urls[0],
        code_verifier: codeVerifier
      })
    });

    const tokenData = await response.json();
    return tokenData.access_token;
  };
  ```

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

  # Exchange authorization code for access token
  def exchange_code_for_token(code):
      response = requests.post('${AUTH_BASE_URL}/oauth/token', data={
          'grant_type': 'authorization_code',
          'client_id': credentials['client_id'],
          'client_secret': credentials['client_secret'],
          'code': code,
          'redirect_uri': credentials['redirect_urls'][0],
          'code_verifier': code_verifier
      })
      
      token_data = response.json()
      return token_data['access_token']
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "refresh_token_here",
  "scope": "accounts payments"
}
```

## PKCE Implementation

PKCE (Proof Key for Code Exchange) adds an extra layer of security to the OAuth flow. Here's how to implement it:

<CodeGroup>
  ```javascript JavaScript theme={null}
  // Generate PKCE challenge and verifier
  const generatePKCE = () => {
    const codeVerifier = crypto.randomUUID();
    const codeChallenge = btoa(codeVerifier);
    return { codeVerifier, codeChallenge };
  };

  // Store code verifier securely (session storage, etc.)
  const { codeVerifier, codeChallenge } = generatePKCE();
  sessionStorage.setItem('code_verifier', codeVerifier);

  // Use code challenge in authorization request
  const authUrl = `https://sandbox-api.sparkle.ng/oauth/authorize?` +
    `client_id=${clientId}&` +
    `redirect_uri=${encodeURIComponent(redirectUri)}&` +
    `response_type=code&` +
    `scope=accounts payments&` +
    `code_challenge=${codeChallenge}&` +
    `code_challenge_method=S256`;

  // Retrieve code verifier for token exchange
  const storedCodeVerifier = sessionStorage.getItem('code_verifier');
  ```

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

  def generate_pkce():
      # Generate code verifier
      code_verifier = secrets.token_urlsafe(32)
      
      # Generate code challenge
      code_challenge = base64.urlsafe_b64encode(
          hashlib.sha256(code_verifier.encode()).digest()
      ).decode().rstrip('=')
      
      return code_verifier, code_challenge

  # Generate and store PKCE values
  code_verifier, code_challenge = generate_pkce()
  # Store code_verifier securely (session, database, etc.)
  ```
</CodeGroup>

## Available Scopes

| Scope             | Description                           | Access Level              |
| ----------------- | ------------------------------------- | ------------------------- |
| `accounts`        | Read account information and balances | Account data access       |
| `payments`        | Initiate payments and transfers       | Payment initiation        |
| `transactions`    | Read transaction history              | Transaction data access   |
| `standing_orders` | Manage standing orders                | Standing order management |

## Using Access Tokens

Once you have an access token, include it in the `Authorization` header for all API requests:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET https://sandbox-api.sparkle.ng/accounts \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "X-Participant-ID: YOUR_PARTICIPANT_ID" \
    -H "X-Connection-ID: YOUR_CONNECTION_ID"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://sandbox-api.sparkle.ng/accounts', {
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'X-Participant-ID': participantId,
      'X-Connection-ID': connectionId
    }
  });
  ```

  ```python Python theme={null}
  response = requests.get('https://sandbox-api.sparkle.ng/accounts', headers={
      'Authorization': f'Bearer {access_token}',
      'X-Participant-ID': participant_id,
      'X-Connection-ID': connection_id
  })
  ```
</CodeGroup>

## Token Refresh

Access tokens expire after 1 hour. Use the refresh token to get a new access token:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://sandbox-api.sparkle.ng/oauth/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=refresh_token&client_id=your_client_id&client_secret=your_client_secret&refresh_token=your_refresh_token"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://sandbox-api.sparkle.ng/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      client_id: 'your_client_id',
      client_secret: 'your_client_secret',
      refresh_token: 'your_refresh_token'
    })
  });

  const newTokenData = await response.json();
  ```
</CodeGroup>

## Security Best Practices

### 1. Secure Storage

* Store client secrets server-side only
* Use environment variables for sensitive data
* Never expose credentials in client-side code

### 2. Token Management

* Store access tokens securely
* Implement automatic token refresh
* Clear tokens on logout

### 3. PKCE Implementation

* Always use PKCE for web applications
* Generate cryptographically secure random values
* Store code verifier securely during the flow

### 4. Redirect URI Validation

* Use exact redirect URI matching
* Avoid wildcard redirect URIs
* Validate redirect URIs server-side

### 5. State Parameter

* Always include a random state parameter
* Validate state parameter on callback
* Use cryptographically secure random values

## Error Handling

Common OAuth errors and how to handle them:

| Error                  | Description                | Solution                                      |
| ---------------------- | -------------------------- | --------------------------------------------- |
| `invalid_client`       | Invalid client credentials | Check your client\_id and client\_secret      |
| `invalid_grant`        | Invalid authorization code | Ensure code is used only once and not expired |
| `invalid_redirect_uri` | Redirect URI mismatch      | Verify redirect URI matches registration      |
| `invalid_scope`        | Invalid scope requested    | Check scope format and permissions            |
| `access_denied`        | User denied consent        | Handle gracefully and retry                   |

<Note>
  Always implement proper error handling and provide clear feedback to users when authentication fails.
</Note>

## Testing Authentication

Use our sandbox environment to test your authentication implementation:

1. Register your application in sandbox
2. Test the complete OAuth flow
3. Verify token exchange and API calls
4. Test error scenarios and edge cases

<Button href="/quickstart" size="sm">
  Try the Quickstart Guide
</Button>
