-
Notifications
You must be signed in to change notification settings - Fork 18
feat: add support for passwordless #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #110 +/- ##
==========================================
+ Coverage 44.39% 51.67% +7.27%
==========================================
Files 44 50 +6
Lines 2462 2394 -68
==========================================
+ Hits 1093 1237 +144
+ Misses 1369 1157 -212
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds passwordless authentication support to the Authenticator library, introducing new authentication flows including WebAuthn/passkeys, email OTP, and SMS OTP as alternatives to traditional password-based authentication.
Key Changes
- Adds support for multi-factor authentication selection with new
SignInSelectAuthFactorViewand related states - Implements dynamic password field visibility based on authentication flow (password-only vs. user-choice flows)
- Introduces passkey creation prompts and management screens for WebAuthn support
Reviewed changes
Copilot reviewed 48 out of 55 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| SignInView.swift | Updated to conditionally show password field based on authentication flow |
| SignInSelectAuthFactorView.swift | New view for selecting authentication factors (password, email OTP, SMS OTP, WebAuthn) |
| TextField.swift, PasswordField.swift, PhoneNumberField.swift | Fixed accessibility modifier application |
| AuthenticationService.swift | Added @retroactive conformance to ObservableObject |
| SignInStateTests.swift | Added comprehensive tests for password flow and auth factor translation |
| SignInSelectAuthFactorStateTests.swift | New test suite for auth factor selection and re-selection flows |
| SignUpStateTests.swift | Updated tests for authentication flow validation logic |
| MockAuthenticationService.swift | Enhanced with multi-step flow support for passwordless testing |
| PasskeyPromptTests.swift, ContinueSignInWithFirstFactorSelectionTests.swift | New UI tests for passkey flows |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using: { [weak state] value in | ||
| guard let state = state else { return nil } | ||
|
|
||
| // Password is always required when shown (both password flow and userChoice with password preferred) | ||
| return FieldValidators.required(value) | ||
| } |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validator closure captures state weakly with [weak state] but never checks if state is nil before using it. When state is nil, this returns nil instead of performing validation, which could allow invalid input.
Change to:
using: { [weak state] value in
guard let state = state else {
// If state is deallocated, apply required validation as default
return FieldValidators.required(value)
}
// Password is always required when shown
return FieldValidators.required(value)
}| "authenticator.passkeyCreated.title" = "Passkey created successfully!"; | ||
| "authenticator.passkeyCreated.message" = "Passkey created successfully!"; | ||
| "authenticator.passkeyCreated.existingPasskeys" = "Existing Passkeys"; | ||
| "authenticator.passkeyCreated.unknowName" = "Unknown Provider"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: authenticator.passkeyCreated.unknownName
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this asset need to be in different sizes?
| .font(theme.fonts.body) | ||
| textView.accessibilityHidden(true) | ||
| _ = textView.accessibilityHidden(true) | ||
| return textView |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: can this be chained to resolve the copilot issue?
let textView = SwiftUI.Text(label)
.foregroundColor(theme.colors.foreground.disabled.opacity(0.6))
.font(theme.fonts.body)
.accessibilityHidden(true)
| .font(theme.fonts.body) | ||
| textView.accessibilityHidden(true) | ||
| _ = textView.accessibilityHidden(true) | ||
| return textView |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same suggestion as in another file: can this be chained with accessibilityHidden modifier?
| var preferredPasswordFactor: AuthFactor? { | ||
| // First, try to find password with SRP (more secure) | ||
| if let passwordSRP = first(where: { | ||
| if case .password(let srp) = $0, srp == true { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: this can be moved to something similar on line 55
var isPasswordWithSRP: Bool {
guard case .password(let srp) = $0, srp == true else {
return false
}
return true
}
| authenticatorState.setCurrentStep(nextStep) | ||
| } catch { | ||
| log.error("Unable to select auth factor") | ||
| let authenticationError = self.error(for: error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setBusy(false) here?
| log.error("Unable to Sign In") | ||
| let authenticationError = self.error(for: error) | ||
| setMessage(authenticationError) | ||
| throw authenticationError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setBusy(false)?
| validator: passwordValidator | ||
| ) | ||
| .textContentType(.password) | ||
| #if os(iOS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is textInputAutocapitalization(.never) set only for iOS?
| } | ||
|
|
||
| init() { | ||
| // Configure email as the username attribute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can remove this if not needed
| } | ||
| } | ||
|
|
||
| extension Array where Element == AuthFactor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add a few unit tests for the Array extension? mostly to test sorting criteria
Issue #, if available:
#95
Description of changes:
This pull request introduces support for flexible authentication flows in the
Authenticatorcomponent, allowing for multiple authentication factors and user-driven selection. It adds new steps and states to handle passkey creation, confirmation password, and factor selection, while refactoring the codebase to accommodate these new flows.Authentication Flow & Factor Selection Enhancements:
AuthenticationFlowmodel (AuthenticationFlow.swift) and integrated it throughout theAuthenticatorandAuthenticatorStateto support both password-only and user-choice authentication flows.AuthFactormodel (AuthFactor.swift) representing various authentication factors (password, email OTP, SMS OTP, WebAuthn/passkey), with utilities for sorting, selection, and conversion to Amplify types.AuthenticatorStepandStepenums to include new steps for selecting authentication factors, confirming password, prompting to create a passkey, and handling passkey creation.View and State Integration:
Authenticatorgeneric to accept new content views for sign-in factor selection, confirm password, passkey prompts, and passkey creation, and wired these into the main view logic.signInStateproperty and updated view logic to support new sign-in flows and state transitions.Internal State and Credentials:
Credentialsto track the currently selected authentication factor, enabling detection of changes and proper flow restarts.Package Resource Handling:
Resourcesdirectory as a processed resource in the package manifest to support new assets if needed.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.