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