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

## How it works

```
Browser                     Your Server                   Plivo
  |                             |                           |
  | 1. Request token            |                           |
  | --------------------------> |                           |
  |                             | 2. POST /JWT/Token/       |
  |                             | ------------------------> |
  |                             |                           |
  |                             | 3. Returns signed JWT     |
  |                             | <------------------------ |
  | 4. Return JWT               |                           |
  | <-------------------------- |                           |
  |                             |                           |
  | 5. loginWithAccessToken()   |                           |
  | -------------------------------------------------------->
  |                             |                           |
  | 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`.
</Note>

## Prerequisites

Before generating JWT tokens, you need:

* A Plivo account with `auth_id` and `auth_token` ([sign up](https://console.plivo.com/accounts/register/))
* A [Plivo Application](/account/api/application/) that defines your answer and hangup webhook URLs
* A [Plivo Endpoint](/voice/api/endpoints/) 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.
</Note>

### Server-side examples

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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"
      }'
    ```
  </Tab>
</Tabs>

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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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
    ```
  </Tab>
</Tabs>

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

For more details, see the [Application API reference](/account/api/application/).

### Create an endpoint

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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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
    ```
  </Tab>
</Tabs>

<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.
</Note>

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](/voice/api/endpoints/).

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