From 45162fa2daf8b939a7345d2b1d8dba649adaceba Mon Sep 17 00:00:00 2001 From: KaveeshaPiumini Date: Thu, 12 Feb 2026 20:41:17 +0530 Subject: [PATCH] add passkey docs --- .../config/vocabularies/vocab/accept.txt | 6 + .../guides/authentication/_category_.json | 6 + .../_category_.json | 6 + .../passwordless-authentication/passkeys.mdx | 257 ++++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100644 docs/content/guides/authentication/_category_.json create mode 100644 docs/content/guides/authentication/passwordless-authentication/_category_.json create mode 100644 docs/content/guides/authentication/passwordless-authentication/passkeys.mdx diff --git a/.vale/styles/config/vocabularies/vocab/accept.txt b/.vale/styles/config/vocabularies/vocab/accept.txt index 7d38ece65..32cac6ab3 100644 --- a/.vale/styles/config/vocabularies/vocab/accept.txt +++ b/.vale/styles/config/vocabularies/vocab/accept.txt @@ -12,4 +12,10 @@ Thunder npm pnpm [Vv]ite +[Pp]asswordless +[Uu]sernameless +APIs +UIs +iCloud +hostname diff --git a/docs/content/guides/authentication/_category_.json b/docs/content/guides/authentication/_category_.json new file mode 100644 index 000000000..d53da3228 --- /dev/null +++ b/docs/content/guides/authentication/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 5, + "label": "Authentication", + "collapsible": true +} + diff --git a/docs/content/guides/authentication/passwordless-authentication/_category_.json b/docs/content/guides/authentication/passwordless-authentication/_category_.json new file mode 100644 index 000000000..77009b113 --- /dev/null +++ b/docs/content/guides/authentication/passwordless-authentication/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "label": "Passwordless Authentication", + "collapsible": true +} + diff --git a/docs/content/guides/authentication/passwordless-authentication/passkeys.mdx b/docs/content/guides/authentication/passwordless-authentication/passkeys.mdx new file mode 100644 index 000000000..4ac851d62 --- /dev/null +++ b/docs/content/guides/authentication/passwordless-authentication/passkeys.mdx @@ -0,0 +1,257 @@ +--- +title: Passkeys +description: Configure and use Passkeys for passwordless sign-in +--- + +# Passkeys + +Use passkeys to offer phishing-resistant, passwordless authentication. Thunder exposes WebAuthn-based APIs to register passkey credentials and to authenticate users with those credentials. + +## Overview + +Passkeys are a modern, secure alternative to passwords based on the WebAuthn standard. They use public-key cryptography to provide: + +- **Phishing-resistant authentication**: Passkeys are bound to your domain and cannot be used on fake sites +- **Passwordless experience**: Users authenticate with biometrics, PINs, or security keys instead of remembering passwords +- **Cross-device compatibility**: Passkeys sync across devices via platform authenticators (e.g., iCloud Keychain, Google Password Manager) + +Thunder supports passkeys through three approaches: + +1. **Thunder Gate (Hosted UI)**: Use Thunder's hosted authentication pages (`thunder-gate`) by configuring passkeys in your application settings—no custom UI or API calls needed +2. **Atomic API approach**: Direct HTTP endpoints (`/register/passkey/*` and `/auth/passkey/*`) for full control over the registration and authentication flow +3. **Flow-based approach**: Integrate passkeys into orchestrated authentication/registration flows via the `/flow/execute` API, combining passkeys with other authentication methods + +All approaches follow the WebAuthn standard ceremony: +- **Registration**: Generate a credential creation challenge, collect the attestation from the browser's `navigator.credentials.create()`, and store the credential +- **Authentication**: Generate an assertion challenge, collect the signed assertion from `navigator.credentials.get()`, and verify it + +## Prerequisites + +- Serve the UI over HTTPS with a hostname that matches your WebAuthn Relying Party ID (RP ID). +- Add allowed origins for WebAuthn to your deployment configuration. Example (`repository/conf/deployment.yaml`): + +```yaml +passkey: + allowed_origins: + - "https://localhost:8090" + - "https://localhost:3000" +``` + +- Use a WebAuthn-capable browser (recent Chrome, Edge, Safari, or Firefox); you can confirm support at https://passkeys.dev/device-support/ or by checking `window.PublicKeyCredential` in the browser console. +- Ensure users already exist or are created beforehand in Thunder for passkey registration. + +## Use Thunder Gate (Hosted UI) + +The simplest way to enable passkeys is through Thunder Gate, Thunder's hosted authentication and registration UI. This approach uses OAuth2/OIDC authorization flow: + +1. **Create an application** in the Thunder Develop console (or via the Application API). +2. **Configure authentication flows** for the application: + - Navigate to **Applications** → Select your application → **Flows** tab + - Select an **Authentication Flow** that includes passkey authentication (e.g., "Passkey Authentication" or "Basic + Passkey Authentication and Registration Flow") + - Optionally, select a **Registration Flow** that includes passkey registration (e.g., "Passkey Registration Flow") + - The flow builder UI lets you customize which executors run and configure relying party settings +3. **Integrate with your application**: + - Redirect users to Thunder's OAuth2 authorize endpoint: + ``` + https://localhost:8090/oauth2/authorize?client_id=&redirect_uri=&response_type=code&scope=openid + ``` + - Thunder automatically redirects to Thunder Gate (e.g., `https://localhost:5190/gate/signin`) based on the `gate_client` configuration in `deployment.yaml` + - Thunder Gate renders the authentication UI based on your selected flow, handling passkey WebAuthn ceremonies in the browser + - After successful authentication, users are redirected back to your application with an authorization code (exchange it for tokens via `/oauth2/token`) + +This approach requires **no custom UI development** or direct WebAuthn API calls from your application. Thunder Gate (`thunder-gate`) handles: +- Rendering sign-in/registration prompts based on the configured flow +- Passkey registration during user sign-up (if registration flow includes passkey executor) +- Passkey authentication during sign-in +- Fallback to other authentication methods (password, social login, etc.) as defined in the flow +- All WebAuthn ceremony handling (`navigator.credentials.create()` and `.get()`) + +**When to use this approach:** +- You want a quick setup without building custom authentication UIs +- You're using Thunder as an OAuth2/OIDC provider for your applications +- You want Thunder to manage the full authentication experience with customizable flows + +**When to use API-based approaches:** +- You need full control over the UI/UX beyond what flow configuration offers +- You're building a mobile application or SPA with custom authentication flows that don't fit OAuth2 redirect flow +- You want to embed authentication directly in your application without redirects + +## Use Passkey Atomic API in Your Application +> The registration and authentication flows below describe the **atomic API** approach (direct `/register/passkey/*` and `/auth/passkey/*` calls). + +### Registration Flow + +1) **Start registration** – create WebAuthn creation options and a session token. + +```bash +curl -k -X POST https://localhost:8090/register/passkey/start \ + -H "Content-Type: application/json" \ + -d '{ + "userId": "", + "relyingPartyId": "localhost", + "relyingPartyName": "Thunder", + "authenticatorSelection": { + "userVerification": "preferred" + }, + "attestation": "none" + }' +``` + +Response fields: +- `publicKeyCredentialCreationOptions`: pass directly to `navigator.credentials.create()` (after Base64URL→ArrayBuffer conversion). +- `sessionToken`: required for the finish call. + +2) **Run WebAuthn ceremony in the browser** – call `navigator.credentials.create()` with the returned options. See the sample implementation in `samples/apps/react-vanilla-sample/src/services/authService.ts`. + +3) **Finish registration** – send the attestation result with the session token. + +```bash +curl -k -X POST https://localhost:8090/register/passkey/finish \ + -H "Content-Type: application/json" \ + -d '{ + "publicKeyCredential": { + "id": "", + "type": "public-key", + "response": { + "clientDataJSON": "", + "attestationObject": "" + } + }, + "sessionToken": "", + "credentialName": "My laptop key" + }' +``` + +On success, the API returns passkey registration metadata for the newly created credential (`CredentialID`, `CredentialName`, and `CreatedAt`). + +### Authentication Flow + +1) **Start authentication** – request assertion options. + +```bash +curl -k -X POST https://localhost:8090/auth/passkey/start \ + -H "Content-Type: application/json" \ + -d '{ + "userId": "", + "relyingPartyId": "localhost" + }' +``` + +- `userId` is optional for usernameless authentication. +- Response fields: + - `publicKeyCredentialRequestOptions`: pass to `navigator.credentials.get()`. + - `sessionToken`: required for finish. + +2) **Run WebAuthn assertion in the browser** – call `navigator.credentials.get()` with the options. + +3) **Finish authentication** – send the assertion result with the session token. + +```bash +curl -k -X POST https://localhost:8090/auth/passkey/finish \ + -H "Content-Type: application/json" \ + -d '{ + "publicKeyCredential": { + "id": "", + "type": "public-key", + "response": { + "clientDataJSON": "", + "authenticatorData": "", + "signature": "", + "userHandle": "" + } + }, + "sessionToken": "", + "skipAssertion": false + }' +``` + +On success, the API returns an authentication response compatible with other Thunder auth flows. + +## Use Passkeys With Flow/Execute + +Passkeys also work through the flow engine (`POST /flow/execute`), which returns dynamic prompts and additional data. + +### Authentication With Flow/Execute + +1) **Start the flow** – send an initial flow request. + +```bash +curl -k -X POST https://localhost:8090/flow/execute \ + -H "Content-Type: application/json" \ + -d '{ + "applicationId": "", + "flowType": "AUTHENTICATION" + }' +``` + +- The response includes a `flowId` and a step `action` for passkeys. `data.additionalData.passkeyChallenge` contains the WebAuthn request options. The passkey session token is stored server-side in the flow context runtime data and is managed by the server; the client does not need to read or send it. + +2) **Run WebAuthn in the browser** – call `navigator.credentials.get()` with the decoded `passkeyChallenge`. + +3) **Continue the flow** – post the assertion back to the flow engine. + +```bash +curl -k -X POST https://localhost:8090/flow/execute \ + -H "Content-Type: application/json" \ + -d '{ + "flowId": "", + "action": "", + "inputs": { + "credentialId": "", + "clientDataJSON": "", + "authenticatorData": "", + "signature": "", + "userHandle": "" + } + }' +``` + +- On success, the next response either completes the flow (with an assertion) or advances to the next step. + +### Registration With Flow/Execute + +> Passkey registration in a flow must run **after the user is created or identified**. Ensure the flow provisions the user (e.g., collecting username/email and running provisioning) or resolves an existing user before the passkey register start node, as shown in the bundled flow definitions. + +1) **Start a registration flow** – send an initial flow request. + +```bash +curl -k -X POST https://localhost:8090/flow/execute \ + -H "Content-Type: application/json" \ + -d '{ + "applicationId": "", + "flowType": "REGISTRATION" + }' +``` + +> Reminder: Calling `/flow/execute` with a flow that only runs the passkey registration executor is impractical; the flow must first provision or resolve the user so registration has a valid subject. + +- The response includes a `flowId`, a registration step `action`, and `data.additionalData.passkeyCreationOptions` with the WebAuthn creation options. The passkey session state is maintained server-side and associated with the flow, so clients only need to carry `flowId` (and the step `action`) between calls. Ensure the flow has already collected the user identifier before this step because registration requires a user ID. + +2) **Run WebAuthn in the browser** – call `navigator.credentials.create()` with the decoded `passkeyCreationOptions`. + +3) **Finish registration in the flow** – post the attestation back to the flow engine. + +```bash +curl -k -X POST https://localhost:8090/flow/execute \ + -H "Content-Type: application/json" \ + -d '{ + "flowId": "", + "action": "", + "inputs": { + "credentialId": "", + "clientDataJSON": "", + "attestationObject": "", + "credentialName": "My laptop key" + } + }' +``` + +- On success, the response includes credential metadata (e.g., `passkeyCredentialID`) or advances to the next step of the flow. + +## Common Issues + +- **Origin mismatch**: The browser origin must be listed under `passkey.allowed_origins` and must match the RP ID domain. +- **HTTP instead of HTTPS**: WebAuthn requires HTTPS in production. +- **Stale session token**: Use the `sessionToken` from the most recent start call for each ceremony. +- **Unsupported platform authenticator**: Adjust `authenticatorSelection` (e.g., `authenticatorAttachment`) in the start request to match the target device. +- **User not found**: Ensure the user exists in Thunder before registering a passkey or start authentication with a valid user ID (unless using usernameless auth). \ No newline at end of file