Skip to content

Delegated OAuth metadata advertises an unreachable revocation_endpoint when mounted under a bundle path #252

Description

@navado

Summary

authorization_server_metadata() advertises a revocation_endpoint that is hardcoded to the issuer root ({issuer}/oauth/revoke), while authorization_endpoint, token_endpoint and registration_endpoint all accept caller-supplied overrides. When a deployment mounts the delegated-credential OAuth adapter under a Connection Hub bundle path — as the Connection Hub migration intends — the three overridable endpoints point at the bundle path and the revocation endpoint points at a route that does not exist.

Where

app/ai-app/src/kdcube-ai-app/kdcube_ai_app/apps/chat/sdk/solutions/connections/delegated_credentials/oauth/metadata.py:46

"authorization_endpoint": authorization_endpoint or f"{issuer}/oauth/authorize",
"token_endpoint": token_endpoint or f"{issuer}/oauth/token",
"registration_endpoint": registration_endpoint or f"{issuer}/oauth/register",
# no override parameter:
"revocation_endpoint": f"{issuer}/oauth/revoke",

revocation_endpoint is the only one of the four with no corresponding keyword argument in the signature, so a deployment has no way to point it at the path where the adapter is actually mounted.

Reproduction (live, platform release 2026.07.28.210)

$ curl -s https://yey.boats/.well-known/oauth-authorization-server | jq '{authorization_endpoint, token_endpoint, revocation_endpoint}'
{
  "authorization_endpoint": "https://yey.boats/api/integrations/bundles/home/demo/connection-hub@1-0/public/oauth/authorize",
  "token_endpoint": "https://yey.boats/api/integrations/bundles/home/demo/connection-hub@1-0/public/oauth/token",
  "revocation_endpoint": "https://yey.boats/oauth/revoke"
}

$ curl -s -o /dev/null -w '%{http_code}\n' https://yey.boats/oauth/revoke
404
$ curl -s https://yey.boats/oauth/revoke
{"error":"oauth_route_not_found","path":"revoke"}

Authorize/token/register resolve under the bundle path; revoke does not. Root /oauth/* routing was intentionally dropped once clients moved to the Connection Hub endpoints, so the advertised root path is dead.

Impact

RFC 7009 revocation is unreachable for any deployment that mounts the adapter under a bundle path. The docstring at that line states the intent — "a disconnecting client revokes its token here, which also retires its Connection Hub card (no orphan)" — so a client disconnecting a connector cannot retire its grant, and the Connection Hub card is orphaned. Tokens stay valid until natural expiry (access 1h, refresh 180d per oauth/store.py:26).

This is metadata-only; it does not affect the authorization-code or refresh flows.

Suggested fix

Give revocation_endpoint the same override treatment as its three siblings:

def authorization_server_metadata(
    issuer: str,
    *,
    authorization_endpoint: str | None = None,
    token_endpoint: str | None = None,
    registration_endpoint: str | None = None,
    revocation_endpoint: str | None = None,
    ...
) -> Dict[str, Any]:
    ...
    "revocation_endpoint": revocation_endpoint or f"{issuer}/oauth/revoke",

and pass the bundle-mounted path from the Connection Hub call site alongside the other three. A test in oauth/tests/test_discovery.py asserting all four endpoints honor their overrides would keep them from drifting apart again.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions