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

# JWT Authentication

> Generate JWT access tokens for the Plivo Browser SDK to authenticate browser sessions without exposing endpoint credentials.

## Introduction

The Plivo Browser SDK supports two authentication methods for registering SIP endpoints:

1. **Username/Password**: `client.login(username, password)`
2. **JWT Access Token**: `client.loginWithAccessToken(jwt)` *(recommended, added in v2.2.16)*

JWT tokens are short-lived, server-signed tokens that authenticate a browser session against a Plivo SIP endpoint without exposing endpoint credentials to the client. This makes JWT the recommended approach for production applications.

> [!NOTE]
>
> JWT authentication requires `plivo-browser-sdk` v2.2.16 or later. Earlier versions only support username/password authentication.

## How it works

```mermaid theme={null}
sequenceDiagram
    participant B as Browser
    participant S as Your Server
    participant P as Plivo
    B->>S: (1) Request token
    S->>P: (2) POST /JWT/Token/
    P-->>S: (3) Returns signed JWT
    S-->>B: (4) Return JWT
    B->>P: (5) loginWithAccessToken()
    P-->>B: (6) SIP REGISTER (WebRTC)
```

1. Your browser app requests a JWT from your backend server.
2. Your server calls the Plivo REST API to generate a signed token.
3. Plivo returns a signed JWT.
4. Your server passes the JWT to the browser.
5. The Browser SDK uses the JWT to register with Plivo via `loginWithAccessToken()`.
6. Plivo validates the token and completes SIP registration over WebRTC.

> [!NOTE]
>
> JWTs **must** be generated server-side via the Plivo REST API. Locally-signed JWTs (using libraries like `jsonwebtoken`) are rejected by Plivo's SIP infrastructure, even if signed with your `auth_token`.

## Prerequisites

Before generating JWT tokens, you need:

* A Plivo account with `auth_id` and `auth_token` ([sign up](https://cx.plivo.com/signup))
* A [Plivo Application](https://plivo.com/docs/account/api/application.md) that defines your answer and hangup webhook URLs
* A [Plivo Endpoint](https://plivo.com/docs/voice/api/endpoints.md) linked to the application
* `plivo-browser-sdk` v2.2.16+ installed in your frontend

If you don't have an application and endpoint yet, see [Setting up an application and endpoint](#setting-up-an-application-and-endpoint) below.

## Generating a JWT token

Use the Plivo REST API to generate a signed JWT for a specific endpoint.

### API endpoint

```
POST https://api.plivo.com/v1/Account/{auth_id}/JWT/Token/
```

### Authentication

Use HTTP Basic Auth with your Plivo `auth_id` and `auth_token`.

### Request body

| Field | Type | Required | Description |
| - | - | - | - |
| `iss` | string | Yes | Your Plivo `auth_id`. |
| `sub` | string | Yes | The subject identifier for the token. |
| `nbf` | number | Yes | Not Before timestamp (Unix seconds). The token is invalid before this time. |
| `exp` | number | Yes | Expiration timestamp (Unix seconds). The token is invalid after this time. Maximum allowed validity is 24 hours. |
| `per` | object | Yes | Permissions object. See [Permissions](#permissions). |
| `app` | string | No | Plivo Application ID. Associates the session with a specific application for call routing. |

### Permissions

The `per` object controls what the authenticated endpoint can do:

```json theme={null}
{
  "voice": {
    "incoming_allow": true,
    "outgoing_allow": true
  }
}
```

| Field | Type | Description |
| - | - | - |
| `incoming_allow` | boolean | Whether the endpoint can receive inbound calls. |
| `outgoing_allow` | boolean | Whether the endpoint can make outbound calls. |

### Response

A successful request returns a JSON object containing the signed JWT:

```json theme={null}
{
  "api_id": "2c09a7fc-1234-11ee-b979-0242ac110002",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6InBsaXZvO3Y9MSJ9..."
}
```

The returned JWT has the following structure:

| Part | Details |
| - | - |
| **Header** | `{ "alg": "HS256", "typ": "JWT", "cty": "plivo;v=1" }` |
| **Payload** | Contains `iss`, `sub`, `nbf`, `exp`, `per`, `app`, plus Plivo-added fields |
| **Signature** | HMAC-SHA256, signed by Plivo (not your `auth_token`) |

> [!NOTE]
>
> The `cty: "plivo;v=1"` header is added automatically by the Plivo REST API when generating tokens. Plivo's server validates this field during SIP registration.

### Server-side examples

> [tab] Node.js
>
> ```javascript theme={null}
> // Express.js route handler
> app.post("/api/plivo-token", async (req, res) => {
>   const authId = process.env.PLIVO_AUTH_ID;
>   const authToken = process.env.PLIVO_AUTH_TOKEN;
>   const now = Math.floor(Date.now() / 1000);
>
>   const response = await fetch(
>     `https://api.plivo.com/v1/Account/${authId}/JWT/Token/`,
>     {
>       method: "POST",
>       headers: {
>         "Content-Type": "application/json",
>         Authorization: `Basic ${Buffer.from(`${authId}:${authToken}`).toString("base64")}`,
>       },
>       body: JSON.stringify({
>         iss: authId,
>         sub: "myendpoint",         // endpoint username
>         nbf: now,
>         exp: now + 300,            // 5 minutes
>         per: {
>           voice: {
>             incoming_allow: true,
>             outgoing_allow: true,
>           },
>         },
>         app: "77241325312960404",  // application ID
>       }),
>     }
>   );
>
>   if (!response.ok) {
>     return res.status(500).json({ error: "Failed to generate token" });
>   }
>
>   const data = await response.json();
>   res.json({ token: data.token });
> });
> ```

> [tab] Python
>
> ```python theme={null}
> import os
> import time
> import base64
> import requests
> from flask import Flask, jsonify
>
> app = Flask(__name__)
>
> @app.route("/api/plivo-token", methods=["POST"])
> def get_plivo_token():
>     auth_id = os.environ["PLIVO_AUTH_ID"]
>     auth_token = os.environ["PLIVO_AUTH_TOKEN"]
>     now = int(time.time())
>
>     credentials = base64.b64encode(
>         f"{auth_id}:{auth_token}".encode()
>     ).decode()
>
>     response = requests.post(
>         f"https://api.plivo.com/v1/Account/{auth_id}/JWT/Token/",
>         headers={
>             "Content-Type": "application/json",
>             "Authorization": f"Basic {credentials}",
>         },
>         json={
>             "iss": auth_id,
>             "sub": "myendpoint",         # endpoint username
>             "nbf": now,
>             "exp": now + 300,            # 5 minutes
>             "per": {
>                 "voice": {
>                     "incoming_allow": True,
>                     "outgoing_allow": True,
>                 }
>             },
>             "app": "77241325312960404",  # application ID
>         },
>     )
>
>     if not response.ok:
>         return jsonify({"error": "Failed to generate token"}), 500
>
>     data = response.json()
>     return jsonify({"token": data["token"]})
> ```

> [tab] cURL
>
> ```bash theme={null}
> curl -X POST "https://api.plivo.com/v1/Account/{auth_id}/JWT/Token/" \
>   -H "Content-Type: application/json" \
>   -u '<auth_id>:<auth_token>' \
>   -d '{
>     "iss": "{auth_id}",
>     "sub": "myendpoint",
>     "nbf": 1700000000,
>     "exp": 1700000300,
>     "per": {
>       "voice": {
>         "incoming_allow": true,
>         "outgoing_allow": true
>       }
>     },
>     "app": "77241325312960404"
>   }'
> ```

## Browser SDK integration

### Login with a JWT

After fetching a JWT from your server, use `loginWithAccessToken()` to register with Plivo:

```javascript theme={null}
import Plivo from "plivo-browser-sdk";

// Initialize the SDK
const plivoBrowserSdk = new window.Plivo({
  debug: "INFO",
  permOnClick: true,
  enableTracking: true,
});

// Set up event handlers
plivoBrowserSdk.client.on("onLogin", () => {
  console.log("Registered with Plivo successfully");
  // Ready to make or receive calls
});

plivoBrowserSdk.client.on("onLoginFailed", (errorCode) => {
  const errorMessage = plivoBrowserSdk.client.getErrorStringByErrorCodes(errorCode);
  console.error("Login failed:", errorCode, errorMessage);
});

// Fetch JWT from your server
const response = await fetch("/api/plivo-token", { method: "POST" });
const { token } = await response.json();

// Register using JWT
plivoBrowserSdk.client.loginWithAccessToken(token);
```

### Making a call after login

Once registered, you can make outbound calls:

```javascript theme={null}
plivoBrowserSdk.client.on("onCallAnswered", (callInfo) => {
  console.log("Call connected:", callInfo);
});

plivoBrowserSdk.client.on("onCallTerminated", (hangupInfo, callInfo) => {
  console.log("Call ended:", hangupInfo);
});

// Call a phone number
plivoBrowserSdk.client.call("+14155551234");

// Or call with custom SIP headers
plivoBrowserSdk.client.call("+14155551234", {
  "X-PH-SessionId": "session-abc-123",
});
```

### Refreshing tokens

JWT tokens are short-lived. If you need to re-register (for example, after a network disconnect), fetch a new token and call `loginWithAccessToken()` again:

```javascript theme={null}
plivoBrowserSdk.client.on("onConnectionChange", async (info) => {
  if (info.state === "connected") {
    // WebSocket reconnected - re-register with a fresh token
    const response = await fetch("/api/plivo-token", { method: "POST" });
    const { token } = await response.json();
    plivoBrowserSdk.client.loginWithAccessToken(token);
  }
});
```

## Setting up an application and endpoint

If you don't already have a Plivo Application and Endpoint, create them before generating JWT tokens.

### Create an application

A Plivo Application defines the webhook URLs that Plivo calls when a browser-initiated call connects.

> [tab] cURL
>
> ```bash theme={null}
> curl -X POST "https://api.plivo.com/v1/Account/{auth_id}/Application/" \
>   -H "Content-Type: application/json" \
>   -u '<auth_id>:<auth_token>' \
>   -d '{
>     "app_name": "my-browser-app",
>     "answer_url": "https://your-server.com/answer",
>     "answer_method": "POST",
>     "hangup_url": "https://your-server.com/hangup",
>     "hangup_method": "POST"
>   }'
> ```

> [tab] Python
>
> ```python theme={null}
> import plivo
>
> client = plivo.RestClient("<auth_id>", "<auth_token>")
> response = client.applications.create(
>     app_name="my-browser-app",
>     answer_url="https://your-server.com/answer",
>     answer_method="POST",
>     hangup_url="https://your-server.com/hangup",
>     hangup_method="POST",
> )
> print(response)  # includes app_id
> ```

> [tab] Node.js
>
> ```javascript theme={null}
> const plivo = require("plivo");
>
> const client = new plivo.Client("<auth_id>", "<auth_token>");
> client.applications
>   .create("my-browser-app", {
>     answerUrl: "https://your-server.com/answer",
>     answerMethod: "POST",
>     hangupUrl: "https://your-server.com/hangup",
>     hangupMethod: "POST",
>   })
>   .then((response) => console.log(response));
> // response includes appId
> ```

The response includes an `app_id`. Save this for creating endpoints and generating JWT tokens.

For more details, see the [Application API reference](https://plivo.com/docs/account/api/application.md).

### Create an endpoint

A Plivo Endpoint is a SIP identity that the Browser SDK registers as. Each endpoint must be linked to an application.

> [tab] cURL
>
> ```bash theme={null}
> curl -X POST "https://api.plivo.com/v1/Account/{auth_id}/Endpoint/" \
>   -H "Content-Type: application/json" \
>   -u '<auth_id>:<auth_token>' \
>   -d '{
>     "username": "myendpoint",
>     "password": "a-strong-random-password",
>     "alias": "my-browser-endpoint",
>     "app_id": "77241325312960404"
>   }'
> ```

> [tab] Python
>
> ```python theme={null}
> import plivo
>
> client = plivo.RestClient("<auth_id>", "<auth_token>")
> response = client.endpoints.create(
>     username="myendpoint",
>     password="a-strong-random-password",
>     alias="my-browser-endpoint",
>     app_id="77241325312960404",
> )
> print(response)  # includes endpoint_id and username
> ```

> [tab] Node.js
>
> ```javascript theme={null}
> const plivo = require("plivo");
>
> const client = new plivo.Client("<auth_id>", "<auth_token>");
> client.endpoints
>   .create("myendpoint", "a-strong-random-password", "my-browser-endpoint", "77241325312960404")
>   .then((response) => console.log(response));
> // response includes endpointId and username
> ```

> [!NOTE]
>
> **Endpoint constraints:**
>
> * `username`: Alphanumeric characters only, 1-25 characters, must start with an alphabetic character.
> * `alias`: Letters, numbers, hyphens, and underscores only.
> * `password`: At least 5 characters. Only used at creation time; the Browser SDK authenticates via JWT, not the endpoint password.

The endpoint username from the response is the value you pass as `sub` when generating JWT tokens. For more details, see the [Endpoint API reference](https://plivo.com/docs/voice/api/endpoints.md).

## Error codes

When JWT authentication fails, the `onLoginFailed` event returns a numeric error code. Use `getErrorStringByErrorCodes()` to get a human-readable message.

| Error Code | Error Name | Description |
| - | - | - |
| 10001 | `INVALID_ACCESS_TOKEN` | The access token is invalid. |
| 10002 | `INVALID_ACCESS_TOKEN_HEADER` | The access token header is invalid. |
| 10003 | `INVALID_ACCESS_TOKEN_ISSUER` | The token issuer (`iss`) is invalid. |
| 10004 | `INVALID_ACCESS_TOKEN_SUBJECT` | The token subject (`sub`) is invalid. |
| 10005 | `ACCESS_TOKEN_NOT_VALID_YET` | The current time is before the token's `nbf` timestamp. |
| 10006 | `ACCESS_TOKEN_EXPIRED` | The token's `exp` timestamp has passed. Generate a new token. |
| 10007 | `INVALID_ACCESS_TOKEN_SIGNATURE` | The token signature is invalid. Ensure the token was generated via the Plivo REST API. |
| 10008 | `INVALID_ACCESS_TOKEN_GRANTS` | The `per` permissions object is missing or invalid. |
| 10009 | `EXPIRATION_EXCEEDS_MAX_ALLOWED_TIME` | The token expiration exceeds the maximum allowed duration. |
| 10010 | `MAX_ALLOWED_LOGIN_REACHED` | The maximum number of concurrent logins has been reached. |

### Handling errors in code

```javascript theme={null}
plivoBrowserSdk.client.on("onLoginFailed", (errorCode) => {
  const errorMessage = plivoBrowserSdk.client.getErrorStringByErrorCodes(errorCode);
  console.error(`Login failed [${errorCode}]: ${errorMessage}`);

  // Handle specific cases
  if (errorCode === 10006) {
    // Token expired - fetch a fresh one and retry
    refreshAndRelogin();
  }
});
```

## Best practices

1. **Keep tokens short-lived.** A validity of 5 minutes is recommended. The Browser SDK maintains the SIP registration after login; re-authentication is only needed when the session disconnects.

2. **Match the endpoint to the application.** Each endpoint is linked to a specific application via `app_id`. The `app` field in the JWT should reference the same application the endpoint is registered to, otherwise call routing may behave unexpectedly.

3. **Never expose credentials to the browser.** Your `auth_id` and `auth_token` should only be used server-side. The browser should only receive the signed JWT.

4. **One endpoint per concurrent session.** While Plivo allows multiple simultaneous registrations for the same endpoint, this is only reliable for outbound-only use cases. For inbound call routing, use a unique endpoint per browser session.
