Sign In with Cross Authentication and Create Passkey
Cross Authentication happens when passkey authentication is not possible. This occurs when devices use different sync fabrics or cannot communicate directly with a passkey manager. To resolve this, LoginID uses OTP authentication to complete the sign-in process.
You can obtain an OTP for cross-authentication in the following ways:
- Sign in with a passkey on your original device and request an OTP using requestOtp method.
- Trigger an email with an OTP by using the requestAndSendOtp method.
Prerequisites
- Create an application to obtain a base URL
Setup SDK
- Javascript
- Kotlin
- Swift
npm i @loginid/websdk3
Import and initialize an instance:
import { LoginIDWebSDK } from "@loginid/websdk3";
const lid = new LoginIDWebSDK({
baseUrl: process.env.LOGINID_BASE_URL,
});
Initialize a singleton configuration for the LoginID service in the application's onCreate
method.
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
// Initialize the singleton instance
val config = LoginIDServiceConfig("<BASE_URL>")
LoginID.configure(config).client()
}
}
Configure the singleton instance of LIDClient
with a base URL during the app's launch in the AppDelegate
.
import LoginID
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
// Initialize the singleton instance
LIDClient.shared.configure(baseURL: "<BASE_URL>")
return true
}
}
Cross Authentication Method
- Request OTP From Original Device
- Email OTP
In this section, we explore how to implement OTP authentication via email. This method allows users to securely log in through a temporary OTP sent to their registered email. Below, we outline the process and provide sample code for integration.
Diagram
Imagine the following scenario:
- The user’s passkey is stored in their iPhone's iCloud Keychain, but they are attempting to sign in on a Windows PC
- The user request an OTP using the requestAndSendOtp method on their Windows PC
- The OTP is sent to the user’s registered email and displayed there.
- The user enters the OTP on the Windows PC to sign in using validateOtp.
- After successful authentication, the user is prompted to add a new passkey to the Windows PC using createPasskey.
Request and Send OTP
We send an OTP to the registered username using the requestAndSendOtp
method. LoginID will send the OTP via email to the user.
- Javascript
- Kotlin
- Swift
import { LoginIDWebSDK } from "@loginid/websdk3";
const lid = new LoginIDWebSDK({
baseUrl: process.env.REACT_APP_LOGINID_BASE_URL,
});
const SendOTP: React.FC = () => {
const [username, setUsername] = useState<string>("");
const [error, setError] = useState<string>("");
const [success, setSuccess] = useState<string>("");
const handleSendOTP = async (e: React.FormEvent) => {
e.preventDefault();
try {
// Send an OTP under the user's email
await lid.requestAndSendOtp(username, "email");
setSuccess("OTP sent successfully!");
} catch (e) {
if (e instanceof Error) {
setError(e.message);
}
}
};
};
import loginid.LoginID
import services.modals.MessageType
class MainActivity : AppCompatActivity() {
private lateinit var usernameEditText: EditText
private lateinit var errorTextView: TextView
private lateinit var generateCodeButton: Button
private fun handleSendOTP() {
val username = usernameEditText.text.toString()
errorTextView.text = ""
GlobalScope.launch(Dispatchers.Main) {
try {
// Send an OTP code under the user's email
LoginID.client().sendCode(username, MessageType.EMAIL)
successTextView.text = "OTP sent successfully!"
} catch (e: Exception) {
when (e) {
is LoginIDError -> {
errorTextView.text = e.message ?: "A LoginID error occurred"
}
else -> {
errorTextView.text = e.message ?: "An error occurred"
}
}
}
}
}
}
Coming soon
In this section, we’ll walk through how to request an OTP on the original device. Before generating the OTP, the user must authenticate using a passkey. Once authenticated, the OTP can be displayed to the user and used for authentication on another device.
Below are the steps and sample code to implement this process. You can integrate this feature within the user’s security or profile settings.
Diagram
Imagine the following scenario:
- The user’s passkey is stored in their iPhone's iCloud Keychain, but they are attempting to sign in on a Windows PC
- The user authenticates with their passkey on the iPhone to request an OTP using the requestOtp method.
- The user enters the OTP on the Windows PC to sign in using validateOtp.
- After successful authentication, the user is prompted to add a new passkey to the Windows PC using createPasskey.
Request OTP On Original Device
Request a temporary authentication OTP on the original device (e.g., iPhone). The user must authenticate with a passkey before the OTP is received.
- Javascript
- Kotlin
- Swift
import { LoginIDWebSDK } from "@loginid/websdk3";
const lid = new LoginIDWebSDK({
baseUrl: process.env.REACT_APP_LOGINID_BASE_URL,
});
const RequestOTP: React.FC = () => {
const [username, setUsername] = useState<string>("");
const [error, setError] = useState<string>("");
const [otp, setOtp] = useState<string>("");
const handleRequestOTP = async (e: React.FormEvent) => {
e.preventDefault();
try {
// Authenticate with passkey and display OTP on device that has passkey
const result = await lid.requestOtp(username);
setOtp(result.code);
} catch (e) {
if (e instanceof Error) {
setError(e.message);
}
}
};
};
import loginid.LoginID
import services.modals.MessageType
class MainActivity : AppCompatActivity() {
private lateinit var usernameEditText: EditText
private lateinit var errorTextView: TextView
private lateinit var codeTextView: TextView
private lateinit var generateCodeButton: Button
private fun handleGenerateCode() {
val username = usernameEditText.text.toString()
errorTextView.text = ""
GlobalScope.launch(Dispatchers.Main) {
try {
// Authenticate with passkey and display code on device that has passkey
val result = LoginID.client().generateCodeWithPasskey(this@MainActivity, username)
codeTextView.setText(result.code)
} catch (e: Exception) {
when (e) {
is LoginIDError -> {
errorTextView.text = e.message ?: "A LoginID error occurred"
}
else -> {
errorTextView.text = e.message ?: "An error occurred"
}
}
}
}
}
}
Coming soon
Validate the OTP
The user can now enter the OTP on the new device (e.g., Windows PC) to authenticate and sign in. You may prompt them to create a passkey.
- Javascript
- Kotlin
- Swift
import React, { useState } from "react";
import { useAuth } from "../../contexts/AuthContext";
import { LoginIDWebSDK } from "@loginid/websdk3";
const lid = new LoginIDWebSDK({
baseUrl: process.env.REACT_APP_LOGINID_BASE_URL,
});
const LoginWithOTP: React.FC = () => {
const [username, setUsername] = useState<string>("");
const [otp, setOtp] = useState<string>("");
const [error, setError] = useState<string>("");
const { setAuthUser } = useAuth();
const handleOTPLogin = async (e: React.FormEvent) => {
e.preventDefault();
try {
// Authenticate with the OTP
const { token } = await lid.validateOtp(username, otp);
// Return LoginID token to your backend for verification
setAuthUser(user);
} catch (e) {
if (e instanceof Error) {
setError(e.message);
}
}
};
};
import loginid.LoginID
class MainActivity : AppCompatActivity() {
private lateinit var usernameEditText: EditText
private lateinit var codeEditText: EditText
private lateinit var errorTextView: TextView
private lateinit var authenticateButton: Button
private fun handleAuthenticate() {
val username = usernameEditText.text.toString()
val otp = codeEditText.text.toString()
errorTextView.text = ""
GlobalScope.launch(Dispatchers.Main) {
try {
// User authenticates with passkey
val result = LoginID.client().authenticateWithCode(username, otp)
// Return LoginID token to your backend for verification
AuthContext.setAuthUser(user)
} catch (e: Exception) {
when (e) {
is LoginIDError -> {
errorTextView.text = e.message ?: "A LoginID error occurred"
}
else -> {
errorTextView.text = e.message ?: "An error occurred"
}
}
}
}
}
}
Coming soon
Once you have received the result LoginID token, you can send it to your backend, and verify it. For detailed technical instructions, refer to this section on verifying LoginID tokens.
(Optional) Add a Passkey on a New Device
After the user successfully authenticates with an OTP, the response returns a LoginID token, which can be used to add a new passkey to the current new device.
- Javascript
- Kotlin
- Swift
import React, { useState } from "react";
import * as backend from "../../services/main";
import { useAuth } from "../../contexts/AuthContext";
import { LoginIDWebSDK } from "@loginid/websdk3";
const lid = new LoginIDWebSDK({
baseUrl: process.env.REACT_APP_LOGINID_BASE_URL,
});
const AddPasskey: React.FC = () => {
const [username, setUsername] = useState<string>("");
const [error, setError] = useState<string>("");
const { user } = useAuth();
const handleAddPasskey = async (e: React.FormEvent) => {
e.preventDefault();
try {
// Local user access token has permission to add passkey to existing LoginID user
await lid.createPasskey(user.username);
} catch (e) {
if (e instanceof Error) {
setError(e.message);
}
}
};
};
import loginid.LoginID
class MainActivity : AppCompatActivity() {
private lateinit var errorTextView: TextView
private lateinit var addPasskeyButton: Button
private fun handleAddPasskey() {
val username = AuthContext.getUser().username
errorTextView.text = ""
GlobalScope.launch(Dispatchers.Main) {
try {
// Local user access token has permission to add passkey to existing LoginID user
val addPasskeyResult = LoginID.client().addPasskey(
this@MainActivity,
username,
)
} catch (e: Exception) {
when (e) {
is LoginIDError -> {
errorTextView.text = e.message ?: "A LoginID error occurred"
}
else -> {
errorTextView.text = e.message ?: "An error occurred"
}
}
Log.e("Error", "Error during passkey addition", e)
}
}
}
}
Coming soon