Passkeys (WebAuthn)
Passkeys use the WebAuthn standard for phishing-resistant, passwordless authentication. Users sign in with a fingerprint, Face ID, or hardware security key. No passwords to forget or leak.
How It Works
Steward’s passkey flow is smart: it tries login first, and if the user doesn’t have a passkey registered, automatically falls back to registration.
1. User enters email
2. SDK calls POST /auth/passkey/login/options
3. If user exists → browser prompts for passkey → verify → JWT
4. If user is new (404) → browser prompts to create passkey → register → JWT
The entire flow is a single SDK call.
SDK Usage
import { StewardAuth } from "@stwd/sdk";
const auth = new StewardAuth({
baseUrl: "https://api.steward.fi",
storage: localStorage,
});
// One call handles both registration and login
const result = await auth.signInWithPasskey("user@example.com");
console.log(result);
// {
// token: "eyJhbGci...", // 15-min access token
// refreshToken: "stwd_rt_...", // 30-day refresh token
// expiresIn: 900,
// user: { id: "usr_...", email: "user@example.com", walletAddress: "0x..." }
// }
Passkeys require a browser environment with WebAuthn support. Calling signInWithPasskey in Node.js throws an error. Use signInWithEmail or signInWithSIWE for server-side auth.
React Usage
The <StewardLogin> component includes passkey support by default:
import { StewardProvider, StewardLogin } from "@stwd/react";
function LoginPage() {
return (
<StewardProvider
client={client}
agentId="my-agent"
auth={{ baseUrl: "https://api.steward.fi" }}
>
<StewardLogin
showPasskey // enabled by default
showEmail={false} // hide email if you only want passkeys
showGoogle={false}
showDiscord={false}
title="Welcome"
onSuccess={(result) => console.log("Signed in:", result.user)}
/>
</StewardProvider>
);
}
Peer Dependency
The SDK dynamically imports @simplewebauthn/browser for the WebAuthn ceremony. Install it as a peer dependency:
npm install @simplewebauthn/browser
If the package is missing, signInWithPasskey throws a clear error message.
Server Configuration
To enable passkeys on your self-hosted Steward instance, set these environment variables:
# The relying party ID — usually your domain (no protocol, no port)
PASSKEY_RP_ID=myapp.com
# The expected origin for WebAuthn ceremonies
PASSKEY_ORIGIN=https://myapp.com
# Display name shown in the browser's passkey dialog
PASSKEY_RP_NAME="My App"
PASSKEY_RP_ID must match the domain where your app is served. For local development, use localhost and set PASSKEY_ORIGIN=http://localhost:3000.
API Endpoints
| Endpoint | Method | Description |
|---|
/auth/passkey/login/options | POST | Get WebAuthn authentication options for an existing user |
/auth/passkey/login/verify | POST | Verify a WebAuthn authentication response |
/auth/passkey/register/options | POST | Get WebAuthn registration options for a new user |
/auth/passkey/register/verify | POST | Verify a WebAuthn registration response |
/auth/mfa/passkey/options | POST | Get session-bound passkey MFA options for the signed-in user |
/auth/mfa/passkey/complete | POST | Complete passkey MFA step-up and receive a refreshed recent-MFA session |
Login and registration endpoints accept { email, tenantId? } in the request body.
MFA endpoints require the current bearer session; the options response includes
challengeId, and completion sends { challengeId, response }.
Browser Support
Passkeys are supported in all modern browsers:
- Chrome 67+
- Safari 14+
- Firefox 60+
- Edge 18+
On mobile, passkeys integrate with the platform’s credential manager (iCloud
Keychain on iOS, Google Password Manager on Android). @stwd/react-native
exposes native bridge helpers for passkey login, registration, and MFA step-up:
your app supplies a platform credential-manager adapter with
startAuthentication() and startRegistration(), and Steward handles the API
challenge/verification/session exchange.