A reusable Svelte component library for passkey authentication, with framework-agnostic server-side helpers.
npm install svelte-passkey-component @simplewebauthn/browser @simplewebauthn/server
The GIF above demonstrates the component in action:
Note: This is a demonstration of the component's capabilities. For a complete working example, see the demo app in the repository.
<script>
import { PasskeyAuth } from 'svelte-passkey-component';
</script>
<!-- English (default) -->
<PasskeyAuth
registerRequestUrl="/api/auth/register-request"
registerResponseUrl="/api/auth/register-response"
loginRequestUrl="/api/auth/login-request"
loginResponseUrl="/api/auth/login-response"
logoutUrl="/api/auth/logout"
sessionUrl="/api/auth/session"
/>
<!-- Japanese -->
<PasskeyAuth
locale="ja"
registerRequestUrl="/api/auth/register-request"
registerResponseUrl="/api/auth/register-response"
loginRequestUrl="/api/auth/login-request"
loginResponseUrl="/api/auth/login-response"
logoutUrl="/api/auth/logout"
sessionUrl="/api/auth/session"
/>
<!-- Custom titles -->
<PasskeyAuth
locale="ja"
title="マイアプリにログイン"
dashboardTitle="マイアプリへようこそ"
registerRequestUrl="/api/auth/register-request"
registerResponseUrl="/api/auth/register-response"
loginRequestUrl="/api/auth/login-request"
loginResponseUrl="/api/auth/login-response"
logoutUrl="/api/auth/logout"
sessionUrl="/api/auth/session"
/>
<script>
import { AuthView, DashboardView } from 'svelte-passkey-component';
</script>
<!-- Auth View with Japanese -->
<AuthView
locale="ja"
registerRequestUrl="/api/auth/register-request"
registerResponseUrl="/api/auth/register-response"
loginRequestUrl="/api/auth/login-request"
loginResponseUrl="/api/auth/login-response"
/>
<!-- Auth View with custom title -->
<AuthView
locale="ja"
title="セキュアログイン"
registerRequestUrl="/api/auth/register-request"
registerResponseUrl="/api/auth/register-response"
loginRequestUrl="/api/auth/login-request"
loginResponseUrl="/api/auth/login-response"
/>
<!-- Dashboard View with Japanese -->
<DashboardView
locale="ja"
logoutUrl="/api/auth/logout"
/>
<!-- Dashboard View with custom title -->
<DashboardView
locale="ja"
title="管理画面"
logoutUrl="/api/auth/logout"
/>
---
// src/pages/app.astro
import PasskeyAuth from 'svelte-passkey-component';
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Passkey App</title>
</head>
<body>
<main>
<h1>My Secure Application</h1>
<PasskeyAuth
client:load
locale="ja"
registerRequestUrl="/api/auth/register-request"
registerResponseUrl="/api/auth/register-response"
loginRequestUrl="/api/auth/login-request"
loginResponseUrl="/api/auth/login-response"
logoutUrl="/api/auth/logout"
sessionUrl="/api/auth/session"
/>
</main>
</body>
</html>
The library provides framework-agnostic server-side helpers that work with any backend:
import {
generateRegistrationOptions,
verifyRegistration,
generateAuthenticationOptions,
verifyAuthentication
} from 'svelte-passkey-component/server';
import type { StorageAdapter, SessionAdapter } from 'svelte-passkey-component/server/adapters';
// Example with Express.js
app.post('/api/auth/register-request', async (req, res) => {
const { options, user } = await generateRegistrationOptions({
rpID: 'example.com',
rpName: 'Example App',
storageAdapter: yourStorageAdapter
});
res.json(options);
});
app.post('/api/auth/register-response', async (req, res) => {
const verification = await verifyRegistration({
response: req.body,
expectedChallenge: challengeFromSession,
expectedOrigin: 'https://example.com',
rpID: 'example.com',
storageAdapter: yourStorageAdapter,
user: userFromSession
});
if (verification.verified) {
res.json({ success: true, user: verification.user });
} else {
res.status(400).json({ success: false });
}
});
Currently supported languages:
en
) - Defaultja
) - 日本語You can extend the translations by adding new language support:
import { translations } from 'svelte-passkey-component';
// Add French support
translations.fr = {
auth: {
title: 'Authentification par Passkey',
signIn: 'Se connecter avec Passkey',
createAccount: 'Créer un nouveau compte avec Passkey',
processing: 'Traitement...',
error: 'Erreur:'
},
dashboard: {
welcome: 'Bienvenue !',
loggedIn: 'Vous êtes connecté.',
userHandle: 'Votre identifiant utilisateur:',
logout: 'Déconnexion'
}
};
<script>
import { browser } from '$app/environment';
// Auto-detect browser language
const locale = browser ? navigator.language.split('-')[0] : 'en';
</script>
<PasskeyAuth {locale} ... />
You can override the default titles while keeping the language support:
<!-- Custom titles with Japanese locale -->
<PasskeyAuth
locale="ja"
title="アプリケーションログイン"
dashboardTitle="ダッシュボード"
// ... other props
/>
<!-- Individual components with custom titles -->
<AuthView
locale="ja"
title="セキュア認証"
// ... other props
/>
<DashboardView
locale="ja"
title="ユーザー管理画面"
// ... other props
/>
Note: When title
or dashboardTitle
is provided, it overrides the default translation for that specific text while keeping other translations intact.
The library uses adapter patterns for database integration. You can implement your own adapters:
import type { StorageAdapter } from 'svelte-passkey-component/server/adapters';
// PostgreSQL with Prisma
export class PrismaStorageAdapter implements StorageAdapter {
constructor(private prisma: PrismaClient) {}
async getUser(userHandle: string) {
return await this.prisma.user.findUnique({
where: { id: userHandle },
include: { authenticators: true }
});
}
async createUser() {
return await this.prisma.user.create({
data: { id: randomUUID() },
include: { authenticators: true }
});
}
async getAuthenticator(credentialID: string) {
return await this.prisma.authenticator.findUnique({
where: { credentialID }
});
}
async saveAuthenticator(authenticator: Authenticator) {
await this.prisma.authenticator.create({
data: authenticator
});
}
async updateAuthenticatorCounter(credentialID: string, newCounter: number) {
await this.prisma.authenticator.update({
where: { credentialID },
data: { counter: newCounter }
});
}
async linkAuthenticatorToUser(userHandle: string, authenticator: Authenticator) {
await this.prisma.authenticator.update({
where: { credentialID: authenticator.credentialID },
data: { userId: userHandle }
});
}
}
import type { SessionAdapter } from 'svelte-passkey-component/server/adapters';
// Redis for sessions
export class RedisSessionAdapter implements SessionAdapter {
constructor(private redis: Redis) {}
async getSession(sessionId: string) {
return await this.redis.get(`session:${sessionId}`);
}
async setSession(sessionId: string, userHandle: string) {
await this.redis.setex(`session:${sessionId}`, 3600, userHandle); // 1 hour
}
async deleteSession(sessionId: string) {
await this.redis.del(`session:${sessionId}`);
}
}
Prop | Type | Default | Description |
---|---|---|---|
locale |
string |
'en' |
Language code (e.g., 'en', 'ja') |
title |
string |
- | Custom title for authentication view (overrides default translation) |
dashboardTitle |
string |
- | Custom title for dashboard view (overrides default translation) |
registerRequestUrl |
string |
- | URL for registration request endpoint |
registerResponseUrl |
string |
- | URL for registration response endpoint |
loginRequestUrl |
string |
- | URL for login request endpoint |
loginResponseUrl |
string |
- | URL for login response endpoint |
logoutUrl |
string |
- | URL for logout endpoint |
sessionUrl |
string |
- | URL for session check endpoint |
The component emits standard Svelte events and uses stores for state management:
import { authStore } from 'svelte-passkey-component';
// Subscribe to auth state changes
authStore.subscribe(state => {
console.log('Auth state:', state);
// { isLoggedIn: boolean, userHandle: string | null, error: string | null }
});
Contributions are welcome! Please feel free to open issues or submit pull requests.
MIT