# Authentication

## Overview

The SDK uses JWT token-based authentication to keep your API credentials secure. Instead of exposing your Client ID and Secret in browser code, tokens are generated server-side by your backend.

***

## Why Token-Based Authentication?

{% hint style="success" %}
**Security Benefits:**

* API credentials (Client ID/Secret) never leave your server
* Tokens are scoped to specific customers and invoices
* Tokens have limited lifetime, reducing exposure risk
* Token refresh is handled automatically by the SDK
  {% endhint %}

***

## Authentication Flow

```
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Your           │     │  Your           │     │  Alternative    │
│  Backend        │     │  Frontend       │     │  API            │
└────────┬────────┘     └────────┬────────┘     └────────┬────────┘
         │                       │                       │
         │  1. POST /v1/checkout-auth/init              │
         │  (with OAuth2 client credentials)            │
         │──────────────────────────────────────────────>│
         │                       │                       │
         │  2. Returns JWT token                        │
         │<──────────────────────────────────────────────│
         │                       │                       │
         │  3. Pass token to frontend                   │
         │──────────────────────>│                       │
         │                       │                       │
         │                       │  4. SDK uses token    │
         │                       │  for all API calls    │
         │                       │──────────────────────>│
         │                       │                       │
         │                       │  5. Token expires     │
         │                       │  onAccessTokenExpired │
         │                       │  callback triggered   │
         │                       │<─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│
         │                       │                       │
         │  6. Frontend requests │                       │
         │  new token            │                       │
         │<──────────────────────│                       │
         │                       │                       │
         │  7. New token returned│                       │
         │──────────────────────>│                       │
         │                       │                       │
         │                       │  8. SDK continues     │
         │                       │  with new token       │
         │                       │──────────────────────>│
```

***

## Backend Implementation

### Step 1: Get OAuth Token

First, exchange your Client ID and Secret for an OAuth access token:

```bash
curl -X POST https://public-api.alternativepayments.io/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
  -d "grant_type=client_credentials"
```

Response:

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 3600
}
```

### Step 2: Generate Checkout Token

Use the OAuth token to generate a checkout token for a specific customer and invoice:

```bash
curl -X POST https://public-api.alternativepayments.io/v1/checkout-auth/init \
  -H "Authorization: Bearer {oauth_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "cus_xxx",
    "invoice_id": "inv_xxx"
  }'
```

Response:

```json
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "expires_at": 1703980800
}
```

### Complete Backend Example

{% tabs %}
{% tab title="Node.js (Express)" %}

```typescript
import express from 'express';

const router = express.Router();

const CLIENT_ID = process.env.ALTERNATIVE_CLIENT_ID;
const CLIENT_SECRET = process.env.ALTERNATIVE_CLIENT_SECRET;
const API_BASE = 'https://public-api.alternativepayments.io';

// Cache OAuth token
let oauthToken: { token: string; expiresAt: number } | null = null;

async function getOAuthToken(): Promise<string> {
  // Return cached token if valid
  if (oauthToken && oauthToken.expiresAt > Date.now() + 60000) {
    return oauthToken.token;
  }

  const response = await fetch(`${API_BASE}/oauth/token`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Basic ${Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')}`,
    },
    body: 'grant_type=client_credentials',
  });

  const data = await response.json();
  oauthToken = {
    token: data.access_token,
    expiresAt: Date.now() + data.expires_in * 1000,
  };

  return data.access_token;
}

router.post('/api/checkout-token', async (req, res) => {
  try {
    const { customerId, invoiceId } = req.body;

    // Validate inputs
    if (!customerId || !invoiceId) {
      return res.status(400).json({ error: 'customerId and invoiceId are required' });
    }

    // Get OAuth token
    const accessToken = await getOAuthToken();

    // Generate checkout token
    const response = await fetch(`${API_BASE}/v1/checkout-auth/init`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        customer_id: customerId,
        invoice_id: invoiceId,
      }),
    });

    if (!response.ok) {
      const error = await response.json();
      return res.status(response.status).json(error);
    }

    const data = await response.json();
    res.json({
      token: data.token,
      expiresAt: data.expires_at,
    });
  } catch (error) {
    console.error('Failed to generate checkout token:', error);
    res.status(500).json({ error: 'Failed to generate token' });
  }
});

export default router;
```

{% endtab %}

{% tab title="Python (FastAPI)" %}

```python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import os
import base64
import time

app = FastAPI()

CLIENT_ID = os.environ["ALTERNATIVE_CLIENT_ID"]
CLIENT_SECRET = os.environ["ALTERNATIVE_CLIENT_SECRET"]
API_BASE = "https://public-api.alternativepayments.io"

# Token cache
oauth_token = {"token": None, "expires_at": 0}


async def get_oauth_token() -> str:
    global oauth_token

    # Return cached token if valid
    if oauth_token["token"] and oauth_token["expires_at"] > time.time() + 60:
        return oauth_token["token"]

    credentials = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()

    async with httpx.AsyncClient() as client:
        response = await client.post(
            f"{API_BASE}/oauth/token",
            headers={
                "Content-Type": "application/x-www-form-urlencoded",
                "Authorization": f"Basic {credentials}",
            },
            data="grant_type=client_credentials",
        )

    data = response.json()
    oauth_token = {
        "token": data["access_token"],
        "expires_at": time.time() + data["expires_in"],
    }

    return data["access_token"]


class TokenRequest(BaseModel):
    customer_id: str
    invoice_id: str


@app.post("/api/checkout-token")
async def generate_checkout_token(request: TokenRequest):
    try:
        access_token = await get_oauth_token()

        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{API_BASE}/v1/checkout-auth/init",
                headers={
                    "Authorization": f"Bearer {access_token}",
                    "Content-Type": "application/json",
                },
                json={
                    "customer_id": request.customer_id,
                    "invoice_id": request.invoice_id,
                },
            )

        if response.status_code != 200:
            raise HTTPException(status_code=response.status_code, detail=response.json())

        data = response.json()
        return {"token": data["token"], "expiresAt": data["expires_at"]}

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
```

{% endtab %}
{% endtabs %}

***

## Frontend Implementation

### Initialize with Token

```typescript
import { AlternativeClient } from '@getalternative/partner-sdk';

// Fetch token from your backend
const { token } = await fetch('/api/checkout-token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ customerId: 'cus_xxx', invoiceId: 'inv_xxx' }),
}).then(res => res.json());

// Initialize SDK
const client = await AlternativeClient.create({
  accessToken: token,
  environment: 'production',
});
```

### Handle Token Expiration

Provide an `onAccessTokenExpired` callback to automatically refresh tokens:

```typescript
const client = await AlternativeClient.create({
  accessToken: token,
  environment: 'production',
  onAccessTokenExpired: async () => {
    // This is called when the SDK receives a 401 response
    console.log('Token expired, fetching new one...');

    const response = await fetch('/api/checkout-token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ customerId: 'cus_xxx', invoiceId: 'inv_xxx' }),
    });
    const { token } = await response.json();

    return token; // Return the new token
  },
});
```

{% hint style="warning" %}
If `onAccessTokenExpired` is not provided and the token expires, the SDK will throw an `AccessTokenExpiredError`.
{% endhint %}

***

## Error Handling

```typescript
import { AccessTokenExpiredError } from '@getalternative/partner-sdk';

try {
  // SDK operations
} catch (error) {
  if (error instanceof AccessTokenExpiredError) {
    // Token expired and no refresh callback was provided
    // Redirect user to re-authenticate or show error
    console.error('Session expired. Please refresh the page.');
  }
}
```

***

## Security Best Practices

1. **Never expose credentials in frontend code** - Always generate tokens on your backend
2. **Use HTTPS** - Ensure all communication is encrypted
3. **Validate on your backend** - Verify the customer/invoice belongs to the authenticated user
4. **Set appropriate token lifetimes** - Shorter lifetimes reduce exposure risk
5. **Implement the refresh callback** - Provide a smooth experience when tokens expire

***

## Token Contents

The checkout token is a JWT that contains:

| Claim         | Description                          |
| ------------- | ------------------------------------ |
| `partner_id`  | Your partner identifier              |
| `client_id`   | Your API client ID                   |
| `customer_id` | The customer this token is scoped to |
| `invoice_id`  | The invoice this token is scoped to  |
| `exp`         | Token expiration timestamp           |

{% hint style="info" %}
The SDK automatically extracts customer and invoice information from the token, so you don't need to pass these separately to components.
{% endhint %}
