Native Auth V2: Sign-up - #2565
Conversation
… MFA Replaces the signInV2 not-implemented stub and implements the V2 states it hands back, on top of the matching Common change. Updates the common submodule to 845bddcb4. - signInV2 builds Common command parameters (username, optional copied password, scopes, claims, correlation ID, request interceptor), dispatches the V2 sign-in command through the existing silent-command path, and maps results to Complete, PasswordRequired, MFARequired, or a typed error. - Match the iOS V2 password error taxonomy: a wrong password supplied to signInV2 is SignInErrorV2.isInvalidCredentials(), while a wrong password submitted through PasswordRequiredStateV2 is SubmitPasswordErrorV2.isInvalidPassword(). isInvalidCredentials() stays on SubmitPasswordErrorV2 for source compatibility, is deprecated, and is never true for that path. - Preserve Android V1 account-cache behaviour by rejecting signInV2 while an account is already signed in. Repeated same-account and switch-account sign-in remain unsupported. - Implement MFARequiredStateV2 and MFAVerificationRequiredStateV2 for explicit email-method selection, OTP submission and invalid-code recovery. A method the server did not offer, or a non-email channel, is rejected before any request is sent. The offered methods survive Parcelable restoration so that check still applies to a restored state. - Deliver recoverable failures through the result contract so callback onResult and the suspend return value agree; reserve onError for MsalException; let coroutine cancellation propagate. No error result carries a next state, so the caller retries on the state it already holds. - Map a missing or invalid continuation state to INVALID_STATE rather than not_implemented, and copy then clear password buffers in a finally on every path, leaving the caller's own array untouched. V1 signIn behaviour and its public surface are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Selecting a server-offered method whose channel this increment does not support returned an MFARequestChallengeErrorV2 with no errorType, which is exactly what the generic API-error path produces. An app therefore could not tell "this SDK only supports email one-time codes" from an unspecified server error, since errorType is the only discriminator the V2 error surface exposes. Set NOT_IMPLEMENTED on that branch so callers can identify it via the existing NativeAuthErrorV2.isNotImplemented(), and assert it in the test rather than only asserting what the error is not. No new public API is introduced. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…h head The Common branch has since merged latest dev, so track its head (f190add64) rather than the now-behind implementation commit it still contains. The V2 sign-in unit suites were re-run against this combination. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Ports the in-scope iOS V2 scenarios to the existing Native Auth E2E harness: password at entry, deferred password, unknown user, invalid entry password as invalid credentials, invalid deferred password as invalid password with a retry on the retained state, password plus email OTP MFA including an invalid code followed by a fresh challenge and a scopes assertion, selection of a method the server did not offer, and the Android V1 rejection when an account is already signed in. Every test is @ignore'd because the V2 authorize-challenge sign-in endpoints are not available on the slice these tests run against, matching how this suite already gates scenarios its environment cannot serve. Keeping them compiled means they stay maintained and can be enabled unchanged once a V2-capable slice exists; the same behaviour is asserted today without a service in NativeAuthV2SignInTest. Credentials come from the existing secure lab infrastructure; no captured token, continuation token, authorization code, password or one-time code is committed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Advances the common pointer to f5468eda4, which suppresses EI_EXPOSE_REP2 on the two V2 sign-in password builders so common4j:spotbugsMain passes. The V2 sign-in unit suites were re-run against this combination. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Advance the Common submodule to the commit that preserves the prior continuation-state constructor shape for consumer validation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
❌ Work item link check failed. Description does not contain AB#{ID}. Click here to Learn more. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 73e83caf-df96-432f-932a-b553e9e4b0c1
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d28bf404-3736-4b36-893c-6b7f0cd641ac
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d28bf404-3736-4b36-893c-6b7f0cd641ac
…-auth-v2-signup # Conflicts: # common
Cross-platform review: CIAM Native Auth V2 sign-up public surface (Android MSAL ↔ MSAL iOS
|
| Entry point | NativeAuthV2CommandResult.AttributesInvalid maps to |
|---|---|
AttributesRequiredStateV2 / AttributesInvalidStateV2.submitAttributes (via submitAttributesInternal) |
NativeAuthResultV2.AttributesInvalid — retryable, carries invalidAttributes, exposes a live AttributesInvalidStateV2 |
PasswordRequiredStateV2.submitSignUpPassword |
SubmitPasswordErrorV2(INVALID_PASSWORD) — terminal error, result.invalidAttributes discarded |
Impact. The collapse to INVALID_PASSWORD is reasonable given the assumption that only password was submitted, and isInvalidPassword does let the app retry on the same state. But nothing enforces that assumption: InvalidAttributes.invalidAttributes in the common layer is a flatMap over all innerError.details[].attributeIds (see common#3240 finding 8), so it can legitimately name attributes other than password. When it does, the app is told its password was invalid and loses the real list.
Recommendation. Either surface NativeAuthResultV2.AttributesInvalid consistently from submitSignUpPassword, or branch on whether invalidAttributes is exactly ["password"] and fall through to the generic path otherwise. At minimum, document the assumption in the KDoc.
5. Severity: Medium — NativeAuthResultV2.AttributesInvalid has no iOS counterpart
Issue. iOS's V2 parser never emits an attributes-invalid result — MSALNativeAuthAttributesInvalidState exists as a class but nothing in the V2 code path constructs it (the attributeValidationFailed handling on iOS is V1-only). The mapping that drives this result (attributeValidationError on error.innerError.code) is Android-only.
Impact. A password-policy violation on submit is an actionable, retryable public result on Android and an opaque general error on iOS. That's the largest divergence in the public contract, and it means "Mirror the iOS V2 sign-up scenarios" in the PR description isn't accurate for this branch.
Recommendation. Keep the Android behaviour and file the iOS gap, or hold it until iOS lands the equivalent — either way, call it out in the PR description so the platforms don't drift silently. Same note as common#3240 finding 3.
6. Severity: Medium — reserved attribute names are dropped with no signal to the app
Issue. email and password supplied in NativeAuthSignUpParameters.attributes are filtered out in the common controller with only a Logger.warn. signUpV2's KDoc doesn't mention the reserved names, so an app that sets attributes = mapOf("email" to ...) gets no compile-time, runtime, or documentation signal that its value was ignored.
Separately, AttributesRequiredStateV2.submitAttributes and AttributesInvalidStateV2.submitAttributes apply no reserved-name filtering at all, so an app can re-send email/password mid-flow through the deferred states, bypassing the upfront guard. iOS is identical here (submitAttributes(_:state:) posts the dictionary verbatim), so that half is parity — noting it for the record.
Recommendation. Document the reserved names on NativeAuthSignUpParameters.attributes and on both submitAttributes overloads. Consider surfacing an error instead of silently dropping.
7. Severity: Medium — public attribute values are Map<String, String>; iOS accepts arbitrary JSON
Issue. The public attributes parameter is string-valued end to end. iOS's MSALNativeAuthV2SubmitAttributesRequestBody.attributes is [String: Any] (guarded by JSONSerialization.isValidJSONObject), so a numeric or boolean CIAM extension attribute serializes with its native JSON type.
Impact. extension_age: 30 goes out as "30" from Android and 30 from iOS. If the directory schema types that attribute as a number, one platform is rejected.
Assumption: this may be a deliberate Android-wide constraint carried over from V1 UserAttributes, in which case disregard — but please confirm the v2 submitAttributes contract coerces stringified values for non-string schema types. Same note as common#3240 finding 6.
8. Non-blocking — RequiredUserAttribute.required is Boolean? where iOS is non-optional
iOS coerces a missing required to false (attribute["required"] as? Bool ?? false). Android carries Boolean? through NativeAuthV2RequiredAttribute to the public RequiredUserAttribute.required, so apps see null on Android where iOS sees false. RequiredUserAttribute is a pre-existing V1 type shared with the V1 flow, so changing it isn't in scope — the V2 mapper could default it instead.
Cycle summary
- New issues: 8 (1 High, 6 Medium, 1 non-blocking)
- Resolved issues: —
- Remaining blockers: Add HttpComponent for MSAL #1 —
signUpV2cannot complete an OTP-free password sign-up; the account is created server-side but the app gets a generic error and no continuation state. Blocked on the marker-interface fix in common#3240.
Verified as not issues, for the record: writeToParcel / parcel-constructor field ordering in AttributesRequiredStateV2, AttributesInvalidStateV2, and SignInAfterSignUpStateV2 is symmetric with NativeAuthBaseStateV2; routing the sign-up password through submitAttributes rather than the sign-in submit-password endpoint matches iOS; and the new CodeRequiredStateV2.submitCode branches (AttributesRequired / PasswordRequired / SignInAfterSignUpRequired) correctly cover the widened NativeAuthV2SubmitCodeCommandResult.
Point the Common submodule at PR #3239 head 962e4094d. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d28bf404-3736-4b36-893c-6b7f0cd641ac
…ine/errors/SignInErrorsV2.kt Co-authored-by: Silviu Petrescu <111577419+spetrescu84@users.noreply.github.com>
Keep the V2 server classification as INVALID_CODE while exposing the submitChallenge-aligned public helper from the base API. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d28bf404-3736-4b36-893c-6b7f0cd641ac
Advance the Common submodule to the latest native-auth-v2-signin tip and map select-method failures through Common's consolidated API error result. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the sign-up-specific Common command and remove the stale duplicate test declaration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Advance the Common submodule to the additive reset-password naming commit while retaining the generic compatibility path in this feature branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Route the V2 reset-password state through the flow-specific Common command and result contract. Keep the shared submit-code parameters generic because sign-up and reset password use the same wire request. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commits: • Changes and rationale: • Sign-up submit-code now dispatches NativeAuthV2SignUpSubmitCodeCommand and consumes NativeAuthV2SignUpSubmitCodeCommandResult . • This makes the two flows unambiguous and follows the repository’s existing ResetPassword naming convention. • Updated all reset-password V2 test dispatch expectations and cancellation tests to use the explicit command. |
There was a problem hiding this comment.
🟡 Changes recommended
A few correctness/diagnostic consistency issues remain in newly added V2 code paths (notably invalid-state vs not-implemented handling and inconsistent completion error messaging).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds the MSAL public API layer and state-machine wiring for Native Auth V2 sign-up (signUpV2), aligning the Android V2 surface with existing V2 sign-in and reset-password patterns and expanding test coverage for sign-up and related V2 states.
Changes:
- Introduces V2 sign-up public entry point + follow-up states (code, attributes, password, sign-in-after-sign-up) and corresponding result/error shaping.
- Implements/extends V2 state behaviors (submit password, MFA method selection, MFA challenge submit/resend, submit attributes) with defensive copying and password snapshot clearing.
- Adds/updates unit tests (and gated E2E scaffolding) to cover sign-up parity scenarios, state parceling, callback behavior, and command-parameter construction.
File summaries
| File | Description |
|---|---|
| msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt | Updates V2 state tests (continuation state factory; invalid-state expectations; adds resend and defensive-copy assertions). |
| msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignUpTest.kt | New parity-focused unit test suite for V2 sign-up end-to-end via mocked Common command results. |
| msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignInTest.kt | Adds/refreshes parity-focused unit tests for V2 sign-in, MFA flows, parcel restoration, and password snapshot safety. |
| msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2ResultsTest.kt | Extends result tests to validate defensive copying / immutability of exposed collections. |
| msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceKotlinTest.kt | Updates Kotlin interface tests for new/implemented V2 behavior and reset-password submit-code command wiring. |
| msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceJavaTest.java | Updates Java interface tests to validate blank-username rejection and invalid-state behavior for resend challenge. |
| msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2ErrorsTest.kt | Adjusts error utility-method tests to match updated V2 error taxonomy constants. |
| msal/src/test/java/com/microsoft/identity/nativeauth/utils/CoroutineExtensionsTest.kt | New unit tests for launchOwningPasswordSnapshot cleanup on completion and pre-start cancellation. |
| msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInV2EmailPasswordTest.kt | Adds gated/ignored E2E scaffolding for V2 sign-in scenarios (kept compiled for future enablement). |
| msal/src/test/java/com/microsoft/identity/client/CommandParametersTest.java | Adds/updates tests covering new Native Auth V2 command-parameter adapters (sign-in, sign-up, MFA, submit attributes). |
| msal/src/main/java/com/microsoft/identity/nativeauth/utils/CoroutineExtensions.kt | Adds launchOwningPasswordSnapshot helper to guarantee snapshot clearing on coroutine completion/cancellation. |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInAfterSignUpStateV2.kt | New V2 follow-up state to perform explicit sign-in after server-side sign-up completion. |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/PasswordRequiredStateV2.kt | Implements deferred password submission for sign-in and sign-up (attribute-based), with secure password handling. |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NativeAuthBaseStateV2.kt | Makes the “complete-without-auth-result” diagnostic scenario-neutral. |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFAVerificationRequiredStateV2.kt | Implements MFA challenge submit + resend behaviors for V2, including validation and error mapping. |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFARequiredStateV2.kt | Implements MFA method selection with server-offered-method validation and defensive copying of auth methods. |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/CodeRequiredStateV2.kt | Routes submit-code to scenario-appropriate commands (reset-password vs sign-up) and maps sign-up follow-on states. |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesRequiredStateV2.kt | Implements submit-attributes flow for V2 sign-up (shared internal implementation). |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesInvalidStateV2.kt | Implements retry submit-attributes for V2 sign-up invalid-attributes state (shared internal implementation). |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/results/NativeAuthResultV2.kt | Extends results to include SignInAfterSignUpRequired and makes list exposures immutable/defensively copied. |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/SignInErrorsV2.kt | Updates SubmitPassword error helper semantics to reflect invalid-password taxonomy. |
| msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/MFAErrorsV2.kt | Updates invalid-challenge helper semantics to align with INVALID_CODE taxonomy. |
| msal/src/main/java/com/microsoft/identity/nativeauth/RequiredUserAttribute.kt | Adds V2 required-attribute mapping from Common V2 DTOs to public MSAL model. |
| msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthSignUpParameters.kt | Tightens KDoc to clarify reserved attribute names and recommended usage. |
| msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplication.kt | Implements signInV2 + signUpV2 public API logic (commands, mapping, password snapshot handling). |
| msal/src/main/java/com/microsoft/identity/nativeauth/INativeAuthPublicClientApplication.kt | Updates public API docs (account-conflict handling and callback delivery/threading notes). |
| msal/src/main/java/com/microsoft/identity/nativeauth/AuthMethod.kt | Adds mapping from Common V2 auth methods to public AuthMethod model. |
| msal/src/main/java/com/microsoft/identity/client/internal/CommandParametersAdapter.java | Adds adapters for new Native Auth V2 commands (sign-in start, sign-up start, submit attributes, MFA, sign-in-after-sign-up), plus claims propagation where needed. |
Review details
- Files reviewed: 29/29 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Resolve Native Auth V2 conflicts while preserving the sign-up implementation and Common feature submodule. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: aec90838-d856-481b-be54-648629a6e067
| scenario = NativeAuthFlowScenarioV2.SIGN_UP, | ||
| exception = e | ||
| ) | ||
| } finally { |
There was a problem hiding this comment.
Below in this file on line 970:
The callback overload launches with the caller-owned mutable parameters. The password and attributes are not read until the coroutine executes, so a caller that clears its password immediately after this method returns can change the submitted request. Could we snapshot the username, password, and attributes before launching and use launchOwningPasswordSnapshot , consistent with signInV2 above?
There was a problem hiding this comment.
Done
|
Copilot resolve the merge conflicts in this pull request |
…v2-signup # Conflicts: # common # msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignInTest.kt # msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt Co-authored-by: djanardhan98 <307255345+djanardhan98@users.noreply.github.com>
Resolved the current |
| NativeAuthResultV2.CodeRequired( | ||
| nextState = CodeRequiredStateV2( | ||
| continuationState = result.continuationState, | ||
| scenario = NativeAuthFlowScenarioV2.SIGN_UP, |
There was a problem hiding this comment.
This now returns CodeRequiredStateV2 for sign-up, but its resendCode() method still unconditionally passes NATIVE_AUTH_V2_RESET_PASSWORD_RESEND_CODE ( CodeRequiredStateV2.kt:328–332 ).
Please select the resend API identifier by scenario and add a sign-up resend identifier in Common if needed. Otherwise sign-up resend requests are recorded with the password-reset identifier.
There was a problem hiding this comment.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Summary
Adds the msal public API layer for Native Auth V2 sign-up (
signUpV2), alongside the existing V2 sign-in and SSPR support.Mirrors the iOS V2 sign-up scenarios (AzureAD/microsoft-authentication-library-for-objc#3093) and follows the established V2 sign-in / SSPR public-surface shape:
signUpV2entry point + unifiedNativeAuthResultV2results (CodeRequired, AttributesRequired/Invalid, Complete) andSignUpErrorV2taxonomy.Tests
NativeAuthV2SignUpTestplus updated interface/state/error tests.Dependency
Pairs with common PR AzureAD/microsoft-authentication-library-common-for-android#3240 (bumped via the
commonsubmodule).Draft.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com