mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-05-09 05:35:30 +02:00
[GH-ISSUE #1088] [Feature]: Provide support for Azure Entra ID #773
Labels
No labels
A1
automated
automated
bug
bug
Closed due to inactivity
contrib
dependencies
dependencies
duplicate
enhancement
feedback
hold
invalid
Notes
P1S
pull-request
security
security
ThumbsUp
user-report
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/bambuddy#773
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @cadtoolbox on GitHub (Apr 22, 2026).
Original GitHub issue: https://github.com/maziggy/bambuddy/issues/1088
Originally assigned to: @netscout2001 on GitHub.
Problem or Use Case
The OIDC callback only trusts the email claim when email_verified is exactly true in the ID token (mfa.py:1376). This is a Google-style OIDC assumption that doesn't hold for Azure Entra ID (Microsoft Entra ID), which does not include email_verified in ID tokens by default — even for directory-verified addresses.
As a result, email-based user matching (auto-linking and auto-creation) silently fails for Azure Entra ID providers. The email is discarded and the log shows:
OIDC provider X: ignoring email for sub='...' because email_verified=None
Users are redirected to no_linked_account even though their email exists in Bambuddy.
OIDC login with Azure Entra ID should resolve users by email when the identity is verified by the tenant.
Proposed Solution
When provider_email is None after the email_verified check, fall back to the preferred_username or upn claims from the ID token. These are directory-controlled claims in Azure Entra ID — the tenant (not the end user) controls their values, making them safe to trust once the issuer has been validated.
if provider_email is None:
for fallback_claim in ("preferred_username", "upn"):
candidate = claims.get(fallback_claim)
if candidate and "@" in candidate:
provider_email = candidate
break
This is safe because:
The issuer is already validated against the OIDC discovery document before this point
preferred_username and upn are tenant-managed claims, not self-asserted
The "@" in candidate check ensures only email-shaped values are used
Google and other providers that emit email_verified: true continue to use the existing primary path unchanged
Affected providers
Any OIDC provider that does not emit the email_verified claim, including:
Microsoft Entra ID (Azure AD)
Some self-hosted providers (e.g., Authentik, depending on configuration)
Environment
Bambuddy version: current
OIDC provider: Azure Entra ID (tenant endpoint https://login.microsoftonline.com/{tenant-id}/v2.0)
Scopes: openid email profile
Alternatives Considered
No response
Feature Category
Other
Priority
Critical for my use case
Mockups or Examples
No response
Contribution
Checklist
@netscout2001 commented on GitHub (Apr 23, 2026):
Thank you for the detailed writeup
Rather than a hardcoded fallback to
preferred_username/upn, we are evaluating an approach that adds two configurable fields per OIDC provider:email_claim"email"require_email_verifiedtrueemail_verified == trueThis would produce three runtime paths:
email_claim="email"+require_email_verified=true— identical to the current C1-guard; no behaviour change for Google or any other provider that emitsemail_verified: true.email_claim="email"+require_email_verified=false— for Entra ID setups where theemailclaim is present butemail_verifiedis never sent.email_verified=falsewould still discard the email; only absent/nullpasses through.email_claim="preferred_username"(or"upn", or any tenant-managed claim) — for setups where the email identity lives in a non-standard claim. The value would be validated as email-shaped (non-empty local part, exactly one@, non-empty domain, ≤ 255 chars) rather than a bare"@" in valuecheck, which would accept values like"@"or"x@".Why not the fallback approach
The fallback design in your proposal is clean and would work, but it has two properties that concern us:
preferred_usernamewhen the standard email path failed creates an implicit second trust anchor that is invisible in the provider settings UI. An explicitemail_claimfield would make the operator's intent auditable.auto_link_existing_accountsinteraction: Case C skips theemail_verifiedcheck entirely (there is nothing to check), which creates the same account-takeover risk asrequire_email_verified=falsewhen combined with auto-link. This would require an additional guard:auto_link_existing_accountsshould only be permitted withemail_claim="email"andrequire_email_verified=true. The fallback approach would need this guard too, but it would be harder to express cleanly.One question for you:
When you decode your Entra ID ID token, does the
emailclaim appear (with a value but withoutemail_verified), or is the email identity only available inpreferred_username/upn? Knowing which path you hit would help us better understand the scope and write more targeted documentation once a solution is ready.@cadtoolbox commented on GitHub (Apr 23, 2026):
@netscout2001 The email claim is present in the token with the correct value. email_verified is missing (None). Azure Entra ID simply doesn't include that claim.
@netscout2001 commented on GitHub (Apr 23, 2026):
Thanks for the additional token data — very helpful!
One more question before i can start: could you share which of these claims are present in your Azure token, and whether they contain a valid email-format value (e.g. user@company.com)?
preferred_username
upn
This only affects which claim name in the tooltip/description — not the underlying logic.
@cadtoolbox commented on GitHub (Apr 23, 2026):
@netscout2001 The actual problem is still the email_verified issue. The OIDC callback matches against the email field on the User record in Bambuddy's database (auth.py:306), doing a case-insensitive lookup. But it never gets that far because the email is discarded at mfa.py:1376 before matching is attempted.
So the chain is:
Azure sends email: "user@company.com" but no email_verified
Bambuddy sees email_verified is None, discards the email
provider_email is None, so email matching is skipped entirely
No existing OIDC link exists → no_linked_account error
The fix is the code change to fall back to preferred_username — or to trust email when email_verified is absent (not false).
@netscout2001 commented on GitHub (Apr 25, 2026):
Please test it using today's daily build.
It includes Azure and RememberMe support for testing.
@netscout2001 commented on GitHub (Apr 26, 2026):
should be fixed in /dev branch by https://github.com/maziggy/bambuddy/pull/1126
please check
@cadtoolbox commented on GitHub (Apr 27, 2026):
@netscout2001 Good news, it's working now with Azure when the email claim is set to 'preferred_username'. But...
[{"type":"value_error","loc":["body"],"msg":"Value error, auto_link_existing_accounts requires require_email_verified=True and email_claim='email'","input":{"name":"XYZ SSO","issuer_url":"https://login.microsoftonline.com/XXXXXXXXXXXX/v2.0","client_id":"XXXXXXXXXXX,"scopes":"openid email profile","is_enabled":true,"auto_create_users":true,"auto_link_existing_accounts":true,"email_claim":"preferred_username","require_email_verified":false},"ctx":{"error":{}}}]I need to be able to create an account in Bambuddy and then the user logs in via SSO, it grants them access as needed. Otherwise, anyone and everyone can use it without any control.
@netscout2001 commented on GitHub (Apr 28, 2026):
should be fixed in /dev branch by https://github.com/maziggy/bambuddy/pull/1142
please check
@cadtoolbox commented on GitHub (Apr 28, 2026):
@netscout2001 Superb work! It works perfectly now.