Skip to main content

iOS

Initial Setup

The LoginID iOS SDK enables you to add passkey authentication in your native iOS application without having to redirect the user to any pages outside your application.

The SDK leverages the Authentication Services framework for creating and syncing passkeys with iCloud Keychain.

Required settings:

  • Configure apple-app-site-association file
  • Base URL
info

The LoginID iOS Mobile SDK requires iOS 15+ for compatibility.

Configure apple-app-site-association File

In order for passkeys to work with your iOS application, you need to link your application with a website. You do this by creating an apple-app-site-association and hosting it on your domain website. More info here.

You Need an Apple Developer Account

An Apple Developer Account is a requirement as the Associated Domains capability is not available for free. Currently, an Apple Developer Account can be obtained from https://developer.apple.com/support/enrollment.

Obtain Team ID and Bundler ID

  • The team ID can be obtained on your developer account console at https://developer.apple.com/account.
  • The bundler ID can be obtained on your Signing & Capabilities section of your application.

Host apple-app-site-association JSON File on Your Website Directory

To host an apple-app-site-association file, you need to serve it as a static JSON file on your website. Note that the file must be named exactly apple-app-site-association without the .json extension and your server must be configured to serve it as JSON. The file should be located at <WEBSITE_DOMAIN>/.well-known/apple-app-site-association in the root directory of your website.

Here is an example of the minimum required fields in the file:

{
"webcredentials": {
"apps": ["<TEAM_ID>.<BUNDLER_ID>"]
}
}

Use the following example as a template and replace TEAM_ID and BUNDLER_ID with your values.

More information here.

Create an Associated Domains Capability on Your iOS App

To enable this capability

  1. You must have an Apple Developer Account
  2. Go to the Signing & Capabilities section of your application
  3. Add a capability by clicking the + Capability button
  4. Choose Associated Domains
  5. Enter the domain of your hosted website. Make sure to prefix it with webcredentials. Here's an example of what it should look like:
webcredentials:example.com
Customer Dashboard

Create Application

Create a new application to obtain your base URL.

Add SDK to Existing Application

Adding the SDK to your application currently requires a download and manual import via an .xcframework.

Download the SDK

After creating an application, visit the iOS section in the Get Started guide to download the latest SDK. The SDK is provided as an .xcframework file, which will be imported into your application.

Import the Framework

Open your project in Xcode, then drag and drop the .xcframework file into the Xcode Navigator area. Alternatively, go to General -> Frameworks, Libraries, and Embedded Content to manually add it. Ensure it's set to Embed & Sign. The SDK doesn't require any other external dependencies, making it simple to integrate.

Create an SDK Instance

The LoginID API must be called before any other APIs. You should call this API within your App struct’s initializer (init()).


import SwiftUI
import LoginID

@main
struct MyApp: App {
init() {
let baseUrl = "<BASE_URL>"

// Initialize the singleton instance
LoginIDSDK.shared.configure(baseUrl: baseUrl)
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}

API Reference

authenticateWithPasskey

This method authenticates a user with a passkey and will trigger additional dialogs to guide the user through the process.

A short-lived authorization token is returned, allowing access to protected resources for the given user such as listing, renaming or deleting passkeys.

public func authenticateWithPasskey(
username: String,
options: LIDOptions?
) async throws -> AuthResult
ParameterTypeRequiredDetails
usernameStringYesThe username for the account attempting to authenticate. Passing an empty string will trigger usernameless authentication, where the user can select their passkey profile to sign-in with.
optionsLIDOptions?NoAdditional options for the sign-in process.

AuthResult {
token: String
fallbackOptions: [String]?
isAuthenticated: Bool
isFallback: Bool
userId: String?
passkeyId: String?
deviceId: String?
}

For more details about the LoginID token, click here.

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)

var lidClient : LoginIDSDK = LoginIDSDK.shared

...

let username = "billy@loginid.io"
var options : LIDOptions? = LIDOptions() //or nil

Task {
do {
let result = try await lidClient.authenticateWithPasskey(
username: username,
options: options,
)
print(result.token)
} catch let error as LoginIDError {
print("Failed with error: \(error.message)")
}
}

confirmTransaction

This method initiates a non-repudiation signature process by generating a transaction-specific challenge and then expects the client to provide an assertion response using a passkey.

This method is useful for confirming actions such as payments or changes to sensitive account information, ensuring that the transaction is being authorized by the rightful owner of the passkey.

For a more detailed guide click here.

public func confirmTransaction(
username: String,
txPayload: String,
options: LIDOptions?
) async throws -> TxConfirmResult
ParameterTypeRequiredDetails
usernameStringYesThe username for the account attempting to perform transaction confirmation.
txPayloadStringYesThe transaction-specific payload, containing details like transaction amount, recipient, and other necessary metadata.
optionsLIDOptions?NoAdditional options for transaction confirmation, including transaction type and other relevant information.
options.nonceStringNoA unique nonce to ensure the transaction's integrity and prevent replay attacks
options.txTypeStringNoSpecify the type of transaction being confirmed for additional validation.

TxConfirmResult {
token: String
credentialId: String
authCred: LoginIDPasskey?
}

For more details on validating the transaction token, see the transaction confirmation guide.

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)

var lidClient : LoginIDSDK = LoginIDSDK.shared

...
let username = "billy@loginid.io"
let txPayload = "999999"
let nonce = "f846bb01-492e-422b-944a-44b04adc441e"
var options: LIDOptions? = LIDOptions()
options.nonce = nonce

Task {
do {
let result = try await lidClient.confirmTransaction(
username: username,
txPayload: txPayload,
options: options,
)
print(result.token)
} catch let error as LoginIDError {
print(error.message)
}
}

createPasskey

This method helps to create a passkey. The only required parameter is the username, but additional attributes can be provided in the options parameter. Note: While the authorization token is optional, it must always be used in a production environment. You can skip it during development by adjusting the app configuration in the LoginID dashboard.

A short-lived authorization token is returned, allowing access to protected resources for the given user such as listing, renaming or deleting passkeys.

Requires one of the following to add a new passkey to an existing user:

public func createPasskey(
username: String,
authzToken: String?,
options: LoginIDOptions?
) async throws -> AuthResult
ParameterTypeRequiredDetails
usernameStringYesThe username for the account being registered.
authzTokenString?NoAuthorization token for passkey creation with the scope reg:write.
optionsLIDOptionsNoAdditional options for the passkey creation process.
options.displayNameStringNoA human-palatable name for the user account, intended only for display on your passkeys and modals.
options.usernameTypeStringNoSpecify username type validation. Types include email, phone, or other.

AuthResult {
token: String
fallbackOptions: [String]?
isAuthenticated: Bool
isFallback: Bool
userId: String?
passkeyId: String?
deviceId: String?
}

For more details about the LoginID token, click here.

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)

var lidClient : LoginIDSDK = LoginIDSDK.shared
...

let username = "billy@loginid.io"
var options: LIDOptions? = LIDOptions() //or nil

Task {
do {
let result = try await lidClient.createPasskey(
username: username,
authzToken: nil,
options: options,
)
print(result.token)
} catch let error as LoginIDError {
print(error.message)
}
}

deletePasskey

Delete a specified passkey by ID from LoginID. The user must be fully authorized for this call to succeed.

Requires one of the following:

public func deletePasskey(
id: String,
options: LIDOptions?
) async throws
ParameterTypeRequiredDetails
idStringYesThe UUID of the passkey attempting to delete.
optionsLIDOptions?NoAdditional options for the delete passkey process.
options.authzTokenstringNoThe user's authorization token with the scope passkey:write.

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)
var lidClient: LoginIDSDK = LoginIDSDK.shared

let passkeyId = "4df206a7-fbe2-4521-93a1-3db51ed0ee27"
var options: LIDOptions? = LIDOptions()

Task {
do {
try await lidClient.deletePasskey(
id: passkeyId,
options: options
)
print("Passkey deleted")
} catch let error as LoginIDError {
print("Failed with error: \(error.message)")
}
}

getSessionInfo

Check whether the user of the current session is authenticated and returns user info. This info is retrieved locally and no requests to backend are made.

public func getSessionInfo() -> SessionInfo?

Retrieves the session data SessionInfo or nil if not set.

SessionInfo {
username: String
id: String
}

let user = LoginIDSDK.shared.getSessionInfo();

listPasskeys

This method returns list of passkeys associated with the current user. The user must be fully authorized for this call to succeed.

Requires one of the following:

public func listPasskeys(
options: LIDOptions?
) async throws -> [LoginIDPasskey]
ParameterTypeRequiredDetails
optionsLIDOptions?NoAdditional options for the list passkeys process.
options.authzTokenStringNoThe user's authorization token with the scope passkey:read.

[LIDPasskeyInfo] {
createdAt: String
credentialSynced: Bool
id: String
name: String
}

An array of the user's passkeys.

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)

var lidClient : LoginIDSDK = LoginIDSDK.shared

...

var options: LIDOptions? = LIDOptions() //or nil

Task {
do {
let result = try await lidClient.listPasskeys(
options: options,
)

for passkey in result {
print(passkey.name)
}
} catch let error as LoginIDError {
print(error.message)
}
}

logout

Clears current user session. This method is executed locally and it just deletes authorization token from local persistant storage.

public func logout() -> Void

LoginIDSDK.shared.logout();

renamePasskey

Renames a specified passkey by ID. The user must be fully authorized for this call to succeed.

Requires one of the following:

public func renamePasskey(
id: String,
name: String,
options: LIDOptions?
) async throws
ParameterTypeRequiredDetails
idStringYesThe UUID of the passkey attempting to rename.
nameStringYesThe new name for the passkey.
optionsLIDOptions?NoAdditional options for the renaming passkey process.
options.authzTokenstringNoThe user's authorization token with the scope passkey:write.

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)

var lidClient: LoginIDSDK = LoginIDSDK.shared

let passkeyId = "4df206a7-fbe2-4521-93a1-3db51ed0ee27"
let newName = "My Favourite Passkey"
var options: LIDOptions? = LIDOptions()

Task {
do {
try await lidClient.renamePasskey(
id: passkeyId,
name: newName,
options: options
)
print("Passkey renamed")
} catch let error as LoginIDError {
print("Failed with error: \(error.message)")
}
}

requestAndSendOtp

This method requests an OTP from the backend to be sent via the selected method. The method of delivery should be based on the user's choice from the list of available options. This can be found in the result of authenticateWithPasskey method as fallbackOptions.

The username must have either a valid email or phone profile for the method to work.

Use the validateOtp method to verify the code sent to the user.

For further implementation details have a look at the cross device authentication guide.

public func requestAndSendOtp(
username: String,
method: String = "email",
options: LIDOptions?
) async throws
ParameterTypeRequiredDetails
usernameStringYesThe username for the account to which the code will be sent.
methodStringNoThe method to use for sending the code. Valid values are email (default) and sms.
optionsLIDOptions?NoAdditional options for the request.

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)
var lidClient: LoginIDSDK = LoginIDSDK.shared

let username = "billy@loginid.io"
let method = "email"
var options: LIDOptions? = LIDOptions()

Task {
do {
try await lidClient.requestAndSendOtp(
username: username,
method: method,
options: options
)
print("Code sent successfully")
} catch let error as LoginIDError {
print("Failed with error: \(error.message)")
}
}

requestOtp

This method returns a one-time OTP to be displayed on the current device. The user must be authenticated on this device. The OTP is meant for cross-authentication, where the user reads the OTP from the screen and enters it on the target device.

Use the validateOtp method to verify the code sent to the user.

For further implementation details have a look at the cross device authentication guide.

public func requestOtp(
username: String,
options: LIDOptions?
) async throws -> OTPResult
ParameterTypeRequiredDetails
usernameStringYesThe username used for passkey authentication and OTP request.
optionsLIDOptions?NoAdditional options for the request.
options.authzTokenstringNoThe user's authorization token with the scope reg:write.

OTPResult {
code: String
expiresAt: String
}

Where code is the OTP used for other forms of authentication and recovery flows.

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)
var lidClient: LoginIDSDK = LoginIDSDK.shared

let username = "billy@loginid.io"
var options: LIDOptions? = LIDOptions()

Task {
do {
let result = try await lidClient.requestOtp(
username: username,
options: options
)
print("Display OTP to user now: \(result.createdAt)")
} catch let error as LoginIDError {
print("Failed with error: \(error.message)")
}
}

validateOtp

This method verifies the OTP and returns an authorization token, which can be used with the passkeyCreate() method to create a new passkey. The authorization token has a short validity period and should be used immediately.

Have a look at the recovery flow guide on how to implement this feature.

public func validateOtp(
username: String,
otp: String,
options: LIDOptions?
) async throws -> AuthResult
ParameterTypeRequiredDetails
usernameStringYesThe username for the account attempting to authenticate.
otpStringYesThe OTP code to authenticate.
optionsLIDOptions?NoAdditional options for the authentication process.

AuthResult {
token: String
fallbackOptions: [String]?
isAuthenticated: Bool
isFallback: Bool
userId: String?
passkeyId: String?
deviceId: String?
}

For more details about the LoginID token, click here.

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)
var lidClient: LoginIDSDK = LoginIDSDK.shared

// Can be obtained from requestOtp/requestAndSendOtp or backend API
let otp = "123456"
let username = "billy@loginid.io"
var options: LIDOptions? = LIDOptions()

Task {
do {
let authResponse = try await lidClient.validateOtp(
username: username,
otp: otp,
options: options
)
print("Successful with response: \(authResponse.token)")
} catch let error as LoginIDError {
print("Failed with error: \(error.message)")
}
}

verifyConfigSettings

Validates the application's configuration settings and provides a suggested correction if any issues are detected.

public func verifyConfigSettings() async throws -> LoginIDConfigResult?

LoginIDConfigResult {
solution: String
code: String
errorMessage: String
}

LoginIDSDK.shared.configure(baseUrl: <BASE_URL>)

Task {
do {
let result = try await LoginIDSDK.shared.verifyConfigSettings()
if let result = result {
print("Verification returned a solution: \(result.solution)")
} else {
self.outputLabel.text = "Configuration settings verified successfully."
}
} catch let error as LoginIDError {
print("LoginID verification failed with error: \(error.message)")
}
}

Errors

LoginIDError

Can occur during the authentication process. It is designed to encapsulate detailed information about login-related errors, making it easier to handle and debug issues related to user authentication.

FieldTypeDetails
msgstringThe error code associated with the login error.
msgCodestringThe detailed message or description of the error.
messagestringThe detailed message or description of the error. (alias of msgCode)

Here is an example:


...
...

} catch let error as LoginIDError {
print("Failed with error: \(error.message)")
print("Failed with error code: \(error.msgCode)")
}