-
Notifications
You must be signed in to change notification settings - Fork 259
Add support for signing up with passkey [SDK-4974] #942
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
Changes from 28 commits
4beed82
f7d1b1a
8217fc1
3cb80ef
96a4f91
2bee055
dbf95ff
a24d079
af21b36
27c5fc9
120ed2e
913a733
051c7e8
65716a0
254628f
ced6d15
d4e1fcf
70faae2
e910229
25d8ed1
a97c943
ab6a75b
ed7967e
06e28b9
3cf2003
dff5192
3d82798
fbfa8c0
7a8b140
079910c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| // swiftlint:disable file_length | ||
| // swiftlint:disable type_body_length | ||
|
|
||
| import Foundation | ||
|
|
||
| struct Auth0Authentication: Authentication { | ||
|
|
@@ -205,6 +208,78 @@ struct Auth0Authentication: Authentication { | |
| telemetry: self.telemetry) | ||
| } | ||
|
|
||
| #if !os(tvOS) && !os(watchOS) | ||
| @available(iOS 16.6, macOS 13.5, visionOS 1.0, *) | ||
| func login(signupPasskey attestation: SignupPasskey, | ||
| signupChallenge challenge: PasskeySignupChallenge, | ||
| connection: String?, | ||
| audience: String?, | ||
| scope: String) -> Request<Credentials, AuthenticationError> { | ||
| let url = URL(string: "oauth/token", relativeTo: self.url)! | ||
| let id = attestation.credentialID.encodeBase64URLSafe() | ||
|
|
||
| var authenticatorResponse: [String: Any] = [ | ||
| "id": id, | ||
| "rawId": id, | ||
| "type": "public-key", | ||
| "response": [ | ||
| "clientDataJSON": attestation.rawClientDataJSON.encodeBase64URLSafe(), | ||
| "attestationObject": attestation.rawAttestationObject!.encodeBase64URLSafe() | ||
| ] | ||
| ] | ||
|
|
||
| authenticatorResponse["authenticatorAttachment"] = attestation.attachment.stringValue | ||
|
|
||
| var payload: [String: Any] = [ | ||
| "client_id": self.clientId, | ||
NandanPrabhu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "grant_type": "urn:okta:params:oauth:grant-type:webauthn", | ||
| "auth_session": challenge.authenticationSession, | ||
| "authn_response": authenticatorResponse | ||
| ] | ||
|
|
||
| payload["realm"] = connection | ||
| payload["audience"] = audience | ||
| payload["scope"] = includeRequiredScope(in: scope) | ||
|
|
||
| return Request(session: session, | ||
| url: url, | ||
| method: "POST", | ||
| handle: codable, | ||
| parameters: payload, | ||
| logger: self.logger, | ||
| telemetry: self.telemetry) | ||
| } | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the implementation of the request challenge method. |
||
| @available(iOS 16.6, macOS 13.5, visionOS 1.0, *) | ||
| func passkeySignupChallenge(email: String?, | ||
| phoneNumber: String?, | ||
| username: String?, | ||
| name: String?, | ||
| connection: String?) -> Request<PasskeySignupChallenge, AuthenticationError> { | ||
| let url = URL(string: "passkey/register", relativeTo: self.url)! | ||
|
|
||
| var userProfile: [String: Any] = [:] | ||
| userProfile["email"] = email | ||
| userProfile["phone_number"] = phoneNumber | ||
| userProfile["username"] = username | ||
| userProfile["name"] = name | ||
|
|
||
| var payload: [String: Any] = [ | ||
| "client_id": self.clientId, | ||
| "user_profile": userProfile | ||
| ] | ||
| payload["realm"] = connection | ||
|
|
||
| return Request(session: session, | ||
| url: url, | ||
| method: "POST", | ||
| handle: codable, | ||
| parameters: payload, | ||
| logger: self.logger, | ||
| telemetry: self.telemetry) | ||
| } | ||
| #endif | ||
|
|
||
| func resetPassword(email: String, connection: String) -> Request<Void, AuthenticationError> { | ||
| let payload = [ | ||
| "email": email, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ public protocol Authentication: Trackable, Loggable { | |
| // MARK: - Methods | ||
|
|
||
| /** | ||
| Logs in a user using an email and an OTP code received via email. This is the last part of the passwordless login | ||
| Logs a user in using an email and an OTP code received via email. This is the last part of the passwordless login | ||
| flow. | ||
|
|
||
| ## Usage | ||
|
|
@@ -70,7 +70,7 @@ public protocol Authentication: Trackable, Loggable { | |
| func login(email: String, code: String, audience: String?, scope: String) -> Request<Credentials, AuthenticationError> | ||
|
|
||
| /** | ||
| Logs in a user using a phone number and an OTP code received via SMS. This is the last part of the passwordless login flow. | ||
| Logs a user in using a phone number and an OTP code received via SMS. This is the last part of the passwordless login flow. | ||
|
|
||
| ## Usage | ||
|
|
||
|
|
@@ -116,7 +116,7 @@ public protocol Authentication: Trackable, Loggable { | |
| func login(phoneNumber: String, code: String, audience: String?, scope: String) -> Request<Credentials, AuthenticationError> | ||
|
|
||
| /** | ||
| Logs in a user using a username and password with a realm or connection. | ||
| Logs a user in using a username and password with a realm or connection. | ||
|
|
||
| ## Usage | ||
|
|
||
|
|
@@ -150,11 +150,11 @@ public protocol Authentication: Trackable, Loggable { | |
| ``` | ||
|
|
||
| - Parameters: | ||
| - usernameOrEmail: Username or email of the user. | ||
| - password: Password of the user. | ||
| - realmOrConnection: Domain of the realm or connection name. | ||
| - audience: API Identifier that your application is requesting access to. | ||
| - scope: Space-separated list of requested scope values. | ||
| - username: Username or email of the user. | ||
| - password: Password of the user. | ||
| - realm: Domain of the realm or connection name. | ||
| - audience: API Identifier that your application is requesting access to. | ||
| - scope: Space-separated list of requested scope values. | ||
| - Returns: Request that will yield Auth0 user's credentials. | ||
| - Requires: The `http://auth0.com/oauth/grant-type/password-realm` grant. Check | ||
| [our documentation](https://auth0.com/docs/get-started/applications/application-grant-types) for more information. | ||
|
|
@@ -299,7 +299,7 @@ public protocol Authentication: Trackable, Loggable { | |
| func multifactorChallenge(mfaToken: String, types: [String]?, authenticatorId: String?) -> Request<Challenge, AuthenticationError> | ||
|
|
||
| /** | ||
| Authenticates a user with their Sign In with Apple authorization code. | ||
| Logs a user in with their Sign In with Apple authorization code. | ||
|
|
||
| ## Usage | ||
|
|
||
|
|
@@ -344,7 +344,7 @@ public protocol Authentication: Trackable, Loggable { | |
| func login(appleAuthorizationCode authorizationCode: String, fullName: PersonNameComponents?, profile: [String: Any]?, audience: String?, scope: String) -> Request<Credentials, AuthenticationError> | ||
|
|
||
| /** | ||
| Authenticates a user with their Facebook [session info access token](https://developers.facebook.com/docs/facebook-login/access-tokens/session-info-access-token/) and profile data. | ||
| Logs a user in with their Facebook [session info access token](https://developers.facebook.com/docs/facebook-login/access-tokens/session-info-access-token/) and profile data. | ||
|
|
||
| ## Usage | ||
|
|
||
|
|
@@ -389,7 +389,7 @@ public protocol Authentication: Trackable, Loggable { | |
| func login(facebookSessionAccessToken sessionAccessToken: String, profile: [String: Any], audience: String?, scope: String) -> Request<Credentials, AuthenticationError> | ||
|
|
||
| /** | ||
| Logs in a user using a username and password in the default directory. | ||
| Logs a user in using a username and password in the default directory. | ||
|
|
||
| ## Usage | ||
|
|
||
|
|
@@ -482,7 +482,7 @@ public protocol Authentication: Trackable, Loggable { | |
| - email: Email for the new user. | ||
| - username: Username for the new user (if the connection requires a username). Defaults to `nil`. | ||
| - password: Password for the new user. | ||
| - connection: Name of the connection where the user will be created (database connection). | ||
| - connection: Name of the database connection where the user will be created. | ||
| - userMetadata: Additional user metadata parameters that will be added to the newly created user. | ||
| - rootAttributes: Root attributes that will be added to the newly created user. These will not overwrite existing parameters. See https://auth0.com/docs/api/authentication#signup for the full list of supported attributes. | ||
| - Returns: A request that will yield a newly created database user (just the email, username, and email verified flag). | ||
|
|
@@ -493,6 +493,141 @@ public protocol Authentication: Trackable, Loggable { | |
| */ | ||
| func signup(email: String, username: String?, password: String, connection: String, userMetadata: [String: Any]?, rootAttributes: [String: Any]?) -> Request<DatabaseUser, AuthenticationError> | ||
|
|
||
| #if !os(tvOS) && !os(watchOS) | ||
| /// Logs a user in using a signup passkey credential and the signup challenge. This is the last part of the passkey signup flow. | ||
| /// | ||
| /// ## Availability | ||
| /// | ||
| /// This feature is currently available in | ||
| /// [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access). | ||
| /// Please reach out to Auth0 support to get it enabled for your tenant. | ||
| /// | ||
| /// ## Usage | ||
| /// | ||
| /// ```swift | ||
| /// Auth0 | ||
| /// .authentication() | ||
| /// .login(signupPasskey: signupPasskey, | ||
| /// signupChallenge: signupChallenge, | ||
| /// connection: "Username-Password-Authentication") | ||
| /// .start { result in | ||
| /// switch result { | ||
| /// case .success(let credentials): | ||
| /// print("Obtained credentials: \(credentials)") | ||
| /// case .failure(let error): | ||
| /// print("Failed with: \(error)") | ||
| /// } | ||
| /// } | ||
| /// ``` | ||
| /// | ||
| /// You can also specify audience (the Auth0 API identifier) and scope values: | ||
| /// | ||
| /// ```swift | ||
| /// Auth0 | ||
| /// .authentication() | ||
| /// .login(signupPasskey: signupPasskey, | ||
| /// signupChallenge: signupChallenge, | ||
| /// connection: "Username-Password-Authentication", | ||
| /// audience: "https://example.com/api", | ||
| /// scope: "openid profile email offline_access") | ||
| /// .start { print($0) } | ||
| /// ``` | ||
| /// | ||
| /// - Parameters: | ||
| /// - attestation: The signup passkey credential obtained from the [`ASAuthorizationControllerDelegate`](https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerdelegate) delegate. | ||
| /// - challenge: The passkey signup challenge obtained from ``passkeySignupChallenge(email:phoneNumber:username:name:connection:)``. | ||
| /// - connection: Name of the database connection where the user will be created. If a connection name is not specified, your tenant's default directory will be used. | ||
| /// - audience: API Identifier that your application is requesting access to. | ||
| /// - scope: Space-separated list of requested scope values. | ||
| /// - Returns: A request that will yield Auth0 user's credentials. | ||
| /// | ||
| /// - [Authentication API Endpoint](https://auth0.com/docs/native-passkeys-api#authenticate-new-user) | ||
| /// - [Native Passkeys for Mobile Applications](https://auth0.com/docs/native-passkeys-for-mobile-applications) | ||
| /// - [Supporting passkeys](https://developer.apple.com/documentation/authenticationservices/supporting-passkeys#Register-a-new-account-on-a-service) | ||
| @available(iOS 16.6, macOS 13.5, visionOS 1.0, *) | ||
| func login(signupPasskey attestation: SignupPasskey, | ||
| signupChallenge challenge: PasskeySignupChallenge, | ||
| connection: String?, | ||
| audience: String?, | ||
| scope: String) -> Request<Credentials, AuthenticationError> | ||
|
|
||
| /// Requests a challenge for registering a new user with a passkey. This is the first part of the passkey signup flow. | ||
| /// | ||
| /// You need to provide at least one user identifier and an optional display `name`. By default, database | ||
| /// connections require a valid `email`. If you have enabled [Flexible Identifiers](https://auth0.com/docs/authenticate/database-connections/activate-and-configure-attributes-for-flexible-identifiers) | ||
| /// for your database connection, you may use any combination of `email`, `phoneNumber`, or `username`. These | ||
| /// options can be required or optional and must match your Flexible Identifiers configuration. | ||
| /// | ||
| /// ## Availability | ||
| /// | ||
| /// This feature is currently available in | ||
| /// [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access). | ||
| /// Please reach out to Auth0 support to get it enabled for your tenant. | ||
| /// | ||
| /// ## Usage | ||
| /// | ||
| /// ```swift | ||
| /// Auth0 | ||
| /// .authentication() | ||
| /// .passkeySignupChallenge(email: "support@auth0.com", | ||
| /// name: "John Appleseed", | ||
| /// connection: "Username-Password-Authentication") | ||
| /// .start { result in | ||
| /// switch result { | ||
| /// case .success(let signupChallenge): | ||
| /// print("Obtained signup challenge: \(signupChallenge)") | ||
| /// case .failure(let error): | ||
| /// print("Failed with: \(error)") | ||
| /// } | ||
| /// } | ||
| /// ``` | ||
| /// | ||
| /// Use the challenge with [`ASAuthorizationPlatformPublicKeyCredentialProvider`](https://developer.apple.com/documentation/authenticationservices/asauthorizationplatformpublickeycredentialprovider) | ||
| /// from the `AuthenticationServices` framework to generate a new passkey credential. It will be delivered through the [`ASAuthorizationControllerDelegate`](https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerdelegate) | ||
| /// delegate. Check out [Supporting passkeys](https://developer.apple.com/documentation/authenticationservices/supporting-passkeys#Register-a-new-account-on-a-service) | ||
| /// to learn more. | ||
| /// | ||
| /// ```swift | ||
| /// let credentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider( | ||
| /// relyingPartyIdentifier: signupChallenge.relyingPartyId | ||
| /// ) | ||
| /// | ||
| /// let registrationRequest = credentialProvider.createCredentialRegistrationRequest( | ||
| /// challenge: signupChallenge.challengeData, | ||
| /// name: signupChallenge.userName, | ||
| /// userID: signupChallenge.userId | ||
| /// ) | ||
| /// | ||
| /// let authController = ASAuthorizationController(authorizationRequests: [registrationRequest]) | ||
| /// authController.delegate = self // ASAuthorizationControllerDelegate | ||
| /// authController.presentationContextProvider = self | ||
| /// authController.performRequests() | ||
| /// ``` | ||
| /// | ||
| /// Then, call ``login(signupPasskey:signupChallenge:connection:audience:scope:)`` with the created | ||
| /// passkey credential and the challenge to log the new user in. | ||
| /// | ||
| /// - Parameters: | ||
| /// - email: Email address of the user. | ||
| /// - phoneNumber: Phone number of the user. | ||
| /// - username: Username of the user. | ||
| /// - name: Display name of the user. | ||
| /// - connection: Name of the database connection where the user will be created. If a connection name is not specified, your tenant's default directory will be used. | ||
| /// - Returns: A request that will yield a passkey signup challenge. | ||
| /// | ||
| /// ## See Also | ||
| /// | ||
| /// - [Authentication API Endpoint](https://auth0.com/docs/native-passkeys-api#request-signup-challenge) | ||
| /// - [Native Passkeys for Mobile Applications](https://auth0.com/docs/native-passkeys-for-mobile-applications) | ||
| /// - [Supporting passkeys](https://developer.apple.com/documentation/authenticationservices/supporting-passkeys#Register-a-new-account-on-a-service) | ||
| @available(iOS 16.6, macOS 13.5, visionOS 1.0, *) | ||
| func passkeySignupChallenge(email: String?, | ||
| phoneNumber: String?, | ||
| username: String?, | ||
| name: String?, | ||
| connection: String?) -> Request<PasskeySignupChallenge, AuthenticationError> | ||
| #endif | ||
|
|
||
| /** | ||
| Resets the password of a database user. | ||
|
|
||
|
|
@@ -712,7 +847,7 @@ public protocol Authentication: Trackable, Loggable { | |
| - [Authentication API Endpoint](https://auth0.com/docs/api/authentication#refresh-token) | ||
| - [Refresh Tokens](https://auth0.com/docs/secure/tokens/refresh-tokens) | ||
| */ | ||
| func ssoExchange(withRefreshToken: String) -> Request<SSOCredentials, AuthenticationError> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| func ssoExchange(withRefreshToken refreshToken: String) -> Request<SSOCredentials, AuthenticationError> | ||
|
|
||
| /** | ||
| Renews the user's credentials using a refresh token. | ||
|
|
@@ -845,6 +980,34 @@ public extension Authentication { | |
| return self.signup(email: email, username: username, password: password, connection: connection, userMetadata: userMetadata, rootAttributes: rootAttributes) | ||
| } | ||
|
|
||
| #if !os(tvOS) && !os(watchOS) | ||
| @available(iOS 16.6, macOS 13.5, visionOS 1.0, *) | ||
| func login(signupPasskey attestation: SignupPasskey, | ||
| signupChallenge challenge: PasskeySignupChallenge, | ||
| connection: String? = nil, | ||
| audience: String? = nil, | ||
| scope: String = defaultScope) -> Request<Credentials, AuthenticationError> { | ||
| self.login(signupPasskey: attestation, | ||
| signupChallenge: challenge, | ||
| connection: connection, | ||
| audience: audience, | ||
| scope: scope) | ||
| } | ||
|
|
||
| @available(iOS 16.6, macOS 13.5, visionOS 1.0, *) | ||
| func passkeySignupChallenge(email: String? = nil, | ||
| phoneNumber: String? = nil, | ||
| username: String? = nil, | ||
| name: String? = nil, | ||
| connection: String? = nil) -> Request<PasskeySignupChallenge, AuthenticationError> { | ||
| return self.passkeySignupChallenge(email: email, | ||
| phoneNumber: phoneNumber, | ||
| username: username, | ||
| name: name, | ||
| connection: connection) | ||
| } | ||
| #endif | ||
|
|
||
| func startPasswordless(email: String, type: PasswordlessType = .code, connection: String = "email") -> Request<Void, AuthenticationError> { | ||
| return self.startPasswordless(email: email, type: type, connection: connection) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1057,7 +1057,6 @@ public extension CredentialsManager { | |
| /// - Parameters: | ||
| /// - parameters: Additional parameters to use. | ||
| /// - headers: Additional headers to use. | ||
| /// - callback: Callback that receives a `Result` containing either the renewed user's credentials or an error. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The async/await wrapper has no callback. |
||
| /// - Requires: The scope `offline_access` to have been requested on login to get a refresh token from Auth0. If | ||
| /// there is no refresh token, a ``CredentialsManagerError/noRefreshToken`` error will be thrown. | ||
| /// - Warning: Do not call `store(credentials:)` afterward. The Credentials Manager automatically persists the | ||
|
|
||

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.
This is the implementation of the login method.