feat(profile-sync): add MFA credential enrollment - #10266
Merged
Merged
Conversation
mathieuartu
added this pull request to stack #10268
September 16, 2026 13:42
This was referenced Sep 16, 2026
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
from
September 16, 2026 14:08
ed6e074 to
66759be
Compare
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
2 times, most recently
from
September 16, 2026 20:31
a8c6583 to
0d73110
Compare
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
from
September 16, 2026 21:03
9cec589 to
e2ae81d
Compare
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
from
September 17, 2026 06:48
e2ae81d to
05d8bdd
Compare
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
from
September 17, 2026 06:53
05d8bdd to
12ca387
Compare
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
2 times, most recently
from
September 17, 2026 08:12
040560e to
081c55a
Compare
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
from
September 17, 2026 13:39
081c55a to
9c22c31
Compare
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
from
September 18, 2026 07:23
663f3b0 to
f43170c
Compare
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
from
September 18, 2026 10:14
f43170c to
678158c
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 678158c. Configure here.
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
2 times, most recently
from
September 18, 2026 13:11
7539a3d to
a89612e
Compare
pull Bot
pushed a commit
to Reality2byte/core
that referenced
this pull request
Sep 18, 2026
## Explanation Wires the MFA HTTP client into the SRP JWT auth SDK on top of the validation foundation. - `mfa/services` calls `/api/v2/mfa/*` (enroll, enroll complete, verify, verify complete, credentials), validates request/response bodies, maps server codes to `MfaError` subclasses, and handles OTP cooldown / `Retry-After`. - `SRPJwtBearerAuth` exposes begin/complete enrollment and verification, credential listing, and assertion-to-token exchange. - Public `JwtBearerAuth` forwards those methods and rejects non-SRP auth types. - Nock fixtures and unit tests cover happy paths and error mapping. Clients still go through `AuthenticationController` in later PRs; this layer is not UI-facing. ## References Depends on MetaMask#10264. Follow-ups: MetaMask#10266, MetaMask#10267. Stack: [stack #10268](https://github.com/MetaMask/core/pull/10268). Related to: https://consensyssoftware.atlassian.net/browse/MUL-2261 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Touches authentication, step-up tokens, and passkey/OTP handling; mistakes could weaken session elevation or mishandle credentials, though coverage is extensive. > > **Overview** > Adds **passkey and email OTP MFA** to the profile-sync JWT auth SDK: a new HTTP layer calls `/api/v2/mfa/*` (enroll, complete, verify, credentials), validates payloads, maps server codes to `MfaError` subclasses, and handles OTP cooldown plus `Retry-After`. > > **`SRPJwtBearerAuth`** and public **`JwtBearerAuth`** expose enrollment/step-up flows, credential listing, and **`exchangeMfaAssertion`** (AAL2 JWT → elevated access token via existing OIDC). MFA is **SRP-only**; email enrollment uses a separate `email` option from `entropySourceId`. > > Schemas mark challenges, OTP codes, passkey payloads, and tokens with **`sensitive()`**. Tests, nock fixtures, changelog, and SDK exports for MFA types are included. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 56b3671. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
2 times, most recently
from
September 18, 2026 21:10
e81accc to
e809f04
Compare
ccharly
reviewed
Sep 21, 2026
Co-authored-by: Cursor <cursoragent@cursor.com>
Refresh enrolled credentials before invalidating the SRP session after email enrollment, and drop in-flight refresh results if the wallet locks.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Drop the credentialsChanged event; stateChange already carries the list - Move step-up state, config and event scaffolding to the step-up layer - Derive the completion credential type from the proof; trace reason.operation - Invalidate the SRP session once via finally after email enrollment - Make the post-sign-in credential warm-up non-blocking; drop unlock refresh - Inline the default trace callback Co-authored-by: Cursor <cursoragent@cursor.com>
…ring email enrollment
mathieuartu
force-pushed
the
mfa/controller-enrollment
branch
from
September 21, 2026 14:07
e809f04 to
d70d671
Compare
ccharly
approved these changes
Sep 21, 2026
ccharly
left a comment
Contributor
There was a problem hiding this comment.
LGTM (went quickly over tests, relying on coverage threshold here)
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.

Explanation
Exposes UI-driven MFA enrollment on
AuthenticationController(no ApprovalController).enrolledCredentialscache (optional on the state type so partial-state selectors stay assignable) with redacted emails in state logs.refreshEnrolledCredentials,beginCredentialEnrollment,completeCredentialEnrollment.AuthenticationController:credentialsChangedwhen the cache actually changes.Enrollment is UI-orchestrated: the controller returns challenges; the client collects the passkey or OTP proof and calls complete.
References
Depends on #10265. Follow-up: #10267. Stack: stack #10268.
Related to: https://consensyssoftware.atlassian.net/browse/MUL-2262
Checklist
Note
High Risk
Changes authentication session lifecycle and MFA enrollment, including SRP token invalidation after email enroll and race handling around lock/sign-out—security-sensitive behavior that clients must wire correctly.
Overview
Adds UI-orchestrated MFA enrollment on
AuthenticationController:refreshEnrolledCredentials,beginCredentialEnrollment, andcompleteCredentialEnrollment, wired to the existing JWT bearer MFA SDK and new messenger action types.Enrollment state lives in a non-persisted
enrolledCredentialscache (UI-visible; state logs allow-list type/status/enrolledAt only). An optionaltraceconstructor hook (@metamask/controller-utils) wraps MFA network steps with operation/credential tags and outcomes.Session and concurrency guards bump an auth-session epoch on lock, sign-out, and wallet reset so in-flight MFA work cannot apply after the session ends; credential refreshes are sequenced so slower responses cannot overwrite newer lists. Email OTP completion invalidates the primary SRP session after a post-enroll refresh (or even when the session ends mid-flow) so cached tokens are not reused without the new email claim; passkey enroll does not touch the session. Sign-out/
clearStateclear the credential cache.Also adds MFA nock fixtures and mock response helpers, depends on
@metamask/controller-utils, and tightensElevatedTokenClaimsStructas a single sensitive struct.Reviewed by Cursor Bugbot for commit d70d671. Bugbot is set up for automated code reviews on this repo. Configure here.