webauthn

Server Setup (SimpleWebAuthn)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "webauthn" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-webauthn

WebAuthn / Passkeys

Server Setup (SimpleWebAuthn)

import { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse, } from '@simplewebauthn/server';

const rpName = 'My App'; const rpID = 'myapp.com'; const origin = 'https://myapp.com';

Registration Flow

Server: Generate Options

app.post('/api/auth/register/options', auth, async (req, res) => { const user = req.user; const existingCredentials = await db.credential.findMany({ where: { userId: user.id }, });

const options = await generateRegistrationOptions({ rpName, rpID, userName: user.email, userDisplayName: user.name, excludeCredentials: existingCredentials.map((c) => ({ id: c.credentialId, transports: c.transports, })), authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred', }, });

// Store challenge for verification await db.challenge.upsert({ where: { userId: user.id }, create: { userId: user.id, challenge: options.challenge }, update: { challenge: options.challenge }, });

res.json(options); });

Server: Verify Registration

app.post('/api/auth/register/verify', auth, async (req, res) => { const user = req.user; const { challenge } = await db.challenge.findUnique({ where: { userId: user.id } });

const verification = await verifyRegistrationResponse({ response: req.body, expectedChallenge: challenge, expectedOrigin: origin, expectedRPID: rpID, });

if (verification.verified && verification.registrationInfo) { const { credential } = verification.registrationInfo; await db.credential.create({ data: { userId: user.id, credentialId: Buffer.from(credential.id), publicKey: Buffer.from(credential.publicKey), counter: credential.counter, transports: req.body.response.transports, }, }); }

res.json({ verified: verification.verified }); });

Authentication Flow

Server: Generate Options

app.post('/api/auth/login/options', async (req, res) => { const options = await generateAuthenticationOptions({ rpID, userVerification: 'preferred', // For passkeys (discoverable credentials), omit allowCredentials });

// Store challenge in session req.session.challenge = options.challenge; res.json(options); });

Server: Verify Authentication

app.post('/api/auth/login/verify', async (req, res) => { const credential = await db.credential.findUnique({ where: { credentialId: Buffer.from(req.body.id, 'base64url') }, });

const verification = await verifyAuthenticationResponse({ response: req.body, expectedChallenge: req.session.challenge, expectedOrigin: origin, expectedRPID: rpID, credential: { id: credential.credentialId, publicKey: credential.publicKey, counter: credential.counter, }, });

if (verification.verified) { // Update counter (replay protection) await db.credential.update({ where: { id: credential.id }, data: { counter: verification.authenticationInfo.newCounter }, });

const token = generateJWT(credential.userId);
res.json({ verified: true, token });

} });

Browser Client

import { startRegistration, startAuthentication } from '@simplewebauthn/browser';

// Register async function registerPasskey() { const optionsRes = await fetch('/api/auth/register/options', { method: 'POST' }); const options = await optionsRes.json();

const credential = await startRegistration({ optionsJSON: options });

const verifyRes = await fetch('/api/auth/register/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credential), }); return (await verifyRes.json()).verified; }

// Login async function loginWithPasskey() { const optionsRes = await fetch('/api/auth/login/options', { method: 'POST' }); const options = await optionsRes.json();

const credential = await startAuthentication({ optionsJSON: options });

const verifyRes = await fetch('/api/auth/login/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credential), }); return (await verifyRes.json()).token; }

Anti-Patterns

Anti-Pattern Fix

Not storing counter Store and verify counter to prevent replay attacks

Reusing challenges Generate fresh challenge for each ceremony

No fallback auth method Offer password/magic link as fallback

Hardcoded rpID Configure from environment, must match domain

Missing transports on exclude Store transports to improve UX on re-registration

Production Checklist

  • HTTPS required (WebAuthn mandates secure context)

  • Challenge generated per ceremony and expired

  • Counter stored and verified

  • Multiple credentials per user supported

  • Credential management UI (view, revoke)

  • Fallback authentication method available

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

webrtc

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review