OpenTalk Controller returns 401 on /v1/me — issuer trailing slash mismatch with Authelia

holger.banzhaf at posteo.de holger.banzhaf at posteo.de
Tue May 26 01:20:58 CEST 2026


Hello,

I am trying to run the OpenTalk Controller with Authelia as an external 
OIDC provider. Login and token issuance work, but all authenticated API 
calls return 401. I have spent considerable time debugging this and 
would appreciate guidance from anyone who has successfully integrated 
Authelia with OpenTalk.


## Setup

- OpenTalk Controller: registry.opencode.de/opentalk/controller:latest 
(rootless Podman / systemd Quadlet)
- OpenTalk Frontend: rootless Podman / systemd Quadlet
- LiveKit: native binary, systemd service (not containerized)
- coturn: native apt package, systemd service (not containerized)
- Authelia: v4.39.19 (rootless Podman / systemd Quadlet)
- Reverse Proxy: Traefik v3
- OpenTalk frontend: talk.example.org
- Authelia: auth.example.org


## Symptom

After a successful OIDC login, the frontend briefly shows the dashboard, 
then immediately redirects back to the login page. A direct API call 
with a valid access token returns:

```
HTTP/2 401
{"code":"unauthorized","message":"Authentication failed"}
```

The controller produces no log output for these rejected requests, which 
makes diagnosis quite difficult.


## Problem 1: Issuer URI trailing slash mismatch

This is our primary suspected root cause.

Authelia always issues tokens with the issuer URL without a trailing slash:

```
iss: "https://auth.example.org"
```

 From what we can tell, the OpenTalk Controller — apparently due to the 
behaviour of the openidconnect-rs library (RFC 8414) — normalizes the 
configured `authority` value by always appending a trailing slash 
internally. It then validates the `issuer` field in the OIDC discovery 
document against this normalized value.

In practice, regardless of whether `authority` in controller.toml is set 
with or without a trailing slash, the controller seems to always expect 
the discovery document to contain:

```
"issuer": "https://auth.example.org/"
```

but Authelia returns:

```
"issuer": "https://auth.example.org"
```

This causes the controller to fail at startup with:

```
Validation error: unexpected issuer URI `https://auth.example.org`
(expected `https://auth.example.org/`)
```

We worked around the startup failure by rewriting the discovery document 
`issuer` field via a Traefik middleware, so that the controller sees the 
trailing slash and starts successfully.

However, the problem appears to recur at runtime: when using JWT access 
tokens (`access_token_signed_response_alg: RS256`), the signed JWT still 
carries `iss: "https://auth.example.org"` (without trailing slash), 
since a signed JWT cannot be rewritten in transit. We believe the 
controller then rejects the token during local JWT issuer validation, 
producing the 401 — though we are not fully certain about the internal 
validation logic.

When using opaque access tokens (without RS256), the controller falls 
back to introspection — but this leads to Problem 2 below.


## Problem 2: Introspection client authentication method

When the opentalk client is configured without 
`access_token_signed_response_alg: RS256`, Authelia issues opaque tokens 
(`authelia_at_...`). The controller must then validate these via the 
introspection endpoint, authenticating itself as the 
`opentalk-controller` client.

We observed two distinct failure modes here and tested them directly.

**a) client_secret_post**

Credentials sent in the HTTP request body:

```bash
curl -s -X POST \
   -d 'client_id=opentalk-controller&client_secret=SECRET&token=TOKEN' \
   https://auth.example.org/api/oidc/introspection

# Result: HTTP 401 — request_unauthorized
```

Authelia rejects this when the client is registered with 
`token_endpoint_auth_method: client_secret_basic`.

**b) client_secret_basic**

Credentials sent as HTTP Basic Auth:

```bash
curl -s -X POST \
   -u 'opentalk-controller:SECRET' \
   -d 'token=TOKEN' \
   https://auth.example.org/api/oidc/introspection

# Result: HTTP 200 — {"active":true,...}
```

This works correctly. We therefore believe the OpenTalk Controller uses 
`client_secret_basic` for introspection (credentials in the 
Authorization header, not in the POST body). We confirmed this by 
decoding the Authorization header captured in Traefik access logs:

```
Authorization: Basic base64(opentalk-controller:SECRET)
```

With a dummy token we correctly receive `{"active": false}`, and with a 
real token `{"active": true}`. So introspection itself appears to work 
when Authelia is configured with `token_endpoint_auth_method: 
client_secret_basic`.

Despite this, the controller still returns 401. This leads us to suspect 
it performs additional local validation beyond introspection — possibly 
a JWT issuer check — which fails due to the trailing slash mismatch 
described in Problem 1. But again, we are not certain about the 
internals here.

## Problem 3: Access Token with empty `aud` claim

Without explicit `audience` configuration, Authelia issues access tokens 
with an empty `aud` claim:

```json
"aud": []

We worked around this by adding audience: [opentalk-controller] to the 
Authelia client configuration, so the token now contains:

"aud": ["opentalk-controller"]

We are not certain whether OpenTalk requires a specific value in aud, or 
whether an empty aud causes the token to be rejected outright. Any 
clarification on what the controller expects here would be helpful.




## Current Authelia client configuration

```yaml
- client_id: opentalk
   public: true
   authorization_policy: one_factor
   redirect_uris:
     - https://talk.example.org/auth/callback
   scopes: [openid, profile, email]
   token_endpoint_auth_method: none
   id_token_signed_response_alg: RS256
   access_token_signed_response_alg: RS256
   audience:
     - opentalk-controller
   claims_policy: opentalk_claims  # includes given_name, family_name, 
email, preferred_username
   consent_mode: implicit

- client_id: opentalk-controller
   public: false
   client_secret: "..."
   token_endpoint_auth_method: client_secret_basic
   scopes: [openid, profile, email]
```


## Current controller.toml [oidc] section

```toml
[oidc]
authority = "https://auth.example.org"

[oidc.frontend]
authority = "https://auth.example.org"
client_id = "opentalk"

[oidc.controller]
client_id = "opentalk-controller"
client_secret = "..."
```


## Questions

1. Is the issuer URI trailing slash mismatch a known incompatibility 
between OpenTalk and Authelia? Is there any controller-side 
configuration to relax or override the issuer validation?

2. At runtime, does the controller validate JWT access tokens locally 
(signature + issuer check) in addition to — or instead of — calling the 
introspection endpoint? If so, is there a way to configure it to rely 
exclusively on introspection?

3. Is `audience: [opentalk-controller]` the correct way to populate the 
`aud` claim in the access token when using Authelia? Without this 
setting, Authelia issues tokens with `"aud": []`, which we suspect may 
also cause issues.

4. Has someone ever tested Authelia  as an OIDC provider for OpenTalk? 
If not, are there known working alternatives besides Keycloak? I ask 
because I need a very lean setup.

5. Are there controller debug flags or log levels that would produce 
more detailed output on 401 rejections? Currently the controller logs 
nothing for these requests, which makes it very hard to understand what 
is actually being validated.

Thank you for any ideas and guidance.
Cheers
Holger


More information about the Opentalk-general mailing list