Skip to content

Add support for enrolling passkeys with My Account API [SDK-5543] #962

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

Merged
merged 35 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
182e263
Add initial implementation
Widcket May 14, 2025
7d25a5d
Add unit tests for My Account client
Widcket May 14, 2025
282fce3
Update models
Widcket May 14, 2025
13dc56f
Fix error messages
Widcket May 15, 2025
dc6f42f
Fix date formatting
Widcket May 15, 2025
47b43bc
Add URL extension
Widcket May 15, 2025
0a2f0d8
Fix error tests
Widcket May 15, 2025
d7d36bc
Remove percent encoding from the authentication method id
Widcket May 15, 2025
276ea00
Add unit tests for `MyAccountError`
Widcket May 15, 2025
101de65
Add unit tests for the models
Widcket May 16, 2025
67aa081
Add remaining unit tests
Widcket May 20, 2025
7943f3f
Add remaining API docs
Widcket May 20, 2025
08df1c4
Extract credential details into `PasskeyCredential`
Widcket May 21, 2025
a83fe22
Update API docs
Widcket May 21, 2025
483f2e5
WIP Fix Real Devices
Widcket May 22, 2025
8d680d5
Remove passkey files from tvOS and visionOS targets
Widcket May 22, 2025
eb4f8be
Move `isNetworkError` to `Auth0APIError`
Widcket May 23, 2025
e85e812
Add reminaining documentation
Widcket May 23, 2025
7fe1055
Update links in Documentation.md
Widcket May 23, 2025
441d28a
Remove leftover test changes
Widcket May 23, 2025
35cd94e
Document the required scopes
Widcket May 23, 2025
662b4c4
Merge branch 'master' into feature/myaccount-passkey-enrollment
Widcket May 23, 2025
5293ee9
Add missing `PASSKEYS_PLATFORM` check
Widcket May 23, 2025
3f39f2c
Merge branch 'feature/myaccount-passkey-enrollment' of github.com:aut…
Widcket May 23, 2025
20c9f7b
Remove unnecessary assertion
Widcket May 23, 2025
59b4bdd
Remove passkeys-specific tests from tvOS test target
Widcket May 23, 2025
1540c33
Add Mocks.swift to the tvOS target
Widcket May 23, 2025
ec85d1f
Guard `MyAccountResult` with `WEB_AUTH_PLATFORM` check
Widcket May 23, 2025
1840d78
Guard `beUnsuccessful()` with `WEB_AUTH_PLATFORM` check
Widcket May 23, 2025
030b015
Adjust glob in Podspec
Widcket May 23, 2025
18b5da5
Revert `WEB_AUTH_PLATFORM` checks
Widcket May 27, 2025
90fc0bb
Empty commit to trigger CI
Widcket May 27, 2025
1fe59bb
Empty commit to trigger CI
Widcket May 27, 2025
ed323e5
Amend matcher description
Widcket May 27, 2025
939da9d
Use bundle fot `MyAccountSpec.self` in test case
Widcket May 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Auth0.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Pod::Spec.new do |s|
s.authors = { 'Auth0' => '[email protected]', 'Rita Zerrizuela' => '[email protected]' }
s.source = { :git => 'https://github.com/auth0/Auth0.swift.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/auth0'
s.source_files = 'Auth0/*.swift'
s.source_files = 'Auth0/**/*.swift'
s.resource_bundles = { s.name => 'Auth0/PrivacyInfo.xcprivacy' }
s.swift_versions = ['5.0']

Expand Down
239 changes: 213 additions & 26 deletions Auth0.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

64 changes: 35 additions & 29 deletions Auth0/Auth0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ public typealias AuthenticationResult<T> = Result<T, AuthenticationError>
*/
public typealias ManagementResult<T> = Result<T, ManagementError>

/**
`Result` wrapper for My Account API operations.
*/
public typealias MyAccountResult<T> = Result<T, MyAccountError>

#if WEB_AUTH_PLATFORM
/**
`Result` wrapper for Web Auth operations.
Expand All @@ -28,7 +33,8 @@ public typealias CredentialsManagerResult<T> = Result<T, CredentialsManagerError
public let defaultScope = "openid profile email"

/**
Auth0 [Authentication API](https://auth0.com/docs/api/authentication) client to authenticate your user using Database, Social, Enterprise or Passwordless connections.
[Authentication API](https://auth0.com/docs/api/authentication) client to authenticate a user with Database, Social,
Enterprise or Passwordless connections.

## Usage

Expand All @@ -40,15 +46,15 @@ public let defaultScope = "openid profile email"
- clientId: Client ID of your Auth0 application.
- domain: Domain of your Auth0 account, for example `samples.us.auth0.com`.
- session: `URLSession` instance used for networking. Defaults to `URLSession.shared`.
- Returns: Auth0 Authentication API client.
- Returns: Authentication API client.
*/
public func authentication(clientId: String, domain: String, session: URLSession = .shared) -> Authentication {
return Auth0Authentication(clientId: clientId, url: .httpsURL(from: domain), session: session)
}

/**
Auth0 [Authentication API](https://auth0.com/docs/api/authentication) client to authenticate your user using Database,
Social, Enterprise or Passwordless connections.
[Authentication API](https://auth0.com/docs/api/authentication) client to authenticate a user with Database, Social,
Enterprise or Passwordless connections.

## Usage

Expand All @@ -64,18 +70,18 @@ public func authentication(clientId: String, domain: String, session: URLSession
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ClientId</key>
<string>YOUR_AUTH0_CLIENT_ID</string>
<key>Domain</key>
<string>YOUR_AUTH0_DOMAIN</string>
<key>ClientId</key>
<string>YOUR_AUTH0_CLIENT_ID</string>
<key>Domain</key>
<string>YOUR_AUTH0_DOMAIN</string>
</dict>
</plist>
```

- Parameters:
- session: `URLSession` instance used for networking. Defaults to `URLSession.shared`.
- bundle: Bundle used to locate the `Auth0.plist` file. Defaults to `Bundle.main`.
- Returns: Auth0 Authentication API client.
- Returns: Authentication API client.
- Warning: Calling this method without a valid `Auth0.plist` file will crash your application.
*/
public func authentication(session: URLSession = .shared, bundle: Bundle = .main) -> Authentication {
Expand All @@ -84,8 +90,7 @@ public func authentication(session: URLSession = .shared, bundle: Bundle = .main
}

/**
Auth0 [Management API v2](https://auth0.com/docs/api/management/v2) client to perform operations with the Users
endpoints.
[Management API v2](https://auth0.com/docs/api/management/v2) client for performing operations with the Users endpoints.

## Usage

Expand All @@ -107,19 +112,19 @@ public func authentication(session: URLSession = .shared, bundle: Bundle = .main
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ClientId</key>
<string>YOUR_AUTH0_CLIENT_ID</string>
<key>Domain</key>
<string>YOUR_AUTH0_DOMAIN</string>
<key>ClientId</key>
<string>YOUR_AUTH0_CLIENT_ID</string>
<key>Domain</key>
<string>YOUR_AUTH0_DOMAIN</string>
</dict>
</plist>
```

- Parameters:
- token: Management API token with the correct allowed scopes to perform the desired action.
- token: Access token for the Management API with the correct allowed scopes to perform the desired action.
- session: `URLSession` instance used for networking. Defaults to `URLSession.shared`.
- bundle: Bundle used to locate the `Auth0.plist` file. Defaults to `Bundle.main`.
- Returns: Auth0 Management API v2 client.
- Returns: Management API v2 client.
- Warning: Calling this method without a valid `Auth0.plist` file will crash your application.
*/
public func users(token: String, session: URLSession = .shared, bundle: Bundle = .main) -> Users {
Expand All @@ -128,8 +133,7 @@ public func users(token: String, session: URLSession = .shared, bundle: Bundle =
}

/**
Auth0 [Management API v2](https://auth0.com/docs/api/management/v2) client to perform operations with the Users
endpoints.
[Management API v2](https://auth0.com/docs/api/management/v2) client for performing operations with the Users endpoints.

## Usage

Expand All @@ -145,18 +149,19 @@ public func users(token: String, session: URLSession = .shared, bundle: Bundle =
* Unlink users

- Parameters:
- token: Management API token with the correct allowed scopes to perform the desired action.
- token: Access token for the Management API with the correct allowed scopes to perform the desired action.
- domain: Domain of your Auth0 account, for example `samples.us.auth0.com`.
- session: `URLSession` instance used for networking. Defaults to `URLSession.shared`.
- Returns: Auth0 Management API v2 client.
- Returns: Management API v2 client.
*/
public func users(token: String, domain: String, session: URLSession = .shared) -> Users {
return Management(token: token, url: .httpsURL(from: domain), session: session)
}

#if WEB_AUTH_PLATFORM
/**
Auth0 client for performing web-based authentication with [Universal Login](https://auth0.com/docs/authenticate/login/auth0-universal-login).
[Universal Login](https://auth0.com/docs/authenticate/login/auth0-universal-login) client for performing web-based
authentication.

## Usage

Expand All @@ -171,18 +176,18 @@ public func users(token: String, domain: String, session: URLSession = .shared)
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ClientId</key>
<string>YOUR_AUTH0_CLIENT_ID</string>
<key>Domain</key>
<string>YOUR_AUTH0_DOMAIN</string>
<key>ClientId</key>
<string>YOUR_AUTH0_CLIENT_ID</string>
<key>Domain</key>
<string>YOUR_AUTH0_DOMAIN</string>
</dict>
</plist>
```

- Parameters:
- session: `URLSession` instance used for networking. Defaults to `URLSession.shared`.
- bundle: Bundle used to locate the `Auth0.plist` file. Defaults to `Bundle.main`.
- Returns: Auth0 Web Auth client.
- Returns: Web Auth client.
- Warning: Calling this method without a valid `Auth0.plist` file will crash your application.
*/
public func webAuth(session: URLSession = .shared, bundle: Bundle = Bundle.main) -> WebAuth {
Expand All @@ -191,7 +196,8 @@ public func webAuth(session: URLSession = .shared, bundle: Bundle = Bundle.main)
}

/**
Auth0 client for performing web-based authentication with [Universal Login](https://auth0.com/docs/authenticate/login/auth0-universal-login).
[Universal Login](https://auth0.com/docs/authenticate/login/auth0-universal-login) client for performing web-based
authentication.

## Usage

Expand All @@ -203,7 +209,7 @@ public func webAuth(session: URLSession = .shared, bundle: Bundle = Bundle.main)
- clientId: Client ID of your Auth0 application.
- domain: Domain of your Auth0 account, for example `samples.us.auth0.com`.
- session: `URLSession` instance used for networking. Defaults to `URLSession.shared`.
- Returns: Auth0 Web Auth client.
- Returns: Web Auth client.
*/
public func webAuth(clientId: String, domain: String, session: URLSession = .shared) -> WebAuth {
return Auth0WebAuth(clientId: clientId, url: .httpsURL(from: domain), session: session)
Expand Down
102 changes: 102 additions & 0 deletions Auth0/Auth0APIError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import Foundation

let apiErrorCode = "code"
let apiErrorDescription = "description"
let apiErrorCause = "cause"

/// Generic representation of Auth0 API errors.
public protocol Auth0APIError: Auth0Error {

/// Raw error values.
var info: [String: Any] { get }

/// Error code.
var code: String { get }

/// HTTP status code of the response.
var statusCode: Int { get }

/// Creates an error from a JSON response.
///
/// - Parameters:
/// - info: JSON response from Auth0.
/// - statusCode: HTTP status code of the response.
///
/// - Returns: A new `Auth0APIError`.
init(info: [String: Any], statusCode: Int)

}

public extension Auth0APIError {

/// The underlying `Error` value, if any. Defaults to `nil`.
var cause: Error? {
return self.info["cause"] as? Error
}

/// Whether the request failed due to network issues.
///
/// Returns `true` when the `URLError` code is one of the following:
/// - [dataNotAllowed](https://developer.apple.com/documentation/foundation/urlerror/datanotallowed)
/// - [notConnectedToInternet](https://developer.apple.com/documentation/foundation/urlerror/notconnectedtointernet)
/// - [networkConnectionLost](https://developer.apple.com/documentation/foundation/urlerror/networkconnectionlost)
/// - [dnsLookupFailed](https://developer.apple.com/documentation/foundation/urlerror/dnslookupfailed)
/// - [cannotFindHost](https://developer.apple.com/documentation/foundation/urlerror/cannotfindhost)
/// - [cannotConnectToHost](https://developer.apple.com/documentation/foundation/urlerror/cannotconnecttohost)
/// - [timedOut](https://developer.apple.com/documentation/foundation/urlerror/timedout)
/// - [internationalRoamingOff](https://developer.apple.com/documentation/foundation/urlerror/internationalroamingoff)
/// - [callIsActive](https://developer.apple.com/documentation/foundation/urlerror/callisactive)
///
/// The underlying `URLError` is available in the ``cause`` property.
var isNetworkError: Bool {
guard let code = (self.cause as? URLError)?.code else {
return false

Check warning on line 53 in Auth0/Auth0APIError.swift

View check run for this annotation

Codecov / codecov/patch

Auth0/Auth0APIError.swift#L53

Added line #L53 was not covered by tests
}

return Self.networkErrorCodes.contains(code)
}

}

extension Auth0APIError {

init(info: [String: Any], statusCode: Int = 0) {
self.init(info: info, statusCode: statusCode)
}

init(cause error: Error, statusCode: Int = 0) {
let info: [String: Any] = [
apiErrorCode: nonJSONError,
apiErrorDescription: "Unable to complete the operation.",
apiErrorCause: error
]
self.init(info: info, statusCode: statusCode)
}

init(description: String?, statusCode: Int = 0) {
let info: [String: Any] = [
apiErrorCode: description != nil ? nonJSONError : emptyBodyError,
apiErrorDescription: description ?? "Empty response body."
]
self.init(info: info, statusCode: statusCode)
}

init(from response: Response<Self>) {
self.init(description: string(response.data), statusCode: response.response?.statusCode ?? 0)
}

static var networkErrorCodes: [URLError.Code] {
return [
.dataNotAllowed,
.notConnectedToInternet,
.networkConnectionLost,
.dnsLookupFailed,
.cannotFindHost,
.cannotConnectToHost,
.timedOut,
.internationalRoamingOff,
.callIsActive
]
}

}
Loading
Loading