Native Auth V2: Sign-up - #3240
Conversation
Extends the V2 SSPR foundation with the Common-side pieces of Native Auth V2 sign-in, covering username + password as the first factor and password followed by an email one-time-code second factor. common4j: - Parse challengeContext.authenticationFactor so a challenge can be classified as single- or multi-factor, and surface every server-offered method as a new opaque-href-free NativeAuthV2AuthMethod. - Retain each method's own links inside NativeAuthV2ContinuationState and follow them via withSelectedMethod(); a stale or unknown method ID fails rather than falling back to another method's href. No endpoint path is ever built from a method ID. - Add sign-in operations, results (MFARequired, PasswordRequired, InvalidCredentials, AuthMethodBlocked) and operation-scoped error mapping for user-not-found, invalid credentials, invalid one-time code and blocked method. The SSPR email-only policy and its error mappings are untouched. - Add the sign-in entry and password verify requests, plus interactor operations that clear the password buffer in a finally on every exit path. common: - Add sign-in start, submit-password, select-MFA-method and submit-MFA-challenge commands and controller orchestration. Password is the only supported first factor; an offer without it fails deterministically instead of falling back to an email one-time code first factor. - Preserve password-submission context so MSAL can report an entry-supplied failure as invalid credentials and a deferred one as an invalid password. - Share one terminal path with SSPR for authorize-challenge continue, the authorization-code token exchange and cache persistence, so scopes, claims and correlation ID behave identically in both flows. - Add sign-in specific PublicApiIds rather than reusing the SSPR ones. Account-cache conflict enforcement stays out of Common; MSAL keeps the Android V1 behaviour of rejecting sign-in while an account is signed in. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
common4j:spotbugsMain failed with two EI_EXPOSE_REP2 violations, one on each Lombok-generated builder that takes the mutable char[] password field: SignInV2StartCommandParametersBuilder and NativeAuthV2SubmitPasswordCommandParametersBuilder. Apply the same suppression the existing password-carrying parameter classes already use, including SignInStartCommandParameters, SignInSubmitPasswordCommandParameters and NativeAuthV2SubmitNewPasswordCommandParameters. No other production change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the prior private constructor shape so current MSAL consumers that create the opaque state reflectively continue to work while method-specific links default to empty. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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>
Use continuation-state scopes and claims for deferred password and MFA token completion, and cover MFA method links across serialization. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…-auth-v2-signup # Conflicts: # common4j/src/main/com/microsoft/identity/common/java/eststelemetry/PublicApiId.java
…internal/controllers/v2/NativeAuthV2FlowController.kt Co-authored-by: Silviu Petrescu <111577419+spetrescu84@users.noreply.github.com>
Cross-platform review: CIAM Native Auth V2 sign-up (Android common ↔ MSAL iOS
|
| Location matched | |
|---|---|
Android (mapInteractionError) |
error.innerError.details[].code == "userAlreadyExists" |
iOS (MSALNativeAuthV2ResponseParser.flowError(from:)) |
top-level error.code == "userAlreadyExists" |
iOS's MSALNativeAuthV2HALResponseSerializer.parseError doesn't parse innerError.details at all, so it structurally cannot see the shape your test fixture asserts:
"error": { "code": "invalidRequest", "innerError": { "details": [
{ "attributeIds": ["email"], "code": "userAlreadyExists", "message": "..." } ] } }Impact. At most one of these is correct against the real CIAM v2 HAL contract. Whichever is wrong silently degrades userAlreadyExists to a generic error — on iOS today, .generalError; on Android, APIError. Apps then can't offer "you already have an account, sign in instead."
Recommendation. Confirm the wire shape with the ESTS/CIAM v2 contract owners and align both SDKs. If the server can emit either shape, match both (top-level code OR any innerError.details[].code) and file the corresponding iOS work item. Please link the contract reference in the PR description so reviewers on both platforms can verify.
3. Severity: Medium — attributeValidationError → AttributesInvalid has no iOS counterpart
Issue. INNER_ERROR_ATTRIBUTE_VALIDATION_FAILED = "attributeValidationError" (matched on error.innerError.code) produces NativeAuthV2InteractionApiResult.InvalidAttributes → the public NativeAuthResultV2.AttributesInvalid with a retryable AttributesInvalidStateV2.
The iOS V2 parser has no mapping for attributeValidationError. MSALNativeAuthAttributesInvalidState exists as a class, but nothing in the V2 code path constructs it — the attributeValidationFailed handling on iOS is V1-only (network/responses/validator/sign_up/).
Impact. The same server response (e.g. a password-policy violation on submit) is an actionable, retryable state on Android and an opaque generalError on iOS. This is the largest public-contract divergence in the PR, and it's in the good direction — but it means "mirrors the iOS V2 sign-up scenarios" isn't accurate for this branch.
Recommendation. Keep the Android behaviour and file the iOS gap, or hold this mapping back until iOS lands it. Either way, call it out explicitly in the PR description so the platforms don't quietly diverge.
4. Severity: Medium — parseCollectAttributes has no self fallback for the submitAttributes link
Issue. iOS resolves the submit target defensively:
collectAttributesResponse.href(for: .submitAttributes) ?? collectAttributesResponse.href(for: .self)(the same ?? .self fallback it uses for .update). Android returns missingLinkError(...) the moment links["submitAttributes"] is absent. Compounding it, self isn't in NativeAuthV2ContinuationState.SUPPORTED_RELATIONS, so a self href would be dropped from the continuation state even if the response carried one.
Impact. A collectAttributes body that only self-links — the exact shape iOS defends against — works on iOS and hard-fails mid-flow on Android.
Recommendation. Mirror the iOS fallback and add self to SUPPORTED_RELATIONS. If the fallback is deliberately omitted because the contract guarantees submitAttributes, please note that in the KDoc and file the iOS side as dead code.
5. Severity: Medium — the password wipe doesn't do what the docs claim
Issue. NativeAuthV2FlowController.upfrontAttributeValues:
values[ATTRIBUTE_NAME_PASSWORD] = String(password)That materializes an immutable heap String, which then flows into NativeAuthV2SubmitAttributesRequestParameters.attributes (Map<String, String>), through the request body, and is never cleared. The finally { StringUtil.overwriteWithNull(parameters.password) } in signUpStart only zeroes the char[].
SignUpV2StartCommandParameters's Javadoc states "The password is never retained in continuation or public state; the controller clears the buffer once the request has been issued," and the inline comment says it's cleared "so it never outlives the request." Neither holds for the String copy — it lives until GC and can be captured in a heap dump.
Impact. Not a new leak relative to the rest of the SDK, but the documented security property is stronger than the implementation. That mismatch is the risk: a future reader will trust the comment.
Recommendation. Either (a) thread the password to the JSON body as char[]/CharSequence so no immutable copy is created, or (b) correct the Javadoc and inline comments to describe what actually happens. The same pattern appears in msal#2565 PasswordRequiredStateV2.submitSignUpPassword (mapOf("password" to String(passwordCopy))) with an equally strong comment.
6. Severity: Medium — attribute values are Map<String, String>; iOS uses [String: Any]
Issue. NativeAuthV2SubmitAttributesRequest, createSubmitAttributesRequest, performSubmitAttributes, SignUpV2StartCommandParameters.attributes, and NativeAuthV2SubmitAttributesCommandParameters.attributes are all Map<String, String>. iOS's MSALNativeAuthV2SubmitAttributesRequestBody.attributes is [String: Any], guarded by JSONSerialization.isValidJSONObject, so numeric/boolean CIAM extension attributes serialize with their native JSON type.
Impact. An extension_age: 30 attribute goes out as "30" from Android and 30 from iOS. If the directory schema types the attribute as a number, one of those is rejected.
Assumption: this may be a deliberate Android-wide constraint — V1 UserAttributes is also string-only — in which case disregard. But please confirm the v2 submitAttributes contract coerces stringified values for non-string attribute types; if it doesn't, this is a functional gap rather than a style difference.
7. Severity: Medium — no email-channel guard on the sign-up CodeRequired branch
Issue. iOS's handleSignUpInteractionResult guards the .verificationRequired case:
guard challenge.channelType.isEmailType else { /* general error:
"Sign up currently supports email one-time-code verification only" */ }Android's CodeRequired branch in handleSignUpInteractionResult surfaces whatever challengeChannel the server sent, with no check.
Impact. If a tenant is configured with SMS OTP for sign-up, Android hands the app a CodeRequiredStateV2 for a channel the flow was never validated against, while iOS fails fast with a clear message. Divergent and harder to diagnose.
Recommendation. Add the same guard, or document why Android intentionally accepts all channels.
8. Severity: Medium — AttributesInvalid.invalidAttributes flattens unrelated detail entries
Issue.
serverError.details.flatMap { it.attributeIds }.distinct()details is the full innerError.details array. Entries whose code is unrelated to the validation failure (e.g. a userAlreadyExists detail carried alongside, which your own fixture comment anticipates) contribute their attributeIds too.
Impact. The app can be told to correct an attribute the server didn't actually reject.
Recommendation. Filter to details that belong to the validation failure before flattening, or document that invalidAttributes is a superset.
9. Severity: Medium — upfront password / attributes are silently discarded unless the first response is collectAttributes
Issue. signUpStart passes upfront = parameters into handleSignUpInteractionResult, but every branch except AttributesRequired ignores it. If the server responds to the entry POST with verify or readyToComplete, the caller's password and attributes are dropped with no signal — the app then gets CodeRequired, and the password it supplied to signUpV2() never reaches the server.
iOS has the identical shape, so this is a shared design gap, not an Android regression. Flagging it because the "entry POST omits the username so we always get collectAttributes back" assumption is load-bearing and undefended on both platforms.
Recommendation. At minimum a Logger.warn when upfront is non-null and gets dropped, so the failure is diagnosable from logs.
10. Severity: Medium — retryState for AttributesInvalid is the pre-submit state
Issue. performSignUpSubmitAttributes passes retryState = state — the state before withAdditionalSubmittedAttributes(...) is applied.
Impact. After a rejected upfront submit, the app retries from a state whose submittedAttributes is empty. If the server subsequently re-requests email (already sent), the flow surfaces AttributesRequired to the app instead of the intended "already submitted" hard error, defeating the loop-detection this PR adds.
Recommendation. Use state.withAdditionalSubmittedAttributes(attributes.keys) as the retry state. The continuation token is unchanged either way, so this is a pure bookkeeping fix.
11. Non-blocking — NativeAuthV2RequiredAttribute.required nullability
iOS coerces a missing required to false (attribute["required"] as? Bool ?? false). Android carries Boolean? all the way to the public RequiredUserAttribute.required, so apps see null on Android where iOS sees false. RequiredUserAttribute is a pre-existing V1 type so changing it isn't in scope here, but the V2 NativeAuthV2RequiredAttribute could default it.
Cycle summary
- New issues: 11 (2 High, 8 Medium, 1 non-blocking)
- Resolved issues: —
- Remaining blockers: Update Readme #1 (
SignInAfterSignUpRequiredunreachable fromsignUpStart— account created, generic error returned) and Updated readme. closes #1 #2 (userAlreadyExistswire-location contradiction with iOS).
Verified as not issues, for the record: parcel read/write symmetry in the new V2 states; routing the sign-up password through submitAttributes rather than the sign-in submit-password endpoint; submittedAttributes case-insensitivity (lowercase() ≈ iOS caseInsensitiveCompare); HAL action string values; and the absence of reserved-name filtering in the deferred submitAttributes path (iOS behaves the same).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 27924655-bd09-44b3-823c-f8c5effaa35d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7f0a19d7-6a3a-4935-bf49-5e95967ad226
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ctor - NativeAuthV2Interactor.executeJsonInteraction now takes an HttpClient.HttpMethod parameter defaulting to POST and dispatches through httpClient.method(...), with performUpdatePassword passing PUT, replacing the usePut boolean flag. - Remove the unused private compatibility constructor from NativeAuthV2ContinuationState and the reflection-based test that was its only consumer. - Update interactor test doubles to stub httpClient.method(...). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 84d1b795-fc24-4f58-a26a-1630241c9c14
parseVerify handles every `verify` action, so a password-typed challenge was turned into PasswordRequired regardless of which step produced it. Track the server's challengeContext.authenticationFactor on NativeAuthV2ContinuationState, inheriting it across responses that do not restate it, and return a dedicated invalid-state error when a password challenge arrives on a step that is not the first factor. An unclassified state fails closed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 84d1b795-fc24-4f58-a26a-1630241c9c14
The user-not-found branch matched only on the AADSTS50034 substring in the server message, with no constraint on the outer error code, and sat above the invalid-credentials branch. A message that merely mentions AADSTS50034 while reporting a recoverable credentials failure would therefore be reported as a terminal UserNotFound instead of InvalidCredentials. Gate the branch on the invalidRequest outer code and move it below the credentials branch, matching the reference implementation in MSAL for objc. The two branches are now mutually exclusive on the outer code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 84d1b795-fc24-4f58-a26a-1630241c9c14
…-auth-v2-signup # Conflicts: # common/src/main/java/com/microsoft/identity/common/nativeauth/internal/controllers/v2/NativeAuthV2FlowController.kt # common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthV2OAuth2Strategy.kt # common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/interactors/NativeAuthV2Interactor.kt # common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2ContinuationState.kt # common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2Operation.kt # common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2ResponseParser.kt
Replied on main MSAL - AzureAD/microsoft-authentication-library-for-android#2565 (comment) |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve the reset-password result contract, support password-only attribute submission, and document password buffer ownership. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce flow-specific command and result names while retaining the original generic command as a deprecated compatibility wrapper. This lets existing MSAL consumers continue compiling until they migrate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commits: • Changes and rationale: • Split sign-up submit-code results from the existing reset-password result contract. This prevents new sign-up outcomes from breaking exhaustive when expressions in existing MSAL consumers. |
There was a problem hiding this comment.
🔵 Needs a closer look
It introduces broad new sign-up flow contracts and controller orchestration across common/common4j, requiring careful human validation of API compatibility and edge-case behavior.
Pull request overview
Adds Native Auth V2 sign-up support across the common4j protocol layer and the common flow/controller layer, aligning Android’s V2 sign-up orchestration with the existing V2 sign-in/SSPR shape.
Changes:
- Extend the V2 HAL parsing + interaction result model to support
collectAttributes, sign-up specific error mapping, and “ready-to-complete → sign-in-after-sign-up” signaling. - Add sign-up request building + interactor operations (
signUpStart,submitAttributes) with continuation-state bookkeeping for previously submitted credentials. - Add
commoncontroller orchestration + command/parameter/result contracts for sign-up start, submit-attributes, submit-code (sign-up), and explicit sign-in-after-sign-up; plus comprehensive unit tests and test fixtures.
File summaries
| File | Description |
|---|---|
| common4j/src/testFixtures/kotlin/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2ContinuationStateTestFactory.kt | New fixture to generate representative continuation states for downstream tests. |
| common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/v2/NativeAuthV2SignUpRequestProviderTest.kt | New tests validating sign-up request bodies/URLs and redaction behavior. |
| common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2SignUpResponseParserTest.kt | New tests for sign-up response parsing (collectAttributes, error mapping, ready-to-complete). |
| common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2ContinuationStateTest.kt | Extends continuation-state tests for sign-up attributes bookkeeping and new link relations. |
| common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/interactors/NativeAuthV2SignUpInteractorTest.kt | New tests for sign-up interactor HTTP contract and credential-name retention behavior. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/v2/NativeAuthV2RequestProvider.kt | Adds sign-up request creation + link resolution fallback logic. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/v2/NativeAuthV2FlowScenario.kt | Adds SIGN_UP to flow scenario enum and updates documentation. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2ResponseParser.kt | Adds collectAttributes parsing and sign-up-specific interaction error mapping. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2RequiredAttribute.kt | New public model for required sign-up attributes to surface above common4j. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2LinkRelation.kt | Adds SUBMIT_ATTRIBUTES and SELF link relations. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2InteractionApiResult.kt | Adds sign-up interaction results: AttributesRequired, UserAlreadyExists, InvalidAttributes. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2HalApiResponse.kt | Extends HAL response model with required attributes array + error details parsing. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/v2/NativeAuthV2ContinuationState.kt | Tracks submitted credential names across sign-up steps and expands supported relations. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/v2/NativeAuthV2SubmitAttributesRequest.kt | New request model for sign-up submit-attributes (password handled as erasable buffer). |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/v2/NativeAuthV2SignUpStartRequest.kt | New request model for sign-up start (continuation-token only). |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthV2OAuth2Strategy.kt | Adds strategy entry points for sign-up start and submit-attributes. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/interactors/NativeAuthV2Interactor.kt | Adds sign-up entry + submit-attributes network operations (with password buffer clearing). |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/controllers/results/SignUpCommandResult.kt | Expands InvalidAttributes contract to cover V2 sign-up command results. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/controllers/results/NativeAuthV2CommandResult.kt | Adds sign-up command results + refines submit-code marker interfaces/contracts. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/controllers/results/INativeAuthCommandResult.kt | Updates result marker interfaces to include new V2 sign-up contracts. |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignUpV2StartCommandParameters.java | New V2 sign-up start parameters (username + optional password + optional attributes). |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/NativeAuthV2SubmitAttributesCommandParameters.java | New submit-attributes parameters (attributes + optional erasable password + continuation state). |
| common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/NativeAuthV2SignInAfterSignUpCommandParameters.java | New explicit sign-in-after-sign-up parameters for token exchange + cache persistence. |
| common4j/src/main/com/microsoft/identity/common/java/eststelemetry/PublicApiId.java | Adds public API IDs for the new V2 sign-up operations. |
| common/src/test/java/com/microsoft/identity/common/nativeauth/internal/controllers/v2/NativeAuthV2SignUpFlowControllerTest.kt | New unit test suite for sign-up orchestration and completion behavior in controller. |
| common/src/test/java/com/microsoft/identity/common/nativeauth/internal/controllers/v2/NativeAuthV2FlowControllerTest.kt | Minor adjustment to reset-password submit-code test expectations. |
| common/src/test/java/com/microsoft/identity/common/nativeauth/internal/commands/NativeAuthV2CommandsTest.kt | Adds coverage for dedicated reset-password/sign-up submit-code command entry points. |
| common/src/main/java/com/microsoft/identity/common/nativeauth/internal/controllers/v2/NativeAuthV2FlowController.kt | Adds sign-up start/submit-attributes/sign-up submit-code/sign-in-after-sign-up orchestration and reserved-attribute validation. |
| common/src/main/java/com/microsoft/identity/common/nativeauth/internal/commands/NativeAuthV2SubmitCodeCommand.kt | Deprecates the generic submit-code command as a compatibility wrapper for reset-password submit-code. |
| common/src/main/java/com/microsoft/identity/common/nativeauth/internal/commands/NativeAuthV2SubmitAttributesCommand.kt | New command for sign-up submit-attributes step. |
| common/src/main/java/com/microsoft/identity/common/nativeauth/internal/commands/NativeAuthV2SignUpSubmitCodeCommand.kt | New command for sign-up submit-code step. |
| common/src/main/java/com/microsoft/identity/common/nativeauth/internal/commands/NativeAuthV2SignUpStartCommand.kt | New command for sign-up start. |
| common/src/main/java/com/microsoft/identity/common/nativeauth/internal/commands/NativeAuthV2SignInAfterSignUpCommand.kt | New command for explicit sign-in-after-sign-up completion. |
| common/src/main/java/com/microsoft/identity/common/nativeauth/internal/commands/NativeAuthV2ResetPasswordSubmitCodeCommand.kt | New command for reset-password submit-code with explicit flow naming. |
Review details
- Files reviewed: 34/34 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Resolve the continuation-state fixture merge by retaining configurable flow support for downstream MSAL tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| data class SignInAfterSignUpRequired( | ||
| override val correlationId: String, | ||
| val continuationState: NativeAuthV2ContinuationState, | ||
| ) : NativeAuthV2SubmitAttributesCommandResult, |
There was a problem hiding this comment.
ReadyToComplete can be returned by the upfront attribute submission performed inside signUpStart() . In that case this result is passed back to signUpStart , but it does not implement NativeAuthV2SignUpStartCommandResult , so the cast in NativeAuthV2FlowController.signUpStart() fails and converts a successful registration into unexpectedSignUpApiError() . Can we check this once?
There was a problem hiding this comment.
The first submitAttributes cannot return readyToComplete from the server as verify on email (and in future sms etc) needs to happen. If readyToComplete happens, it should return the error I would say. This happens because NativeAuthV2SubmitAttributesCommandResult can be done multiple times and at some point readyToComplete (after verify) is ok to be returned
Summary
Adds the common / common4j layer for Native Auth V2 sign-up, 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 code shape:
submittedAttributesinheritance.NativeAuthV2FlowControllersign-up orchestration; sharedsubmitCodeReadyToComplete → SignInAfterSignUpRequired.Tests
Draft — depends on the paired msal PR.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com