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-associationfile - Base URL
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 & Capabilitiessection 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
- You must have an Apple Developer Account
- Go to the
Signing & Capabilitiessection of your application - Add a capability by clicking the
+ Capabilitybutton - Choose
Associated Domains - 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

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()).
- Swift
- Objective-C
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()
}
}
}
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "LoginIDSDK/LoginIDSDK.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *baseUrl = @"<BASE_URL>";
[[LoginIDSDK shared] configureWithBaseUrl:baseUrl];
// Other setup code...
return YES;
}
...
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
| Parameter | Type | Required | Details |
|---|---|---|---|
| username | String | Yes | The 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. |
| options | LIDOptions? | No | Additional 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.
- Swift
- Objective-C
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)")
}
}
NSString *baseUrl = @"<BASE_URL>";
[[LoginIDSDK shared] configureWithBaseUrl:baseUrl];
LoginIDSDK *lidClient = [LoginIDSDK shared];
// Prepare inputs
NSString *username = @"billy@loginid.io";
LoginIDOptions *options = [[LoginIDOptions alloc] init]; // or nil
if (@available(iOS 16.0, *)) {
[lidClient authenticateWithPasskeyWithUsername:username
options:options
onComplete:^(AuthResult * _Nullable result,
LoginIDError * _Nullable error) {
if (error) {
NSLog(@"Failed with error: %@", error.message);
return;
}
NSLog(@"Success, token: %@", result.token);
}];
} else {
NSLog(@"authenticateWithPasskey requires iOS 16.0 or later.");
}
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
| Parameter | Type | Required | Details |
|---|---|---|---|
| username | String | Yes | The username for the account attempting to perform transaction confirmation. |
| txPayload | String | Yes | The transaction-specific payload, containing details like transaction amount, recipient, and other necessary metadata. |
| options | LIDOptions? | No | Additional options for transaction confirmation, including transaction type and other relevant information. |
| options.nonce | String | No | A unique nonce to ensure the transaction's integrity and prevent replay attacks |
| options.txType | String | No | Specify 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.
- Swift
- Objective-C
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)
}
}
NSString *baseUrl = @"<BASE_URL>";
[[LoginIDSDK shared] configureWithBaseUrl:baseUrl];
LoginIDSDK *lidClient = [LoginIDSDK shared];
// Prepare inputs
NSString *username = @"billy@loginid.io";
NSString *txPayload = @"999999";
NSString *nonce = @"f846bb01-492e-422b-944a-44b04adc441e";
LIDOptions *options = [[LIDOptions alloc] init];
options.nonce = nonce;
if (@available(iOS 16.0, *)) {
[lidClient confirmTransactionWithUsername:username
txPayload:txPayload
options:options
onComplete:^(TxConfirmResult * _Nullable result,
LoginIDError * _Nullable error) {
if (error) {
NSLog(@"Failed with error: %@", error.message);
return;
}
NSLog(@"Success, token: %@", result.token);
}];
} else {
NSLog(@"confirmTransaction requires iOS 16.0 or later.");
}
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:
- User must be signed in.
- Usage of authorization tokens.
public func createPasskey(
username: String,
authzToken: String?,
options: LoginIDOptions?
) async throws -> AuthResult
| Parameter | Type | Required | Details |
|---|---|---|---|
| username | String | Yes | The username for the account being registered. |
| authzToken | String? | No | Authorization token for passkey creation with the scope reg:write. |
| options | LIDOptions | No | Additional options for the passkey creation process. |
| options.displayName | String | No | A human-palatable name for the user account, intended only for display on your passkeys and modals. |
| options.usernameType | String | No | Specify 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.
- Swift
- Objective-C
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)
}
}
NSString *username = @"billy@loginid.io";
LIDOptions *options = [[LIDOptions alloc] init]; // or nil
[lidClient createPasskeyWithUsername:username
username:username
authzToken:nil
options:options
completion:^(AuthResult *authResponse, LoginIDError *error) {
if (error) {
NSLog(@"Failed with error: %@", error.message);
} else {
NSLog(@"Successful with response: %@", authResponse.token);
}
}];
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:
- User must be signed in.
- Usage of authorization tokens.
public func deletePasskey(
id: String,
options: LIDOptions?
) async throws
| Parameter | Type | Required | Details |
|---|---|---|---|
| id | String | Yes | The UUID of the passkey attempting to delete. |
| options | LIDOptions? | No | Additional options for the delete passkey process. |
| options.authzToken | string | No | The user's authorization token with the scope passkey:write. |
- Swift
- Objective-C
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)")
}
}
NSString *baseUrl = @"<BASE_URL>";
[[LoginIDSDK sharedClient] configureWithBaseUrl:baseUrl];
LoginIDSDK *lidClient = [LoginIDSDK sharedClient];
NSString *passkeyId = @"4df206a7-fbe2-4521-93a1-3db51ed0ee27";
LoginIDOptions *options = [[LoginIDOptions alloc] init];
[lidClient deletePasskeyWithId:passkeyId
options:options
onComplete:^(LoginIDError *error) {
if (error) {
NSLog(@"Failed with error: %@", error.localizedDescription);
} else {
NSLog(@"Passkey deleted");
}
}];
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:
- User must be signed in.
- Usage of authorization tokens.
public func listPasskeys(
options: LIDOptions?
) async throws -> [LoginIDPasskey]
| Parameter | Type | Required | Details |
|---|---|---|---|
| options | LIDOptions? | No | Additional options for the list passkeys process. |
| options.authzToken | String | No | The 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.
- Swift
- Objective-C
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)
}
}
NSString *baseUrl = @"<BASE_URL>";
[[LoginIDSDK shared] configureWithBaseUrl:baseUrl];
LoginIDSDK *lidClient = [LoginIDSDK shared];
// Prepare inputs
LIDOptions *options = [[LIDOptions alloc] init]; // or nil
[lidClient listPasskeysWithOptions:options
onComplete:^(NSArray<LoginIDPasskey *> * _Nullable result,
LoginIDError * _Nullable error) {
if (error) {
NSLog(@"%@", error.message);
return;
}
for (LoginIDPasskey *passkey in result) {
NSLog(@"%@", passkey.name);
}
}];
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:
- User must be signed in.
- Usage of authorization tokens.
public func renamePasskey(
id: String,
name: String,
options: LIDOptions?
) async throws
| Parameter | Type | Required | Details |
|---|---|---|---|
| id | String | Yes | The UUID of the passkey attempting to rename. |
| name | String | Yes | The new name for the passkey. |
| options | LIDOptions? | No | Additional options for the renaming passkey process. |
| options.authzToken | string | No | The user's authorization token with the scope passkey:write. |
- Swift
- Objective-C
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)")
}
}
NSString *baseUrl = @"<BASE_URL>";
[[LoginIDSDK sharedClient] configureWithBaseUrl:baseUrl];
LoginIDSDK *lidClient = [LoginIDSDK sharedClient];
NSString *passkeyId = @"4df206a7-fbe2-4521-93a1-3db51ed0ee27";
NSString *newName = @"My Favourite Passkey";
LoginIDOptions *options = [[LoginIDOptions alloc] init];
[lidClient renamePasskeyWithId:passkeyId
name:newName
options:options
onComplete:^(LoginIDError *error) {
if (error) {
NSLog(@"Failed with error: %@", error.localizedDescription);
} else {
NSLog(@"Passkey renamed");
}
}];
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
| Parameter | Type | Required | Details |
|---|---|---|---|
| username | String | Yes | The username for the account to which the code will be sent. |
| method | String | No | The method to use for sending the code. Valid values are email (default) and sms. |
| options | LIDOptions? | No | Additional options for the request. |
- Swift
- Objective-C
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)")
}
}
NSString *baseUrl = @"<BASE_URL>";
[[LoginIDSDK sharedClient] configureWithBaseUrl:baseUrl];
LoginIDSDK *lidClient = [LoginIDSDK sharedClient];
NSString *username = @"billy@loginid.io";
NSString *method = @"email";
LoginIDOptions *options = [[LoginIDOptions alloc] init];
[lidClient requestAndSendOtpWithUsername:username
method:method
options:options
onComplete:^(LoginIDError *error) {
if (error) {
NSLog(@"Failed with error: %@", error.localizedDescription);
} else {
NSLog(@"Code sent successfully");
}
}];
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
| Parameter | Type | Required | Details |
|---|---|---|---|
| username | String | Yes | The username used for passkey authentication and OTP request. |
| options | LIDOptions? | No | Additional options for the request. |
| options.authzToken | string | No | The 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.
- Swift
- Objective-C
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)")
}
}
NSString *baseUrl = @"<BASE_URL>";
[[LoginIDSDK sharedClient] configureWithBaseUrl:baseUrl];
LoginIDSDK *lidClient = [LoginIDSDK sharedClient];
NSString *username = @"billy@loginid.io";
NSString *method = @"email";
LoginIDOptions *options = [[LoginIDOptions alloc] init];
[lidClient requestOtpWithUsername:username
options:options
onComplete:^(LoginIDError *error) {
if (error) {
NSLog(@"Failed with error: %@", error.localizedDescription);
} else {
NSLog(@"Code sent successfully");
NSLog(@"Display OTP to user now: %@", result.createdAt)
}
}];
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
| Parameter | Type | Required | Details |
|---|---|---|---|
| username | String | Yes | The username for the account attempting to authenticate. |
| otp | String | Yes | The OTP code to authenticate. |
| options | LIDOptions? | No | Additional 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.
- Swift
- Objective-C
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)")
}
}
NSString *baseUrl = @"<BASE_URL>";
[[LoginIDSDK sharedClient] configureWithBaseUrl:baseUrl];
LoginIDSDK *lidClient = [LoginIDSDK sharedClient];
// Can be obtained from requestOtp/requestAndSendOtp or backend API
NSString *otp = @"123456";
NSString *username = @"billy@loginid.io";
LoginIDOptions *options = [[LoginIDOptions alloc] init];
[lidClient validateOtpWithUsername:username
otp:otp
options:options
onComplete:^(AuthResult *result, LoginIDError *error) {
if (error) {
NSLog(@"Failed with error: %@", error.localizedDescription);
} else {
NSLog(@"Successful with response: %@", result.token);
}
}];
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
}
- Swift
- Objective-C
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)")
}
}
[[LoginIDSDK shared] configureWithBaseUrl:@"<BASE_URL>"];
[[LoginIDSDK shared] verifyConfigSettingsWithCompletion:^(LoginIDConfigResult * _Nullable result, NSError * _Nullable error) {
if (error) {
if ([error.domain isEqualToString:@"LoginIDErrorDomain"]) {
NSLog(@"LoginID verification failed with error: %@", error.localizedDescription);
} else {
NSLog(@"Verification failed with error: %@", error);
}
return;
}
if (result) {
NSLog(@"Verification returned a solution: %@", result.solution);
} else {
NSLog(@"Configuration settings verified successfully.");
}
}];
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.
| Field | Type | Details |
|---|---|---|
| msg | string | The error code associated with the login error. |
| msgCode | string | The detailed message or description of the error. |
| message | string | The 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)")
}