---
sidebar_position: 2
title: Authentication
description: How an app authenticates with Cardinal Media Server. Personal access tokens, password login with the cardinal-app header, 15-minute access tokens, and refresh.
---

# Authentication
*Last updated: October 8, 2026*

Almost every request to the Media Server's API carries a credential that identifies a user on that server. The server applies that user's [roles](/guides/cardinal-media-server/role-based-access-control) to every request, so an app can do exactly what its user can do in the bundled apps, and nothing more.

There are two ways for an app to get a credential today:

| Method | Credential | Lifetime | Best for |
| :--- | :--- | :--- | :--- |
| [Personal access token](#personal-access-tokens) | `cardinal_pat_…` | Until it expires or is revoked | Apps and scripts you build for your own server |
| [Password login](#password-login) | A JWT access token | 15 minutes, renewed with a refresh cookie | Native apps where the user types a username and password |

Personal access tokens are the recommended method for an app you build for your own server. A way for apps to request access to other people's accounts is planned.

## Personal access tokens

A personal access token acts as one user on one Media Server. It carries that user's roles, so an app holding it can do what that user can do, and nothing more.

### Creating a token

The owner and administrators create tokens in the Admin app:

1. Open **Users** and select the user the app should act as.
2. In the **Personal Access Tokens** section, enter a **Token name** and choose when it **Expires**.
3. Select **Create Token**, then copy the token.

The token is shown once. The server stores only a hash of it, so a lost token cannot be recovered: revoke it and create a new one.

For an app, consider creating a dedicated user with the narrowest role that covers what the app does, such as **Music User** for a music player, and creating the token for that user.

Tokens can also be created through the API with `POST /api/v1/users/{id}/personal-access-tokens`, with a body of `{ "label": "...", "expiresAt": "<ISO 8601 date>" }` (`expiresAt` is optional). This endpoint requires a signed-in session: a personal access token cannot create, list, or revoke tokens.

### Using a token

Send the token in the `Authorization` header:

```bash
curl -H "Authorization: Bearer cardinal_pat_..." \
  http://192.168.1.40:24900/api/v1/users/current
```

Where a request cannot carry headers, such as an `<audio>` or `<img>` element, send it as the `token` query parameter instead:

```
http://192.168.1.40:24900/api/v1/music/stream/<musicTrackId>?token=cardinal_pat_...
```

A URL with a token in it is a credential. Do not log it or share it.

Accounts that are linked to a Cardinal Account also accept personal access tokens, without the Cardinal Account token that their interactive sessions need.

### Revoking a token

Revoke a token from the same **Personal Access Tokens** section in the Admin app. Anything using the token stops working immediately.

### Token errors

| Status | Body `code` | Meaning |
| :--- | :--- | :--- |
| `401` | `pat_invalid` | The token is unknown, revoked, or expired. |
| `403` | `pat_not_permitted` | The endpoint manages personal access tokens, which a token cannot do. |
| `403` | | The token's user lacks a capability the endpoint requires. |

## Password login

Log in with a local username and password:

```bash
curl -X POST http://192.168.1.40:24900/api/v1/auth/login \
  -H "cardinal-app: music" \
  -H "Content-Type: application/json" \
  -d '{"username": "alex", "password": "..."}'
```

The `cardinal-app` header is required. Its value is one of `admin`, `music`, `photos`, or `cinema`, and it selects which app's login capability the server checks: a user with only the **Music User** role can log in with `music` and is refused with the others. Send the value that matches the media your app works with. A missing or unknown value is refused with `403`.

A successful login returns:

```json
{
  "JWT": "eyJhbGciOi...",
  "user": { "userId": "...", "username": "alex", ... },
  "scope": "local"
}
```

Send `JWT` as `Authorization: Bearer <JWT>`, or as the `token` query parameter where headers cannot be set. It expires after 15 minutes.

`scope` reflects the server's **Session timeout** setting, and says where the bundled apps keep the token: `local` (persisted), `session` (for the browser session), or `memory` (not persisted).

Password login works only for accounts that have a local username and password. For an account that signs in with a Cardinal Account, use a [personal access token](#personal-access-tokens).

### Refreshing the access token

Alongside the response body, a login sets an `httpOnly` refresh cookie named `cardinal_refresh_tolkien_<instance ID>`, scoped to `Path=/api/v1/auth`. To get a new access token, send that cookie to:

```
POST /api/v1/auth/refresh
```

The response is `{ "JWT": "...", "scope": "..." }`, and the cookie is replaced with a new one. Keep the server's cookies in a cookie jar and send them back on refresh, as most native HTTP clients can.

The cookie lasts as long as the server's **Session timeout** setting allows. See [User sessions](/guides/cardinal-media-server/accounts#user-sessions) for the cookie's attributes.

A web app served from a different site than the Media Server cannot use this cookie: the server does not set it for cross-site logins, because the browser would never send it back. A web app on its own domain should use a [personal access token](#personal-access-tokens) that the user enters into it, rather than one built into the app.

### Session errors

| Status | Meaning | What to do |
| :--- | :--- | :--- |
| `401` | The access token expired. | Refresh, then retry the request. |
| `410` | The access token is not valid on this server, or its user no longer exists. | Discard the token and log in again. |
| `403` | Login was refused, or the user lacks a capability the endpoint requires. | Check the credentials, the `cardinal-app` header, and the user's roles. |

`POST /api/v1/auth/logout` clears the refresh cookie.

## Storing credentials

- Keep tokens in the platform's secure storage, such as the macOS or iOS Keychain or the Android Keystore, or in an environment variable or untracked file for a script.
- Never commit a token to source control or build one into an app you distribute.
- Never write a token to logs, including request URLs that carry it as `?token=`.

