GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix: Support Entra ID (Azure AD) token claims in OIDC auth by larrysingleton007 · Pull Request #6631 · feast-dev/feast

fix: Support Entra ID (Azure AD) token claims in OIDC auth #6631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
ntkathole merged 7 commits into feast-dev:master from larrysingleton007:fix/6630-entra-id-oidc-claims
Jul 23, 2026

Uh oh!

There was an error while loading. Please reload this page.

Merged

fix: Support Entra ID (Azure AD) token claims in OIDC auth #6631

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
7 commits
Select commit Hold shift + click to select a range
5e9c95b
fix: Support Entra ID (Azure AD) token claims in OIDC auth
larrysingleton007 Jul 22, 2026
0e55185
docs: Document Entra ID claim support in OIDC authorization
larrysingleton007 Jul 22, 2026
2f52a80
fix: Harden Entra ID OIDC claim handling from review
larrysingleton007 Jul 22, 2026
0c689db
docs: Add Entra ID token example and group-claim caveat
larrysingleton007 Jul 22, 2026
06afa5b
test: Cover OIDC verified-decode sourcing and trim redundant asserts
larrysingleton007 Jul 22, 2026
456f740
test: Extract shared OIDC discovery and signing-key fixtures
larrysingleton007 Jul 22, 2026
4b4c564
fix: Address review nitpicks on Entra OIDC PR
larrysingleton007 Jul 23, 2026
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

15 changes: 13 additions & 2 deletions 15 docs/getting-started/components/authz_manager.md
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ The server, in turn, uses the same OIDC server to validate the token and extract

Some assumptions are made in the OIDC server configuration:
* The OIDC token refers to a client with roles matching the RBAC roles of the configured `Permission`s (*)
* The roles are exposed in the access token under `resource_access.<client_id>.roles`
* The roles are exposed in the access token under `resource_access.<client_id>.roles` (Keycloak) or in the top-level `roles` claim (Entra ID app roles). Roles found in both are merged.
* The JWT token is expected to have a verified signature and not be expired. The Feast OIDC token parser logic validates for `verify_signature` and `verify_exp` so make sure that the given OIDC provider is configured to meet these requirements.
* The `preferred_username` should be part of the JWT token claim.
* The username is read from the first of `preferred_username`, `upn`, `azp`, `appid`, `sub` present in the token. Entra ID client-credentials (app-only) tokens carry no user claim, so they authenticate as the calling application.
* For `GroupBasedPolicy` support, the `groups` claim should be present in the access token (requires a "Group Membership" protocol mapper in Keycloak).
* **Entra ID limitation**: Group claims use object IDs (GUIDs) instead of names, and are omitted entirely when a user exceeds the group overage threshold. GroupBasedPolicy must reference GUIDs and cannot be used for principals with large group memberships.

(*) Please note that **the role match is case-sensitive**, e.g. the name of the role in the OIDC server and in the `Permission` configuration
must be exactly the same.
Expand All @@ -69,6 +70,16 @@ For example, the access token for a client `app` of a user with `reader` role an
}
```

A Microsoft Entra ID (Azure AD) client-credentials (app-only) token has no user claim; the application authenticates as itself, and its app roles arrive in the top-level `roles` claim:
```json
{
"azp": "11111111-2222-3333-4444-555555555555",
"roles": [
"reader"
]
}
```

#### Server-Side Configuration

The server requires `auth_discovery_url` and `client_id` to validate incoming JWT tokens via JWKS:
Expand Down
34 changes: 25 additions & 9 deletions 34 sdk/python/feast/permissions/auth/oidc_token_parser.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,29 @@ async def _validate_token(self, access_token: str):

@staticmethod
def _extract_username_or_raise_error(data: dict) -> str:
"""Extract the username from the decoded JWT. Raises if missing — identity is mandatory.

Checks ``preferred_username`` first (Keycloak default), then falls back
to ``upn`` (Azure AD / Entra ID).
"""Extract the username from the decoded JWT, or raise if the token
carries none of the recognised identity claims.

Human identity claims take precedence: ``preferred_username`` (Keycloak
default), then ``upn`` (Azure AD / Entra ID). Client-credentials
(app-only) tokens carry neither, so the calling application's own
identity is used instead: ``azp`` (Entra ID v2 tokens), ``appid``
(Entra ID v1 tokens), and finally ``sub``, which every issuer sets.
A claim present with a null or non-string value is skipped rather than
returned, so it never shadows a usable later claim. Because ``sub`` is a
mandatory string JWT claim, the raise below fires only for a malformed
token that provides none of the five as a string.
"""
if "preferred_username" in data:
return data["preferred_username"]
if "upn" in data:
return data["upn"]
for claim in ("preferred_username", "upn", "azp", "appid", "sub"):
value = data.get(claim)
if isinstance(value, str):
return value
logger.debug(
f"No usable username claim; token claims present: {list(data.keys())}"
)
raise AuthenticationError(
Comment thread
larrysingleton007 marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

"Missing preferred_username or upn field in access token."
"Missing username claim in access token: expected one of "
"preferred_username, upn, azp, appid or sub."
)

@staticmethod
Expand Down Expand Up @@ -194,6 +206,10 @@ async def user_details_from_access_token(self, access_token: str) -> User:
if self._auth_config.client_id
else []
)
# Entra ID emits app roles in the top-level `roles` claim instead of
# Keycloak's nested `resource_access.<client_id>.roles`. Merge both
# shapes for every issuer, preserving order and dropping duplicates.
roles = list(dict.fromkeys(roles + self._extract_claim(data, "roles")))
groups = self._extract_claim(data, "groups")

logger.info(
Expand Down
19 changes: 19 additions & 0 deletions 19 sdk/python/tests/unit/permissions/auth/conftest.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from typing import Dict
from unittest.mock import MagicMock

import pytest
from kubernetes import client

Expand Down Expand Up @@ -62,6 +65,22 @@ def oidc_config() -> OidcAuthConfig:
)


@pytest.fixture
def discovery_data() -> Dict[str, str]:
return {
"authorization_endpoint": "https://localhost:8080/realms/master/protocol/openid-connect/auth",
"token_endpoint": "https://localhost:8080/realms/master/protocol/openid-connect/token",
"jwks_uri": "https://localhost:8080/realms/master/protocol/openid-connect/certs",
}


@pytest.fixture
def signing_key() -> MagicMock:
key = MagicMock()
key.key = "a-key"
return key


@pytest.fixture(
scope="module",
params=[
Expand Down
Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.