Skip to main content

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

Setup SDK

npm i @loginid/websdk3

Import and initialize an instance:

import { LoginIDWebSDK } from "@loginid/websdk3";

const lid = new LoginIDWebSDK({
baseUrl: process.env.LOGINID_BASE_URL,
});

Cross Authentication Method

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.

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);
}
}
};
};

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.

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);
}
}
};
};

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.

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);
}
}
};
};