Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion content/configuration/tenant_client_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,33 @@ spec:
| silent_login | no | `true` | `false` | When this option is enabled and a client has a valid auth cookie shared with the login page, its login information will be used to authenticate the user without asking for a username or password. |
| jwt_algorithm | no | `HS256` | `RS256` or `HS256` | JWT signing algorithm for this tenant. Defaults to `HS256` if not specified. Use `RS256` for production (asymmetric, better security, supports key rotation every 90 days), or `HS256` for development/legacy systems (symmetric, requires JWT_SECRET). See [JWT Algorithms](/configuration/jwt_algorithms) for detailed comparison. |

### Predefined Providers

Each entry in the `providers` list can be either a raw JavaScript script (as in the examples above) or a
**predefined-provider object** that references a ready-made provider by type. This lets you reuse a shared provider
implementation without pasting the full script into every tenant.

```yaml
spec:
hosts:
- bnbc.example
providers:
# A raw JavaScript provider ...
- "class UserValidationProvider { [...] }"
# ... and a predefined provider referenced by type:
- type: uitrusting/v1
url: users.srv.cluster.local
token: shared-provider-token # optional
```

| Property | Mandatory | Default | Example | Discussion |
|----------|-----------|---------|----------------------------|-----------------------------------------------------------------------|
| type | yes | - | `uitrusting/v1` | The identifier of the predefined provider to load. |
| url | yes | - | `users.srv.cluster.local` | Host of the backend the predefined provider connects to. |
| token | no | - | `shared-provider-token` | Optional authentication token passed to the predefined provider. |

See [Providers](/providers/providers) for the full list of predefined providers and how they work.

### JWT Algorithm Configuration

Each tenant can use its own JWT signing algorithm, independent of other tenants in the same instance. This allows gradual migration from HS256 to RS256, or mixing tenants with different security requirements.
Expand Down Expand Up @@ -376,7 +403,8 @@ spec:
| name | yes | - | `bnbc-ios-app` | Give the client a unique and specific name. Clients should reflect the device classes that you need to target with specific rights and to get individual statistics from. |
| tenantname | yes | - | `bnbc-tenant` | The name of the tenant for which this client is for. On kubernetes this must contain the tenants namespace: `[tennant namespace]/bnbc-tenant` |
| redirect_urls | yes | - | `["https://www.bnbc.(example|example.com)/bnbc-club/*"]` | A client sends a redirect url to which the response will be redirected to. Specify the allowed urls for security reasons, otherwise it will be possible to hijack the token in the response. See information below. |
| grant_types | no | ["authorization_code", "refresh_token"] | `["password"]` | A list of allowed grant types. If not set, a default set will be applied: `authorization_code`, `refresh_token`. If you need to support the “password" grant, you must specify it explicitly! |
| grant_types | no | ["authorization_code", "refresh_token"] | `["password"]` | A list of allowed grant types. If not set, a default set will be applied: `authorization_code`, `refresh_token`. If you need to support the “password" grant, you must specify it explicitly! Add `device_code` to enable the OAuth 2.0 Device Authorization Grant (RFC 8628). |
| device_grant_config | no | - | _see below_ | Optional fine-tuning for the `device_code` grant. Only used when `device_code` is listed in `grant_types`. All keys are optional and override the built-in defaults. See below. |
| scopes | no | [] | `["recipes:read", "recipes:write", "timeline:post"]` | A list of allowed scopes for this client. If a client requests scopes, these will be filtered by the ones that are allowed. This controls scopes requested by the OAuth client during authorization. |
| allowedProviderScopes | no | [] | `["user:*", "can:*", "org:read"]` | A list of allowed scopes that JavaScript providers can add to user profiles. Supports wildcard patterns (e.g., `user:*` matches `user:list`, `user:add`). Provider-supplied scopes are filtered against this list before being merged with client-requested scopes. Defaults to empty (no provider scopes allowed), providing secure-by-default behavior. |
| referrers | no | [] | `[https://www.bnbc.example/bnbc-club/login]` | If set, only clients that come from these referers are allowed. |
Expand All @@ -388,6 +416,7 @@ spec:
- authorization_code
- refresh_token
- password
- device_code

If you allow a `authorization_code`, you should also allow `refresh_token`, because to refresh a token you need to get
one via the `authorization_code` request.
Expand All @@ -396,6 +425,33 @@ The response from a `password` request does not return a refresh token!
Try to avoid the `password` grant in production! It is insecure and should be replaced by a pkce code request. Only
if you have to support older clients you may need to turn this option on.

Add `device_code` for input-constrained devices (smart TVs, CLIs, IoT) that cannot present a browser. This enables the
OAuth 2.0 Device Authorization Grant (RFC 8628), where the device shows a short `user_code` that the user enters on a
second device to authorize the login.

**Device Grant Configuration**

The Device Authorization Grant is enabled purely by listing `device_code` in `grant_types`. The optional
`device_grant_config` block only overrides the built-in defaults - when it is omitted entirely, the defaults apply.

```yaml
spec:
grant_types:
- authorization_code
- refresh_token
- device_code
device_grant_config:
expires_in: 1800 # optional, lifetime in seconds of the device_code/user_code (default 1800)
interval: 5 # optional, minimum polling interval in seconds (default 5)
# verification_uri: # optional, override the URL shown to the user; auto-detected as https://<host>/activate when omitted
```

| Property | Mandatory | Default | Example | Discussion |
|------------------|-----------|---------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------|
| expires_in | no | `1800` | `1800` | Lifetime in seconds of the issued `device_code` and `user_code`. After this window the user has to start the flow again. |
| interval | no | `5` | `5` | Minimum polling interval in seconds the device must wait between `token` requests while the user completes the login. |
| verification_uri | no | `https://<host>/activate` | `https://login.bnbc.example/activate` | Override the URL shown to the user to enter the `user_code`. Auto-detected as `https://<host>/activate` when omitted. |

**Redirect Urls**
If the requested `redirect_url` of an `AuthRequest` does not match any of these url patterns, the whole
authorization request will be denied.
Expand Down
65 changes: 65 additions & 0 deletions content/oauth/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ You can find an example setup in our [quick start guide](/general/quickstart#cre
>
> Use [PKCE](/oauth/pkce) to request an authorization code.

> **Note about `client_secret`**:
>
> The authorization endpoint (`/authorize`) does not require or validate the `client_secret`. Per RFC 6749
> §4.1.1/§3.2.1 the client is only identified here by its `client_id`. Confidential clients authenticate with their
> secret on the back-channel **token** request (`/token`) instead. This prevents the secret from leaking through the
> browser front-channel.

**Example**: Request an authorization code with PKCE SHA265 code

```text
Expand Down Expand Up @@ -171,6 +178,53 @@ used in certain grant types. The refresh_token is used to obtain a new access to
without having to prompt the user for their login credentials again. That is strictly forbidden with the password grant
type.

### /oauth/device_authorization

The `/oauth/device_authorization` endpoint is the device authorization request endpoint of the
[OAuth 2.0 Device Authorization Grant (RFC 8628)](https://datatracker.ietf.org/doc/html/rfc8628). It is used by
input-constrained devices (command line tools, smart TVs, IoT devices) that authenticate the user on a secondary device
with a browser.

The device POSTs its `client_id` (and optionally a `scope`) to this endpoint and obtains a `device_code` together with a
`user_code`. The `device_code` stays on the device and is used to poll the [/token](#token) endpoint, while the
`user_code` is shown to the user, who enters it in a browser at the [/activate](#activate) verification page.

**Example**: Request a device and user code

```shell
curl --request POST \
--url https://id.example.com/oauth/device_authorization \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data 'client_id=9095A4F2-35B2-48B1-A325-309CA324B97E' \
--data 'scope=openid'
```

The authorization server responds with the device authorization response:

```json
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://id.example.com/activate",
"expires_in": 1800,
"interval": 5
}
```

The device then polls the [/token](#token) endpoint using the `device_code` and the
`urn:ietf:params:oauth:grant-type:device_code` grant type. See the
[device code grant type](/oauth/granttypes#device-code) for the full flow and polling responses.

### /activate

The `/activate` endpoint is the user-facing verification page of the device authorization grant. The user opens this URL
(the `verification_uri` returned by [/oauth/device_authorization](#oauthdevice_authorization)) in a browser, enters the
`user_code` shown on the device and authenticates. After a successful login the user approves the pending device request,
which allows the device to obtain its tokens from the [/token](#token) endpoint.

`GET /activate` renders the verification form (optionally pre-filled with the `user_code` when it is passed as a query
parameter), and `POST /activate` submits the entered `user_code` to approve the device.

### /revoke

The `/revoke` endpoint allows clients to notify Uitsmijter that a previously obtained token (access token or refresh token) is no longer needed and should be invalidated. This endpoint implements [RFC 7009: OAuth 2.0 Token Revocation](https://datatracker.ietf.org/doc/html/rfc7009).
Expand Down Expand Up @@ -309,6 +363,17 @@ This returns a JSON document with the OpenID Provider Metadata:
}
```

When a client of the tenant supports the `device_code` grant, the discovery document additionally advertises a
`device_authorization_endpoint` pointing at [/oauth/device_authorization](#oauthdevice_authorization) and lists
`urn:ietf:params:oauth:grant-type:device_code` in `grant_types_supported`:

```json
{
"device_authorization_endpoint": "https://id.example.com/oauth/device_authorization",
"grant_types_supported": ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code"]
}
```

**Multi-tenant discovery**

Each tenant in Uitsmijter has its own discovery endpoint with tenant-specific configuration:
Expand Down
103 changes: 103 additions & 0 deletions content/oauth/granttypes.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Grant types can be set at `Client` level.
- authorization_code
- refresh_token
- password
- device_code
```

If none of any grant type is specified, that `authorization_code` and `refresh_token` are enabled by default.
Expand Down Expand Up @@ -229,6 +230,108 @@ curl -v \
"https://api.example.com/resource"
```

## Device Code

The device code grant type implements the
[OAuth 2.0 Device Authorization Grant (RFC 8628)](https://datatracker.ietf.org/doc/html/rfc8628). It is designed for
input-constrained devices that either lack a browser or are difficult to type on, such as command line tools, smart TVs,
media consoles and other IoT devices. Instead of typing their credentials on the device, the user authenticates on a
secondary device (typically a phone or laptop) that has a full browser.

The flow consists of three steps:

1. **Device authorization request**: The device POSTs to `/oauth/device_authorization` and receives a `device_code`
(kept on the device), a `user_code` (shown to the user) and a `verification_uri` that the user should open.
2. **User authorization**: The user opens the `verification_uri` in a browser on another device, enters the `user_code`
and authenticates at `/activate`. After a successful login the user approves the pending device request.
3. **Token polling**: While the user authorizes on the secondary device, the device polls `POST /token` with its
`device_code`. Once the user has approved the request, the token endpoint returns an access token (and a refresh
token).

Clients explicitly have to turn on the `device_code` grant type to support it by listing `device_code` in their
`grant_types`. The behaviour of the flow (for example the polling `interval` or the code lifetime) can optionally be
tuned per client via `device_grant_config`. Read more on the
[tenant and client config](/configuration/tenant_client_config) page.

The following values must be set in the request for an access token:

| Property | Value | Description |
|---------------|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
| grant_type | urn:ietf:params:oauth:grant-type:device_code | This tells the server we’re using the device code grant type. The bare `device_code` is also accepted as an alias. |
| client_id | _UUID of the client_ | The public identifier of the application that the developer obtained during registration |
| client_secret | (optional) | Must be set if the client request an secret. Reed more on [tenant and client config](/configuration/tenant_client_config) page |
| device_code | _device code_ | The `device_code` that was returned by the device authorization response. |

### Polling responses

While the user has not yet approved the request, the device keeps polling the `/token` endpoint. The token endpoint
responds with one of the following:

| Status | Body | Meaning |
|--------|-----------------------------------------|-------------------------------------------------------------------------------|
| `200` | access token (and refresh token) | The user approved the request. Stop polling and use the token. |
| `400` | `{"error":"authorization_pending"}` | The user has not yet approved the request. Keep polling at `interval`. |
| `400` | `{"error":"slow_down"}` | The device polls too fast. Increase the polling `interval` (by 5 seconds). |
| `400` | `{"error":"access_denied"}` | The user denied the request. Stop polling. |
| `400` | `{"error":"invalid_grant"}` | The `device_code` is unknown or expired. Stop polling and start a new flow. |

These errors follow the standard RFC 6749/8628 error shape, so standard OAuth2 client libraries interoperate with the
device flow without any Uitsmijter-specific handling.

### Example

First, the device requests a `device_code` and a `user_code` from the device authorization endpoint:

```shell
curl -v \
-X POST \
-d 'client_id=D742D5BF-0402-4C04-9FF8-94C1D2DA5BE2&scope=openid' \
"https://login.example.com/oauth/device_authorization"
```

The server replies with the device authorization response:

```json
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://login.example.com/activate",
"expires_in": 1800,
"interval": 5
}
```

The device now shows the `user_code` and the `verification_uri` to the user and starts polling the token endpoint with
the `device_code`:

```shell
curl -v \
-X POST \
-d 'grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=D742D5BF-0402-4C04-9FF8-94C1D2DA5BE2&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS' \
"https://login.example.com/token"
```

As long as the user has not approved the request, the server replies with `authorization_pending`:

```json
{
"error": "authorization_pending"
}
```

Once the user has entered the `user_code` at the `verification_uri` and approved the request, the server replies with an
access token and a refresh token:

```json
{
"access_token": "aoth5bie8eiy2iPhaeghai6aijahvaeshungae8phieva6tiebeequ6tushei3ei",
"refresh_token": "DOO5AHD6SAi9PA1OOKIAZoOSHOHgO1TO",
"token_type": "bearer",
"expires_in": 7200,
"scope": "openid"
}
```

## Further readings

- Available [Endpoints](/oauth/endpoints)
Expand Down
Loading