Skip to content

feat: add external OIDC issuer support for federation / cross-IdP trust#873

Open
alejandronunezcabello wants to merge 4 commits intoagentic-community:mainfrom
alejandronunezcabello:feat/external-issuer-support
Open

feat: add external OIDC issuer support for federation / cross-IdP trust#873
alejandronunezcabello wants to merge 4 commits intoagentic-community:mainfrom
alejandronunezcabello:feat/external-issuer-support

Conversation

@alejandronunezcabello
Copy link
Copy Markdown
Contributor

Summary

Adds the ability to validate JWT tokens from external OIDC-compliant identity providers alongside Keycloak tokens. This enables federation scenarios where a parent organization's IdP issues tokens that need to be validated by the registry's auth-server.

Motivation

In enterprise deployments, a registry may need to accept tokens from multiple identity providers. For example:

  • A parent company's IdP issues Bearer tokens containing agent roles
  • A partner organization needs programmatic access with their own IdP
  • Multi-cloud deployments with different IdPs per environment

Currently the Keycloak provider only validates tokens from its own Keycloak instance (plus self-signed tokens). This PR adds a generic external issuer capability.

Configuration

Two new environment variables:

Variable Default Description
EXTERNAL_ISSUERS (empty) Comma-separated list of trusted OIDC issuer URLs
EXTERNAL_GROUPS_CLAIM roles Token claim to map to registry groups for RBAC

Example:

EXTERNAL_ISSUERS=https://login.microsoftonline.com/{tenant}/v2.0,https://accounts.google.com
EXTERNAL_GROUPS_CLAIM=roles

How it works

  1. Token arrives at auth-server's validate_token()
  2. If the token's iss claim matches a configured external issuer, or the kid is not found in Keycloak's JWKS, the external issuer path is tried
  3. JWKS is fetched via standard OIDC Discovery (.well-known/openid-configurationjwks_uri)
  4. Token signature is verified against the external issuer's public keys
  5. The configured claim (e.g., roles) is mapped to registry groups for RBAC
  6. External JWKS responses are cached with the same TTL as Keycloak (1 hour)

Backward compatibility

When EXTERNAL_ISSUERS is empty (the default), behavior is identical to the current implementation. No existing functionality is affected.

Changes

  • auth_server/providers/keycloak.py: Added EXTERNAL_ISSUERS, EXTERNAL_GROUPS_CLAIM config, per-issuer JWKS cache, _get_external_jwks(), _validate_external_token(), _validate_external_token_by_kid() methods, and updated validate_token() flow
  • tests/auth_server/unit/providers/test_keycloak.py: Added TestExternalIssuerSupport test class with 6 tests covering discovery, caching, role mapping, multi-issuer fallback, and error cases

Testing

  • Python syntax validated
  • 6 new unit tests added
  • Existing tests unmodified (backward compatible)

alejandronunezcabello and others added 2 commits April 22, 2026 12:56
Adds the ability to validate JWT tokens from external OIDC-compliant identity
providers alongside Keycloak tokens. This enables federation scenarios where
a parent organization's IdP issues tokens that need to be validated by the
registry's auth-server.

Configuration:
- EXTERNAL_ISSUERS: comma-separated list of trusted OIDC issuer URLs
- EXTERNAL_GROUPS_CLAIM: claim name to map to registry groups (default: roles)

How it works:
1. Token arrives at auth-server
2. If issuer matches an external issuer, or kid not found in Keycloak JWKS,
   the external issuer path is tried
3. JWKS is fetched via OIDC discovery (.well-known/openid-configuration)
4. Token is validated against the external issuer's public keys
5. The configured claim (e.g. roles) is mapped to registry groups for RBAC
6. External JWKS responses are cached (1h TTL, same as Keycloak)

Backward compatible: when EXTERNAL_ISSUERS is empty (default), behavior
is identical to before.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevents UnboundLocalError when jwt.decode raises before assignment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread tests/auth_server/unit/providers/test_keycloak.py Outdated
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 22, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 73.62637% with 24 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
auth_server/providers/keycloak.py 73.62% 13 Missing and 11 partials ⚠️

📢 Thoughts on this report? Let us know!

Three fixes for Coop ChatAI federation compatibility:

1. Direct JWKS URI support: EXTERNAL_ISSUERS now accepts JSON array
   with optional jwks_uri field, skipping OIDC Discovery for issuers
   that don't provide .well-known/openid-configuration.
   Legacy comma-separated format still works (backward compatible).

2. uid claim extraction: Username fallback chain now includes 'uid'
   for issuers that use uid instead of sub (e.g. Coop ChatAI).

3. Default groups: New EXTERNAL_DEFAULT_GROUPS env var assigns
   configurable groups when external tokens have no roles/groups
   claims. Essential for minimal tokens that carry only user identity.

Config example for Coop ChatAI:
  EXTERNAL_ISSUERS=[{"issuer":"https://chatai.coop.ch","jwks_uri":"https://chatai.coop.ch/.well-known/jwks.json"}]
  EXTERNAL_DEFAULT_GROUPS=["coop-chatai-users"]

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@aarora79
Copy link
Copy Markdown
Contributor

thank you for contributing this PR @alejandronunezcabello , so you have tested this with Keycloak and Entra?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants