Skip to content

Native Auth V2: Sign-up - #2565

Draft
Silviu Petrescu (spetrescu84) wants to merge 7 commits into
djanardhan/native-auth-v2-signinfrom
spetrescu/native-auth-v2-signup
Draft

Native Auth V2: Sign-up#2565
Silviu Petrescu (spetrescu84) wants to merge 7 commits into
djanardhan/native-auth-v2-signinfrom
spetrescu/native-auth-v2-signup

Conversation

@spetrescu84

Copy link
Copy Markdown
Contributor

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:

  • signUpV2 entry point + unified NativeAuthResultV2 results (CodeRequired, AttributesRequired/Invalid, Complete) and SignUpErrorV2 taxonomy.
  • V2 states wired for the sign-up flow (code, attributes, sign-in-after-sign-up).

Tests

  • New: NativeAuthV2SignUpTest plus updated interface/state/error tests.
  • Full msal V2 suite green (7 classes); remaining full-suite failures are pre-existing network/env only.

Dependency

Pairs with common PR AzureAD/microsoft-authentication-library-common-for-android#3240 (bumped via the common submodule).

Draft.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

❌ Work item link check failed. Description does not contain AB#{ID}.

Click here to Learn more.

@github-actions github-actions Bot added the msal label Sep 1, 2026
@spetrescu84
Silviu Petrescu (spetrescu84) changed the base branch from dev to djanardhan/native-auth-v2-signin September 1, 2026 17:37
@spetrescu84 Silviu Petrescu (spetrescu84) changed the title Native Auth V2: Sign-up (msal) Native Auth V2: Sign-up Sep 2, 2026
@antrix1989

Copy link
Copy Markdown

Cross-platform review: CIAM Native Auth V2 sign-up public surface (Android MSAL ↔ MSAL iOS dev)

Reviewed against the merged iOS counterpart (AzureAD/microsoft-authentication-library-for-objc#3093, now in dev) and against the common-layer half in AzureAD/microsoft-authentication-library-common-for-android#3240 — full findings for that layer are posted there.

The public state machine reads well and maps cleanly onto iOS's states (AttributesRequired / AttributesInvalid / PasswordRequired / CodeRequired / SignInAfterSignUp, with token exchange deferred until the app explicitly calls signIn()). Findings below are the divergences and the doc/behaviour mismatches.


1. Severity: HighsignUpV2 can't complete a sign-up that finishes on the upfront attribute submit

Issue. NativeAuthV2CommandResult.SignInAfterSignUpRequired isn't a member of NativeAuthV2SignUpStartCommandResult in common#3240, so signUpV2's when has no branch for it and the common controller's as? cast to NativeAuthV2SignUpStartCommandResult returns null.

For a tenant configured for password sign-up without email OTP — the server returns state: "continue" off the upfront submitAttributes — the account is created server-side but the app receives a generic SignUpErrorV2 from unexpectedSignUpApiError and never gets a SignInAfterSignUpStateV2. CodeRequiredStateV2.submitCode handles SignInAfterSignUpRequired correctly; only the start step is missing it.

iOS can't hit this — signUp and handleSignUpInteractionResult share one response enum whose .readyToComplete case is reachable from the start step.

Recommendation. Once the marker interface is added in common#3240, add the corresponding branch here:

is NativeAuthV2CommandResult.SignInAfterSignUpRequired -> {
    NativeAuthResultV2.SignInAfterSignUpRequired(
        nextState = SignInAfterSignUpStateV2(result.continuationState, scenario, config),
        scenario = scenario
    )
}

and cover it with a signUpV2 test.


2. Severity: MediumSignInAfterSignUpStateV2 KDoc promises a scope fallback that doesn't exist

Issue. The KDoc says scopes and claims supplied to signIn() "take precedence over any supplied at the start of the sign-up flow," which implies a fallback to the sign-up-time values. There isn't one:

  • CommandParametersAdapter.createNativeAuthV2SignInAfterSignUpCommandParameters passes signInParameters?.scopes straight through.
  • The common controller's completion path uses addDefaultScopes(parameters.scopes).
  • The scopes stored on the continuation state at signUpStart (scopes = parameters.scopes ?: emptyList()) are never read on the completion path.

Impact. signUpV2(scopes = listOf("User.Read")) followed by a no-arg signIn() silently drops User.Read and returns a default-scope token. The app has no way to notice short of inspecting the returned token's scopes.

iOS has the same latent gap — signInAfterSignUp uses only joinScopes(parameters.scopes) and never reads flowContinuationState.scopes, so the value it stores at sign-up start is dead — but iOS doesn't document a fallback. Android does.

Recommendation. Implement the fallback (signInParameters?.scopes ?: continuationState.getScopes(), same for claims) and file the iOS counterpart, or correct the KDoc to say the sign-up-time scopes are not carried forward.


3. Severity: Medium — the password wipe in submitSignUpPassword doesn't cover the String copy

Issue. PasswordRequiredStateV2.submitSignUpPassword:

mapOf("password" to String(passwordCopy))

The KDoc directly above states the copy "is cleared below on every exit path, including cancellation." The CharArray is; the immutable String it's copied into isn't, and that String flows into the Map<String, String> request attributes and lives until GC.

Impact. Not a new leak relative to the rest of the SDK, but the documented guarantee is stronger than the implementation — that gap is the risk. The identical pattern is in the common layer's upfrontAttributeValues, flagged in common#3240.

Recommendation. Either carry the password to the JSON body as CharSequence/char[] so no immutable copy exists, or soften the comment to describe what's actually cleared.


4. Severity: Medium — the same server condition produces two different public contracts

Issue. A rejected attribute surfaces differently depending on which state the app is in:

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: MediumNativeAuthResultV2.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 #1signUpV2 cannot 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants