Biometric Login with OAuth
Use Face ID or Touch ID to let returning users skip the full OAuth login flow
Biometric Login with OAuth
After a user signs in with an OAuth provider, you can store a session token behind Face ID or Touch ID so returning users skip the full login flow.
Prerequisites
- The starti.app SDK is installed and initialized
- An OAuth provider is configured for your app in the starti.app manager
- The device has biometric hardware (Face ID or fingerprint)
This pattern works with any OAuth provider. The examples below use MitID, but you can substitute Google, Apple, or any other supported provider.
Steps
Check biometric availability
Before offering biometric login, check that the device supports it:
const biometricType = await startiapp.Biometrics.getAuthenticationType();
const hasBiometrics = biometricType !== "none";If biometricType is "none", skip the biometric flow and always use the full OAuth login.
First login: authenticate and store the session
After a successful OAuth sign-in, send the authorization code to your backend as usual. When your backend returns a session token, store it with setSecuredContent:
// 1. Sign in with the OAuth provider
const result = await startiapp.Auth.signIn("signaturgruppenmitid");
if (!result.isSuccess) {
console.error("Sign in failed:", result.errorMessage);
return;
}
// 2. Exchange the code on your backend
const response = await fetch("/api/auth/mitid", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
authorizationCode: result.authorizationCode,
codeVerifier: result.codeVerifier,
redirectUri: result.redirectUri,
}),
});
const session = await response.json();
// 3. Store the session token behind biometrics
await startiapp.Biometrics.setSecuredContent(session.token);Saving does not trigger a biometric prompt.
Return visit: retrieve the session with biometrics
On subsequent visits, check if a stored session exists. If it does, retrieve it with a biometric prompt instead of starting a full OAuth login:
const hasSession = await startiapp.Biometrics.hasSecuredContent();
if (hasSession) {
const token = await startiapp.Biometrics.getSecuredContent(
"Log in",
"Verify your identity"
);
if (token) {
// Use the stored token with your backend
const response = await fetch("/api/auth/session", {
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
console.log("Resumed session");
} else {
// Token expired or invalid — clear and do full login
await startiapp.Biometrics.removeSecuredContent();
await doFullLogin();
}
} else {
// User cancelled biometrics or scan failed — do full login
await doFullLogin();
}
} else {
await doFullLogin();
}Sign out: clear stored data
When the user signs out, remove the stored session token alongside your normal sign-out logic:
await startiapp.Biometrics.removeSecuredContent();
await startiapp.Auth.signOut();Handle expired tokens gracefully. If getSecuredContent returns a token but your backend rejects it, call removeSecuredContent() and fall back to a full OAuth login. Note that getSecuredContent returns null both when nothing is stored and when the user cancels the biometric prompt — always treat null as "no stored session".
Complete example
await startiapp.initialize();
const biometricType = await startiapp.Biometrics.getAuthenticationType();
const hasBiometrics = biometricType !== "none";
async function login() {
// Try biometric resume first
if (hasBiometrics) {
const hasSession = await startiapp.Biometrics.hasSecuredContent();
if (hasSession) {
const token = await startiapp.Biometrics.getSecuredContent(
"Log in",
"Verify your identity"
);
if (token) {
const response = await fetch("/api/auth/session", {
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
const user = await response.json();
console.log("Welcome back,", user.name);
return;
}
// Token expired — clear and continue to full login
await startiapp.Biometrics.removeSecuredContent();
}
}
}
// Full OAuth login
const result = await startiapp.Auth.signIn("signaturgruppenmitid");
if (!result.isSuccess) {
alert("Login failed: " + result.errorMessage);
return;
}
const response = await fetch("/api/auth/mitid", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
authorizationCode: result.authorizationCode,
codeVerifier: result.codeVerifier,
redirectUri: result.redirectUri,
}),
});
const session = await response.json();
console.log("Logged in:", session.name);
// Store for biometric resume next time
if (hasBiometrics) {
await startiapp.Biometrics.setSecuredContent(session.token);
}
}
async function logout() {
await startiapp.Biometrics.removeSecuredContent();
await startiapp.Auth.signOut();
console.log("Logged out");
}