🐞 Bug Summary
After v1.0.0 (which includes #4404 / #4410), virtual-server OAuth still fails with Audience doesn't match when using Authentik. The auto-learn mechanism never engages on the first request, and a manual DB write is still required.
The fallback path builds expected_audiences = [canonical_url, oauth_config.client_id], but the virtual-server admin form doesn't expose client_id. With client_id empty, the list collapses to [canonical_url], which never matches an Authentik token whose aud == client_id. Verification fails before _persist_learned_server_audience runs, so auto-learn cannot bootstrap.
This is a follow-up to #4171, which was closed assuming #4404/#4410 fully resolved the Authentik case ("No UI change needed, no manual DB manipulation"). In practice it doesn't, because there is no path to seed client_id for virtual servers.
🧩 Affected Component
Select the area of the project impacted:
🔁 Steps to Reproduce
- Deploy v1.0.0 with Authentik (or any IdP without RFC 8707 support that mints
aud == client_id).
- Register a virtual server via the admin UI with
oauth_enabled = true. The form has no client_id field, so oauth_config ends up with neither client_id nor resource set.
- Connect a client (e.g. Claude.ai custom connector) and complete the OAuth flow. Authentik issues a valid token with
aud = "<authentik-client-id>".
- Client sends the token → 401
Audience doesn't match. Auto-learn never fires.
- Workaround:
UPDATE servers SET oauth_config = jsonb_set(oauth_config, '{client_id}', '"<authentik-client-id>"') WHERE id = '<vs-id>'; After this, the next request matches via the client_id fallback, auto-learn runs, and resource is persisted.
🤔 Expected Behavior
Either approach closes the gap:
📓 Logs / Error Output
WARN [mcpgateway.utils.verify_credentials] OAuth access token verification failed: Audience doesn't match
🧠 Environment Info
You can retrieve most of this from the /version endpoint.
| Key |
Value |
| Version or commit |
v1.0.0 |
| Runtime |
Python 3.12, uvicorn |
| Platform / OS |
Linux (UBI 10), AWS EKS, linux/arm64 |
| Container |
Docker |
🧩 Additional Context (optional)
- Reproduces with any IdP that mints
aud == client_id and ignores the resource parameter from the OAuth authorization request.
- The gateway-side OAuth form already accepts
client_id. The asymmetry between gateway and virtual-server forms is the root cause.
🐞 Bug Summary
After v1.0.0 (which includes #4404 / #4410), virtual-server OAuth still fails with
Audience doesn't matchwhen using Authentik. The auto-learn mechanism never engages on the first request, and a manual DB write is still required.The fallback path builds
expected_audiences = [canonical_url, oauth_config.client_id], but the virtual-server admin form doesn't exposeclient_id. Withclient_idempty, the list collapses to[canonical_url], which never matches an Authentik token whoseaud == client_id. Verification fails before_persist_learned_server_audienceruns, so auto-learn cannot bootstrap.This is a follow-up to #4171, which was closed assuming #4404/#4410 fully resolved the Authentik case ("No UI change needed, no manual DB manipulation"). In practice it doesn't, because there is no path to seed
client_idfor virtual servers.🧩 Affected Component
Select the area of the project impacted:
mcpgateway- APImcpgateway- UI (admin panel)mcpgateway.wrapper- stdio wrapper🔁 Steps to Reproduce
aud == client_id).oauth_enabled = true. The form has noclient_idfield, sooauth_configends up with neitherclient_idnorresourceset.aud = "<authentik-client-id>".Audience doesn't match. Auto-learn never fires.UPDATE servers SET oauth_config = jsonb_set(oauth_config, '{client_id}', '"<authentik-client-id>"') WHERE id = '<vs-id>';After this, the next request matches via theclient_idfallback, auto-learn runs, andresourceis persisted.🤔 Expected Behavior
Either approach closes the gap:
client_idon the virtual-server admin form, like the gateway form already does. Smallest change; feeds the existing fallback path without touching audience logic. Matches the original suggestion in [BUG]: OAuth audience verification always fails with IdPs that do not support RFC 8707 Resource Indicators (e.g. Authentik) #4171.list[str]onoauth_config.resource(RFC 7519 §4.1.3). Lets operators set the accepted audiences explicitly instead of relying on first-write learning. PyJWT already supports list audiences with any-element semantics.📓 Logs / Error Output
🧠 Environment Info
You can retrieve most of this from the
/versionendpoint.v1.0.0Python 3.12, uvicornLinux (UBI 10), AWS EKS, linux/arm64Docker🧩 Additional Context (optional)
aud == client_idand ignores theresourceparameter from the OAuth authorization request.client_id. The asymmetry between gateway and virtual-server forms is the root cause.