Direct Password and MFA Authentication with MS Entra ID#1520
Open
nooreldeenmansour wants to merge 17 commits into
Open
Direct Password and MFA Authentication with MS Entra ID#1520nooreldeenmansour wants to merge 17 commits into
nooreldeenmansour wants to merge 17 commits into
Conversation
71977ed to
d44496f
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1520 +/- ##
==========================================
+ Coverage 84.24% 87.21% +2.96%
==========================================
Files 21 124 +103
Lines 1168 8463 +7295
Branches 0 111 +111
==========================================
+ Hits 984 7381 +6397
- Misses 184 1026 +842
- Partials 0 56 +56 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
95281f3 to
c593968
Compare
552221e to
4b2a458
Compare
7f4a985 to
e3a0ec1
Compare
509900b to
a1e0379
Compare
nooreldeenmansour
added a commit
that referenced
this pull request
Jun 24, 2026
After the Entra password+MFA flow caches the user's password for offline login, the user had no indication that their local password was set to their Entra password (#1520 discussion). Attach a broker-owned notice to the granted response so it surfaces through the PAM conversation. The notice lives in the broker — the component that knows when caching occurred — rather than being hardcoded in authd. Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-Authored-By: GitHub Copilot <noreply@github.com>
2639346 to
b91ee52
Compare
nooreldeenmansour
added a commit
that referenced
this pull request
Jun 24, 2026
libhimmelblau now returns dedicated MsalError variants — AuthorizationDenied, MFAInvalidCode, MFADAGFallbackDisabled — with matching MSAL_ERROR_CODE C enum values for the three MFA outcomes that authd previously classified by matching on error message text. Replace all three string-matching blocks in newMFAError with code-based classification in mfaErrorCategory, removing the fragile dependency on libhimmelblau's error wording. The strings import is retained for AADSTS prefix matching in acquireTokenByRefreshToken. Extend TestMFAErrorCategoryMapping to pin the three new code -> category mappings alongside the existing enum-drift checks. Bump the libhimmelblau submodule to the revision carrying the new structured error variants. See PR #1520 discussions r3453231791 and r3453255404.
18d1001 to
9140aa9
Compare
Move `DeviceRegistrationData` and its validation helper into an untagged file so cached device-registration JSON can be validated without the `libhimmelblau` cgo build.
b67c2f3 to
de9431b
Compare
Add the Go-side types and Cgo bindings needed to start and continue the `libhimmelblau` MFA flow from the broker. Also add helpers to extract user identity fields from access token claims, since the Entra password flow returns an access token rather than a standard OIDC ID token. Bump `libhimmelblau` to the revision that exposes the MFA C API.
The previous `sync.Once` singleton permanently reused the first client configuration that initialized the broker app. Replace it with a keyed cache so device registration and Entra password login can reuse separate broker apps without interfering with each other.
When the delegated token cannot call Microsoft Graph directly, let `GetGroups` fall back to an app-only client-credentials token derived from the configured OIDC client secret. Also keep the Graph-token requirement in cached auth state instead of provider-global mutable state.
Implement the provider methods that start the Entra password flow and complete its MFA challenge. Also advertise `entra_password` in the supported auth modes.
Split `HashAndStorePassword` into separate hashing and persistence steps so callers can hash the plaintext immediately and write the result to disk only after MFA succeeds.
Add the broker-side login flow for direct Entra password authentication and its MFA follow-up modes, including challenge routing, `AADSTS` error handling, and finalization of cached tokens plus the local password hash. Also add `[flows]` configuration for bootstrap auth mode selection and validate the Entra password prerequisites, disabling the flow when neither device registration nor a configured client secret can support group lookup after login.
Point the `libhimmelblau` submodule at a temporary fork that carries the MFA C API needed by this branch until the upstream changes land.
The current e2e environment relies on device auth flow, so keep the new flow disabled there for now to avoid breaking the current expected flow, until e2e tests for `entra_password` are added
The Entra password + MFA flow reads the user's identity from the access token's claims (decoded by libhimmelblau) rather than from a verified OIDC ID token, so the trust previously rested on the TLS connection to Entra alone: a man-in-the-middle able to present a valid certificate for the Microsoft endpoint could have forged the identity claims. Verify the access token's RS256 signature against the tenant's published JWKS, and its tenant, before trusting any claim from it; deny the login on any verification failure. Microsoft first-party (e.g. Graph-scoped) tokens carry a header nonce that is SHA256-rewritten before signing, so the verifier reproduces that rewrite; all login variants then validate. The signature, nonce-rewrite and JWKS logic lives in a cgo-free tokenverify package so it is unit-tested directly; the provider supplies the tenant JWKS via the EntraPasswordProvider interface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When MFA setup fails (authenticator not registered, interactive auth required), libhimmelblau silently fell back to its own Device Authorization Grant flow, showing a plain browser-URL prompt that conflicts with authd's QR-code Device Authentication screen and bypasses the auth state machine entirely. AADSTS 50203 and 16000 surface the same MFA-not-configured condition from a different server path and are routed to Device Authentication for the same reason. Based on https://gitlab.com/nooreldeensalah/libhimmelblau/-/tree/capi-mfa-auth-options
libhimmelblau now returns dedicated error variants for the three MFA outcomes that authd previously classified by matching on error message text. Remove the string-matching blocks from newMFAError and map the new C enum codes directly in mfaErrorCategory.
de9431b to
7a68890
Compare
The Entra password+MFA login failed with "invalid character 'Y' looking
for beginning of value" after a successful grant. The broker emitted the
success message as a bare string while the consumer (dataToMsg) expects
a {"message": ...} envelope, so the PAM client's parse aborted an
already-granted login.
Forward a consistent {userinfo, message} envelope from IsAuthenticated
so consumers always parse the same shape regardless of whether the broker
attached a notice. Encode IAResponse.Msg as {"message": ...} matching
the format used for non-granted replies. Non-string values in the broker's
message field are treated as absent rather than rejected, so a malformed
cosmetic field never blocks an already-granted login at the broker boundary.
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
A cosmetic notice attached to a granted IAResponse must never abort an already-granted login. Treat a parse error on the Msg field as a warning and fall back to showing no notice. Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
After the Entra password+MFA flow caches the user's password for offline login, the user had no indication that their local password was set to their Entra password Attach a broker-owned notice to the granted response so it surfaces through the PAM conversation. The notice lives in the broker — the component that knows when caching occurred — rather than being hardcoded in authd. Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Native (SSH, non-TTY) clients receive the granted message twice: once through nativeModel.sendInfo and again through the PAM TextInfo conversation echo in sendReturnMessageToPam. GDM and interactive- terminal clients do not go through the native sendInfo path, so they must keep receiving the echo. Suppress the redundant PAM-conversation echo only for Native clients by returning false from shouldSendAuthMessage when clientType == Native and the response is a success. Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
UDENG-10797 After the Entra password+MFA flow caches the user's password for offline login, users had no indication that their local password was set to their Entra password ([#1520 discussion](#1520 (comment))). ## Changes - **`broker`**: Attaches a broker-owned caching notice to the granted response so it surfaces through the PAM conversation. The notice lives in the broker — the component that knows when caching occurred. - **`broker/pam`**: Normalizes the granted-message envelope. Granted responses were emitting the notice as a bare string instead of the `{"message": ...}` envelope every consumer expects, aborting already-granted logins with `invalid character 'Y'`. Non-string message values are treated as absent rather than rejecting the login. - **`pam`**: Fixes a double-echo on native clients (SSH). `Native` clients were printing the notice twice — once via `nativeModel.sendInfo` and again via `sendReturnMessageToPam`. GDM and interactive-terminal clients do not go through the native sendInfo path and continue to receive the notice through the PAM conversation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Adds a direct password + MFA authentication flow to the MS Entra ID broker as an additional bootstrap authentication method alongside device authentication. Users can authenticate with
entra_password, complete MFA via push notification (entra_mfa_wait) or OTP/SMS code (entra_mfa_code), then authd caches the OAuth2 token and stores an Argon2id password hash for offline re-authentication.Changes Summary
GroupMember.Read.Alland aclient_secretis configured.[flows]toggles forentra_passwordanddevice_auth, and validate the prerequisites forentra_passwordat startup. When neitherregister_device = truenor a configuredclient_secretcan support group lookup, the flow is disabled or rejected if it would leave no bootstrap login method.Notes
entra_passwordremains disabled in the e2e provisioning script until e2e coverage is added.Related Issues and Discussions (WIP)
UDENG-6756