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

# Control access using JWTs

> Learn how to add access control to a content with Livepeer UI Kit, using JWTs

Using JSON Web Tokens (JWTs) provides a robust way to control access to both
your assets and livestreams. The JWTs can be signed and validated to ensure that
only authorized users can access the content. Below are examples for both assets
and livestreams.

Adding access control to a content only takes a few lines of code.

<Info>
  This guide is written for developers using `@livepeer/react` in a React
  application.
</Info>

## Create Gated Content

### For livestreams

Create your gated stream, with the stream key returned once we create it
(styling has been removed for simplicity)

```tsx accessControl.tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
import { Livepeer } from "livepeer";
import { TypeT } from "livepeer/dist/models/components";

const livepeer = new Livepeer({
  apiKey: process.env.STUDIO_API_KEY ?? "",
});

await livepeer.stream.create({
  name: "...",
  playbackPolicy: {
    type: TypeT.Jwt,
  },
});
```

### For assets

Create your gated asset, with the jwt playback policy type.

```tsx accessControl.ts theme={"theme":{"light":"github-light","dark":"dark-plus"}}
import { Livepeer } from "livepeer";
import { TypeT } from "livepeer/dist/models/components";

const livepeer = new Livepeer({
  apiKey: process.env.STUDIO_API_KEY ?? "",
});

await livepeer.asset.create({
  name: "...",
  playbackPolicy: {
    type: TypeT.Jwt,
  },
});
```

## Sign a JWT (Node.JS API Route)

Next, we add an API route - since we are using Next.JS, we add a custom
[Next.js API route](https://nextjs.org/docs/api-routes/introduction). We add a
check in the API route for a special "secret" that must be passed in the POST
body for the user to gain access to the stream.

<Info>
  Make sure to [create a signing key](/api-reference/signing-key/create) - those
  values will be used as the environment variables `ACCESS_CONTROL_PRIVATE_KEY`
  and `NEXT_PUBLIC_ACCESS_CONTROL_PUBLIC_KEY`.
</Info>

```typescript /api/sign-jwt.ts theme={"theme":{"light":"github-light","dark":"dark-plus"}}
import { signAccessJwt } from "@livepeer/core/crypto";
import { NextApiRequest, NextApiResponse } from "next";

import { ApiError } from "../../lib/error";

export type CreateSignedPlaybackBody = {
  playbackId: string;
  secret: string;
};

export type CreateSignedPlaybackResponse = {
  token: string;
};

const accessControlPrivateKey = process.env.ACCESS_CONTROL_PRIVATE_KEY;
const accessControlPublicKey =
  process.env.NEXT_PUBLIC_ACCESS_CONTROL_PUBLIC_KEY;

const handler = async (
  req: NextApiRequest,
  res: NextApiResponse<CreateSignedPlaybackResponse | ApiError>
) => {
  try {
    const method = req.method;

    if (method === "POST") {
      if (!accessControlPrivateKey || !accessControlPublicKey) {
        return res
          .status(500)
          .json({ message: "No private/public key configured." });
      }

      const { playbackId, secret }: CreateSignedPlaybackBody = req.body;

      if (!playbackId || !secret) {
        return res.status(400).json({ message: "Missing data in body." });
      }

      // we check that the "supersecretkey" was passed in the body
      // this could be a more complex check, like taking a signed payload,
      // getting the address for that signature, and fetching if they own an NFT
      //
      // https://docs.ethers.io/v5/single-page/#/v5/api/utils/signing-key/-%23-SigningKey--other-functions
      if (secret !== "supersecretkey") {
        return res.status(401).json({ message: "Incorrect secret." });
      }

      // we sign the JWT and return it to the user
      const token = await signAccessJwt({
        privateKey: accessControlPrivateKey,
        publicKey: accessControlPublicKey,
        issuer: "https://docs.livepeer.org",
        // playback ID to include in the JWT
        playbackId,
        // expire the JWT in 1 hour
        expiration: "1h",
        // custom metadata to include
        custom: {
          userId: "user-id-1",
        },
      });

      return res.status(200).json({
        token,
      });
    }

    res.setHeader("Allow", ["POST"]);
    return res.status(405).end(`Method ${method} Not Allowed`);
  } catch (err) {
    console.error(err);
    return res
      .status(500)
      .json({ message: (err as Error)?.message ?? "Error" });
  }
};

export default handler;
```

### Configure the Player

Lastly, when the content is created, we make a POST request to the
`/api/create-signed-jwt` API route we created in the previous step.

<Info>
  The React Player passes the JWT with a header, `Livepeer-Jwt`, to the backend,
  for WebRTC and HLS playback. For MP4 playback, it uses a query parameter,
  `jwt`.
</Info>

Then, we pass the JWT to the Player using the
[`jwt`](/sdks/react/player/Root#jwt) prop, which will use that JWT to prove
access to the content!

```tsx access-control.tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
import * as Player from "@livepeer/react/player";

export const AccessControl = () => {
  return (
    <Player.Root src={src} jwt={jwt}>
      <Player.Container>
        <Player.Video />
      </Player.Container>
    </Player.Root>
  );
};
```

### Using a custom player

If you are not using the player, you will need to pass a header, `Livepeer-Jwt`,
when you perform WebRTC SDP negotiation, or when you play back from a m3u8 URL.

For WebRTC SDP negotiation, here is an example of the header being passed:

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
curl -X POST \
     -H "Content-Type: application/sdp" \
     -H "Livepeer-Jwt: your-jwt" \
     --data-binary "@sdpfile.sdp" \
     "https://livepeercdn.studio/webrtc/abcd1234"
```

You can also append the JWT to the WebRTC URL as a query parameter, similar to:

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
curl -X POST \
     -H "Content-Type: application/sdp" \
     --data-binary "@sdpfile.sdp" \
     "https://livepeercdn.studio/webrtc/abcd1234?jwt=your-jwt"
```

Similarly, for HLS playback, you can pass the JWT in a header:

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
curl -X GET \
     -H "Livepeer-Jwt: your-jwt" \
     "https://playback.livepeer.studio/asset/hls/abcd1234/index.m3u8"
```

<Info>
  If you are using HLS.js for your own custom player, you can set the JWT header like this:

  ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
  const hlsConfig = {
    xhrSetup: function (xhr, url) {
      xhr.setRequestHeader("Livepeer-Jwt", "your-jwt");
    },
  };
  ```
</Info>

Finally, you can append the JWT to the m3u8 URL as a query parameter:

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
curl -X GET \
     "https://playback.livepeer.studio/asset/hls/abcd1234/index.m3u8?jwt=your-jwt"
```
