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

# Secure Red5 Pro Streams with JWT Authentication

> Secure Red5 Pro streams with signed JWTs your backend issues. Red5 Pro validates tokens locally with no external calls, supporting role and room controls.

JWT Authentication lets you control who can publish and subscribe to your Red5 Pro streams using JSON Web Tokens (JWTs). Your backend issues a signed token for each authenticated user; the client passes that token when connecting to Red5 Pro, which validates it locally using a shared secret — with no external calls during the stream. This approach scales naturally and integrates with any existing identity provider or authentication service.

## How it works

<Steps>
  <Step title="User authenticates with your backend">
    Your application handles user login however you prefer — password, OAuth, SSO, or any other mechanism.
  </Step>

  <Step title="Your backend issues a signed JWT">
    After validating the user, your backend generates a JWT signed with the shared secret and returns it to the client over HTTPS.
  </Step>

  <Step title="Client passes the token to Red5 Pro">
    When the client connects to publish or subscribe, it includes the JWT as a connection parameter.
  </Step>

  <Step title="Red5 Pro validates the token locally">
    Red5 Pro verifies the cryptographic signature, checks expiration, and evaluates any role, transport, or room claims — all without contacting your backend.
  </Step>
</Steps>

<Tip>
  Because validation is local, JWT Authentication adds no latency to stream
  operations. It is ideal for scaled and distributed deployments.
</Tip>

## Prerequisites

Before configuring JWT Authentication, you need:

* The `red5pro-simple-auth-plugin` installed on your Red5 Pro server (present by default)
* A JWT-issuing service or identity provider
* A shared secret key of at least 32 bytes, used by both your issuing service and Red5 Pro

## Step 1 — Configure the application

Add the `jwtAuthenticator` and `simpleAuthSecurity` beans to your application's context file. For the `live` application, that file is `RED5_HOME/webapps/live/WEB-INF/red5-web.xml`. These beans are already present in the file but commented out — uncomment and adjust them.

```xml theme={null}
<!-- RED5_HOME/webapps/live/WEB-INF/red5-web.xml -->

<bean id="jwtAuthenticator"
      class="com.red5pro.server.plugin.simpleauth.datasource.impl.jwt.JwtAuthenticator"
      init-method="initialize">
    <property name="adapter"       ref="web.handler" />
    <property name="context"       ref="web.context" />
    <property name="jwtSecret"     value="${jwt.secret}" />
    <property name="jwtTtlMinutes" value="${jwt.ttl.minutes}" />
    <property name="expectedIssuer" value="${jwt.issuer}" />
</bean>

<bean id="simpleAuthSecurity"
      class="com.red5pro.server.plugin.simpleauth.Configuration"
      depends-on="jwtAuthenticator">
    <property name="active"                     value="${auth.active}" />
    <property name="rtmp"                       value="${auth.rtmp}" />
    <property name="rtsp"                       value="${auth.rtsp}" />
    <property name="rtc"                        value="${auth.rtc}" />
    <property name="srt"                        value="${auth.srt}" />
    <property name="mpegts"                     value="${auth.mpegts}" />
    <property name="rtmpAllowQueryParamsEnabled" value="${auth.rtmpAllowQueryParamsEnabled}" />
    <property name="allowedRtmpAgents"           value="*" />
    <property name="validator"                   ref="jwtAuthenticator" />
</bean>
```

## Step 2 — Set properties

Add the following section to `RED5_HOME/webapps/live/WEB-INF/red5-web.properties`. The property values are substituted into `red5-web.xml` at runtime.

```properties theme={null}
# RED5_HOME/webapps/live/WEB-INF/red5-web.properties

# Authentication on/off per protocol
auth.active=true
auth.rtmp=true
auth.rtsp=true
auth.rtc=true
auth.srt=false
auth.mpegts=false
auth.rtmpAllowQueryParamsEnabled=true

# JWT settings
jwt.secret=changeme-to-a-strong-32-byte-secret-key
jwt.ttl.minutes=60

# Optional: if set, tokens must have a matching iss claim
jwt.issuer=
```

<Warning>
  Replace `jwt.secret` with a cryptographically random value of at least 32
  bytes. This secret must match the secret your token-issuing service uses to
  sign JWTs. Never commit it to version control.
</Warning>

### Configuration properties reference

**Core (simpleAuthSecurity bean)**

| Property                      | Type    | Description                                                            |
| ----------------------------- | ------- | ---------------------------------------------------------------------- |
| `active`                      | Boolean | Enables or disables authentication for the application                 |
| `rtmp`                        | Boolean | Requires a valid JWT for RTMP connections                              |
| `rtsp`                        | Boolean | Requires a valid JWT for RTSP connections                              |
| `rtc`                         | Boolean | Requires a valid JWT for WebRTC connections                            |
| `srt`                         | Boolean | Requires a valid JWT for SRT connections                               |
| `mpegts`                      | Boolean | Requires a valid JWT for MPEG-TS connections                           |
| `rtmpAllowQueryParamsEnabled` | Boolean | Allows RTMP clients to pass the token in the URL query string          |
| `allowedRtmpAgents`           | String  | Semicolon-separated list of allowed RTMP agent strings; `*` allows all |

**Authenticator (jwtAuthenticator bean)**

| Property         | Type   | Description                                                                        |
| ---------------- | ------ | ---------------------------------------------------------------------------------- |
| `jwtSecret`      | String | Shared secret for validating JWT signatures                                        |
| `jwtTtlMinutes`  | Long   | Maximum allowed token lifetime in minutes; tokens with a longer `exp` are rejected |
| `expectedIssuer` | String | Optional: if set, tokens must have a matching `iss` claim                          |

## Step 3 — Generate tokens in your backend

Your backend service signs JWTs with the shared secret using the **HS256** algorithm. Never generate tokens on the client side or embed the secret in client code.

<CodeGroup>
  ```javascript Node.js theme={null}
  const jwt = require('jsonwebtoken');

  const payload = {
    sub: 'user123',                          // recommended: identifies the user
    iss: 'your-auth-service',               // required if jwt.issuer is set
    roles: 'red5-publisher,red5-subscriber', // controls publish/subscribe access
    'red5-transport': 'WHIP,WHEP',           // optional: restrict to WebRTC only
    'red5-room': 'room1',                    // optional: restrict to a specific room
    exp: Math.floor(Date.now() / 1000) + (60 * 60)  // 1 hour; must not exceed jwtTtlMinutes
  };

  const token = jwt.sign(payload, process.env.RED5_JWT_SECRET, { algorithm: 'HS256' });
  ```

  ```python Python theme={null}
  import jwt
  import time

  payload = {
      'sub': 'user123',
      'iss': 'your-auth-service',
      'roles': 'red5-publisher,red5-subscriber',
      'red5-transport': 'WHIP,WHEP',
      'red5-room': 'room1',
      'exp': int(time.time()) + 3600
  }

  token = jwt.encode(payload, os.environ['RED5_JWT_SECRET'], algorithm='HS256')
  ```

  ```java Java theme={null}
  import io.jsonwebtoken.Jwts;
  import io.jsonwebtoken.SignatureAlgorithm;
  import java.util.Date;

  long expMillis = System.currentTimeMillis() + 3600000; // 1 hour

  String token = Jwts.builder()
      .setSubject("user123")
      .setIssuer("your-auth-service")
      .claim("roles", "red5-publisher,red5-subscriber")
      .claim("red5-transport", "WHIP,WHEP")
      .claim("red5-room", "room1")
      .setExpiration(new Date(expMillis))
      .signWith(SignatureAlgorithm.HS256, System.getenv("RED5_JWT_SECRET"))
      .compact();
  ```
</CodeGroup>

### Token examples for common scenarios

<AccordionGroup>
  <Accordion title="Publisher-only token">
    Issue this to a broadcaster who should only be able to publish, not subscribe.

    ```javascript theme={null}
    const payload = {
      sub: 'broadcaster42',
      roles: 'red5-publisher',
      'red5-transport': 'WHIP',
      exp: Math.floor(Date.now() / 1000) + 3600
    };
    ```
  </Accordion>

  <Accordion title="Subscriber-only token">
    Issue this to a viewer who should only be able to subscribe, not publish.

    ```javascript theme={null}
    const payload = {
      sub: 'viewer99',
      roles: 'red5-subscriber',
      'red5-transport': 'WHEP',
      exp: Math.floor(Date.now() / 1000) + 3600
    };
    ```
  </Accordion>

  <Accordion title="Room-restricted token">
    Restrict the token to a specific room within the application scope.

    ```javascript theme={null}
    const payload = {
      sub: 'user789',
      roles: 'red5-publisher,red5-subscriber',
      'red5-room': 'conference',
      exp: Math.floor(Date.now() / 1000) + 3600
    };
    ```
  </Accordion>

  <Accordion title="Short-lived (near one-time) token">
    Use a 60-second expiry for highly sensitive access scenarios.

    ```javascript theme={null}
    const payload = {
      sub: 'tempuser',
      roles: 'red5-subscriber',
      exp: Math.floor(Date.now() / 1000) + 60
    };
    ```

    Set `jwt.ttl.minutes=2` on the server to tolerate up to 60 seconds of clock drift while still rejecting overly generous tokens.
  </Accordion>
</AccordionGroup>

## JWT claims reference

### Standard claims

| Claim  | Required    | Description                                                                            |
| ------ | ----------- | -------------------------------------------------------------------------------------- |
| `exp`  | Yes         | Expiration timestamp (Unix seconds). Must be in the future and within `jwtTtlMinutes`. |
| `sub`  | Recommended | Subject (username). Stored as a connection attribute for logging and application use.  |
| `iss`  | Conditional | Issuer. Required only if `expectedIssuer` is configured on the server. Case-sensitive. |
| `name` | Optional    | User's full name. Stored as a connection attribute if present.                         |

### Red5 Pro-specific claims

| Claim            | Required | Description                                                                                                              |
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| `roles`          | Optional | Comma-separated roles. Recognized values: `red5-publisher`, `red5-subscriber`. If absent, both roles are allowed.        |
| `red5-transport` | Optional | Comma-separated allowed protocols: `RTMP`, `RTSP`, `WHIP`, `WHEP`. Case-insensitive. If absent, any protocol is allowed. |
| `red5-room`      | Optional | Comma-separated allowed room names (sub-scopes under the application). If absent, any room is allowed.                   |

<Info>
  For the stream path `live/room1/stream1`, the room name is `room1`. If you
  set `red5-room`, clients cannot access application-level streams that have
  no room prefix.
</Info>

**Role matching is case-sensitive.** Use `red5-publisher` and `red5-subscriber` in lowercase. If your identity provider adds its own roles to the token (e.g., `admin`, `editor`), include the Red5 Pro roles alongside them:

```json theme={null}
{
  "roles": "admin,editor,red5-publisher,red5-subscriber"
}
```

## Step 4 — Pass the token from your client

Clients send the JWT as the `token` parameter in their connection configuration. Set `username` and `password` to any non-empty value (conventionally `"jwt"`).

<Tabs>
  <Tab title="WebRTC (HTML5 SDK)">
    ```javascript theme={null}
    var baseConfiguration = {
      host: 'your-server.com',
      app: 'live',
      iceServers: iceServers,
      bandwidth: desiredBandwidth,
      connectionParams: {
        username: 'jwt',
        password: 'jwt',
        token: jwtToken   // the JWT string from your backend
      }
    };
    ```
  </Tab>

  <Tab title="RTMP (ActionScript)">
    Pass `username`, `password`, and the JWT as connection arguments:

    ```actionscript3 theme={null}
    var nc:NetConnection = new NetConnection();
    nc.addEventListener(NetStatusEvent.NET_STATUS, onStatus);
    nc.connect("rtmp://your-server.com/live", "jwt", "jwt", jwtToken);

    function onStatus(ns:NetStatusEvent):void {
        trace(ns.info.code);
    }
    ```

    Or via query string if `rtmpAllowQueryParamsEnabled` is `true`:

    ```actionscript3 theme={null}
    nc.connect("rtmp://your-server.com/live?username=jwt&password=jwt&token=" + jwtToken);
    ```
  </Tab>

  <Tab title="RTSP — Android">
    ```java theme={null}
    R5Configuration config = new R5Configuration(
        R5StreamProtocol.RTSP,
        TestContent.GetPropertyString("host"),
        TestContent.GetPropertyInt("port"),
        TestContent.GetPropertyString("context"),
        TestContent.GetPropertyFloat("buffer_time"));

    config.setParameters("username=jwt;password=jwt;token=" + jwtToken + ";");
    R5Connection connection = new R5Connection(config);
    ```
  </Tab>

  <Tab title="RTSP — iOS">
    ```swift theme={null}
    func getConfig() -> R5Configuration {
        let config = R5Configuration()
        config.host        = Testbed.getParameter("host") as! String
        config.port        = Int32(Testbed.getParameter("port") as! Int)
        config.contextName = Testbed.getParameter("context") as! String
        config.parameters  = "username=jwt;password=jwt;token=\(jwtToken);"
        config.protocol    = 1
        config.buffer_time = Testbed.getParameter("buffer_time") as! Float
        return config
    }
    ```
  </Tab>
</Tabs>

<Note>
  Pass the complete JWT string — `header.payload.signature` — without a
  `Bearer ` prefix in the `token` parameter.
</Note>

## Security best practices

<AccordionGroup>
  <Accordion title="Protect the shared secret">
    The shared secret is the foundation of JWT security. Anyone who possesses it can mint valid tokens.

    * Store it in a secrets manager (HashiCorp Vault, AWS Secrets Manager, etc.)
    * Never commit it to version control
    * Use environment variables or files with restricted permissions
    * Rotate the secret on a regular schedule, coordinating the rotation between Red5 Pro and your issuing service

    Generate a strong secret:

    ```bash theme={null}
    # OpenSSL
    openssl rand -base64 32

    # Node.js
    node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
    ```
  </Accordion>

  <Accordion title="Set appropriate token expiry">
    Short-lived tokens limit the impact of token theft.

    * Standard interactive sessions: 15–60 minutes
    * High-security or near-one-time access: 60 seconds
    * Configure `jwt.ttl.minutes` slightly higher than your token TTL to tolerate clock drift between servers
  </Accordion>

  <Accordion title="Always use encrypted transport">
    * Send tokens from your backend to clients over HTTPS only
    * Use RTMPS, RTSPS, or WebRTC (encrypted by default) for the streaming connection
    * Never log or expose token strings in debug output or error messages
  </Accordion>

  <Accordion title="Enable issuer validation">
    Set `jwt.issuer` on the server to prevent tokens issued by other services from being accepted by Red5 Pro.
  </Accordion>
</AccordionGroup>

## When to use JWT Authentication

<CardGroup cols={2}>
  <Card title="Good fits" icon="circle-check">
    * Production deployments with many users
    * Microservices or API-gateway architectures that already issue JWTs
    * Single Sign-On integrations (Auth0, Okta, AWS Cognito, Azure AD)
    * Multi-tenant systems using room or issuer claims for isolation
    * Applications requiring per-user role control (publisher vs. subscriber)
    * Scenarios needing low-latency, stateless authentication
  </Card>

  <Card title="Poor fits" icon="circle-x">
    * Simple deployments with a small, fixed set of users — use [Simple Authentication](/red5-pro/authentication/simple-auth) instead
    * Situations with no JWT-issuing service available
    * When you need real-time validation against live user state — use [Round-Trip Authentication](/red5-pro/authentication/round-trip-auth) instead
  </Card>
</CardGroup>
