Passlock
Passkey authentication for Astro, Sveltekit and other frameworks
**passlock** is a Passkey authentication for Astro, Sveltekit and other frameworks The project is written primarily in TypeScript, first published in 2024. Key topics include: astro, authentication, passkey, passkeys, svelte.
<a name="readme-top"></a>
<div align="center"> <picture align="center"> <source srcset="https://passlock-assets.b-cdn.net/images/client-repo-banner.dark.svg" media="(prefers-color-scheme: dark)" /> <img align="center" width=550 height=50 src="https://passlock-assets.b-cdn.net/images/client-repo-banner.svg" /> </picture> <p align="center"> Ship production-ready passkey authentication without becoming a WebAuthn expert <br /> <a href="https://passlock.dev"><strong>Project website »</strong></a> <br /> <a href="https://passlock.dev">Documentation</a> · <a href="https://passlock.dev/getting-started/">Quick start</a> · <a href="https://passlock.dev/#demo">Demo</a> </p> </div> <br />[!TIP]
Use our LLM Agent Skill to supercharge Codex, Claude, Copilot or your coding agent of choice :robot:
How Passlock works (in 60 seconds)
- Passlock handles WebAuthn complexity (browser quirks, ceremonies, encoding)
- Your backend authorizes each registration/authentication ceremony and sends a one-time token to the browser
- Your frontend completes the passkey ceremony using a simple JS API, resulting in a code and id_token (JWT)
- Your backend exchanges the code or verifies the JWT using our server library or REST API.
- You stay in control of users, sessions, and authorization
No SDK lock-in. No backend coupling.
This monorepo contains the public browser SDK, server SDK, CLI, and a reference SvelteKit example.
Who Passlock is for
- Developers looking for flexible integration options
- Teams needing to launch quickly, then adopt advanced features as the need arises
- Organizations who don't want to be locked into a product, framework or ecosystem
Key features
:unlock: No lock-in
Framework agnostic. Standards compliant.
:rocket: Zero config passkeys
Works out of the box with sensible defaults.
:arrow_right: Related origins
Migrate passkeys with backend-selected RP IDs and WebAuthn related-origin files.
:iphone: Credential management
Manage passkeys on end-user devices.
:muscle: Powerful
User verification, autofill, roaming authenticators and more.
Quick start
You can be up and running with a working passkey flow in minutes :rocket:
Create a new Passlock tenancy:
bashnpx @passlock/cli init
Take a note of your Tenancy ID and API Key.
Register a passkey
Passkey registration is a three-step process:
- Authorize registration: Your backend generates a registration token for the user.
- Browser ceremony: The browser asks the user to register a passkey.
- Exchange code: Your backend exchanges the code returned by the browser for a registered passkey.
[!TIP]
You only need to pass tokens (strings) between your backend and the browser, avoiding the need to handle JSON and binary data.
typescript// backend/registration.ts import { Passlock } from "@passlock/server"; const tenancyId = "myTenancyId"; const apiKey = "myApiKey"; const passlock = new Passlock({ tenancyId, apiKey }); const result = await passlock.authorizePasskeyRegistration( { rpId: "example.com", rpName: "Example App", userId: "user_123", username: "jdoe@gmail.com", displayName: "Jane Doe", } ); if (result.failure) { // handle the error throw new Error(result.error.message); } // send only this token to your frontend console.log("registration token: %s", result.value.registrationToken);
Choose rpId in your backend for each prepared passkey ceremony. Passlock no
longer uses a separate tenancy-level passkey settings RP ID.
typescript// frontend/register.ts import { Passlock } from "@passlock/browser"; const tenancyId = "myTenancyId"; const passlock = new Passlock({ tenancyId }); // call this in a click handler or similar action // ask your backend for a registration token const registrationToken = await fetchRegistrationToken(); const result = await passlock.registerPasskey({ registrationToken }); if (result.failure) { // handle the error throw new Error(result.error.message); } // send result.code or result.id_token to your backend for verification console.log("code: %s", result.value.code);
In your backend, exchange the code to obtain details about the completed registration. We'll use the @passlock/server library for this, but you can also make vanilla REST calls or verify the id_token instead.
typescript// backend/register.ts import { Passlock } from "@passlock/server"; const tenancyId = "myTenancyId"; const apiKey = "myApiKey"; const passlock = new Passlock({ tenancyId, apiKey }); const result = await passlock.exchangeCode({ code }); if (result.failure) { // handle the error throw new Error(result.error.message); } // includes details about the completed registration // link the authenticatorId to a local user account console.log("user id: %s", result.value.userId); console.log("passkey id: %s", result.value.authenticatorId);
Authenticate a passkey
Passkey authentication follows the same backend-authorized pattern as registration:
- Authorize authentication: Your backend decides the authentication policy and generates an authentication token.
- Browser ceremony: The browser asks the user to present a passkey.
- Exchange code: Your backend exchanges the code returned by the browser and looks up the user.
typescript// backend/authorize-authentication.ts import { Passlock } from "@passlock/server"; const tenancyId = "myTenancyId"; const apiKey = "myApiKey"; const passlock = new Passlock({ tenancyId, apiKey }); const result = await passlock.authorizePasskeyAuthentication({ rpId: "example.com", discoverable: true, }); if (result.failure) { // handle the error throw new Error(result.error.message); } // send only this token to your frontend console.log("authentication token: %s", result.value.authenticationToken);
typescript// frontend/authenticate.ts import { Passlock } from "@passlock/browser"; const tenancyId = "myTenancyId"; const passlock = new Passlock({ tenancyId }); // call this in a button click handler or similar action // ask your backend for an authentication token const authenticationToken = await fetchAuthenticationToken(); const result = await passlock.authenticatePasskey({ authenticationToken }); if (result.failure) { // handle the error throw new Error(result.error.message); } // send result.code or result.id_token to your backend for verification console.log("code: %s", result.value.code);
[!TIP]
To authenticate against a known account, authorize withuserId,allowCredentials, or both. For discoverable login, setdiscoverable: true; for autofill, also setmediation: "conditional".
In your backend, exchange the code and look up the user by userId or authenticatorId ...
typescript// backend/authenticate.ts import { Passlock } from "@passlock/server"; const tenancyId = "myTenancyId"; const apiKey = "myApiKey"; const passlock = new Passlock({ tenancyId, apiKey }); const result = await passlock.exchangeCode({ code }); if (result.failure) { // handle the error throw new Error(result.error.message); } // lookup the user based on their userId or authenticatorId console.log("user id: %s", result.value.userId); console.log("passkey id: %s", result.value.authenticatorId);
[!TIP]
Not using a JS backend? The examples in this README use our @passlock/server server library, but this is not required. Passlock works similarly to OAuth2/OpenID Connect, so you can make vanilla HTTP calls or use any suitable JWT library to verify anid_token(JWT).
More information
Please see the tutorial and documentation
If Passlock saved you time or helped you ship passkeys faster, a ⭐ on GitHub helps more than you think.
Contributors
Showing top 1 contributor by commit count.
