A native WebAuthn and OTP authentication library built with Bun. Zero external authentication dependencies—everything is implemented using native Web Crypto APIs.
- WebAuthn/Passkeys - Full WebAuthn support for passwordless authentication
- Server-side registration and authentication verification
- Browser-side credential creation and assertion
- Platform authenticator detection
- Conditional UI (autofill) support
- TOTP (Two-Factor Authentication) - Time-based One-Time Password implementation
- Generate and verify TOTP codes
- Configurable time steps, digits, and algorithms (SHA-1, SHA-256, SHA-512)
- Generate otpauth:// URIs for authenticator apps
- QR Code Generation - Built-in QR code generation for 2FA setup
- SVG and Data URL output formats
- Configurable error correction levels
- Fully Typed - Complete TypeScript support with comprehensive type definitions
- Native Implementation - No dependency on external auth libraries; uses Bun's native crypto
| Feature | ts-auth | @simplewebauthn | otplib |
|---|---|---|---|
| WebAuthn Support | Yes | Yes | No |
| TOTP Support | Yes | No | Yes |
| QR Code Generation | Yes | No | No |
| Zero Auth Dependencies | Yes | No | No |
| Native Bun Crypto | Yes | No | No |
| Bundle Size | Minimal | ~50KB | ~25KB |
| Single Package | Yes | 2 packages (server + browser) | Yes |
- All-in-one solution - WebAuthn, TOTP, and QR codes in a single package
- Zero external auth dependencies - Uses native Web Crypto APIs and Bun's built-in crypto
- Bun-optimized - Built specifically for Bun's runtime for optimal performance
- Simpler API - Streamlined functions that are easy to understand and use
- Fully typed - Complete TypeScript definitions with no
anytypes in the public API
- Bun >= 1.0.0 (for server-side TOTP and WebAuthn verification)
- HTTPS - WebAuthn requires a secure context (HTTPS or localhost)
- Browser Support (for WebAuthn):
- Chrome/Edge 67+
- Firefox 60+
- Safari 13+
- Mobile: iOS 14.5+, Android Chrome 67+
# Using bun
bun add ts-auth
# Using npm
npm install ts-auth
# Using yarn
yarn add ts-auth
# Using pnpm
pnpm add ts-authimport {
generateRegistrationOptions,
verifyRegistrationResponse,
} from 'ts-auth'
// Generate registration options
const options = generateRegistrationOptions({
rpName: 'My App',
rpID: 'example.com',
userID: 'user-123',
userName: 'john@example.com',
userDisplayName: 'John Doe',
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'preferred',
},
})
// Send `options` to the browser...
// After receiving the response from the browser:
const verification = await verifyRegistrationResponse(
credential,
expectedChallenge,
'https://example.com',
'example.com',
)
if (verification.verified) {
// Store verification.registrationInfo.credential in your database
}import {
startRegistration,
browserSupportsWebAuthn,
platformAuthenticatorIsAvailable,
} from 'ts-auth'
// Check for WebAuthn support
if (!browserSupportsWebAuthn()) {
console.log('WebAuthn is not supported')
}
// Check for platform authenticator (Face ID, Touch ID, Windows Hello)
if (await platformAuthenticatorIsAvailable()) {
console.log('Platform authenticator available')
}
// Start registration with options from your server
const credential = await startRegistration(optionsFromServer)
// Send credential to your server for verificationimport {
generateAuthenticationOptions,
verifyAuthenticationResponse,
} from 'ts-auth'
// Generate authentication options
const options = generateAuthenticationOptions({
rpID: 'example.com',
allowCredentials: [{
id: storedCredentialId,
type: 'public-key',
}],
})
// After receiving the response from the browser:
const verification = await verifyAuthenticationResponse(
credential,
expectedChallenge,
'https://example.com',
'example.com',
storedPublicKey,
storedCounter,
)
if (verification.verified) {
// Update the stored counter with verification.authenticationInfo.newCounter
}import {
generateTOTPSecret,
generateTOTP,
verifyTOTP,
totpKeyUri,
} from 'ts-auth'
// Generate a secret for the user
const secret = generateTOTPSecret()
// Generate the otpauth:// URI for QR codes
const uri = totpKeyUri('user@example.com', 'MyApp', secret)
// Generate a TOTP code (for testing/display)
const code = generateTOTP({ secret })
// Verify a code submitted by the user
const isValid = verifyTOTP(userSubmittedCode, {
secret,
window: 1, // Allow 1 step before/after for clock drift
})import {
generateQRCodeSVG,
generateQRCodeDataURL,
QRErrorCorrection,
totpKeyUri,
} from 'ts-auth'
// Generate a QR code for TOTP setup
const uri = totpKeyUri('user@example.com', 'MyApp', secret)
// Generate as SVG (browser environment)
const svg = generateQRCodeSVG({
text: uri,
width: 256,
height: 256,
correctLevel: QRErrorCorrection.H,
})
// Generate as data URL for <img> tags
const dataUrl = await generateQRCodeDataURL({
text: uri,
width: 256,
height: 256,
})Here's a complete example showing WebAuthn registration and authentication:
// === SERVER SIDE ===
import {
generateRegistrationOptions,
generateAuthenticationOptions,
verifyRegistrationResponse,
verifyAuthenticationResponse,
} from 'ts-auth'
// Store for demo purposes (use a real database in production)
const userCredentials = new Map()
const challenges = new Map()
// 1. Registration: Generate options
function handleRegistrationStart(userId: string, userName: string) {
const options = generateRegistrationOptions({
rpName: 'My Application',
rpID: 'example.com',
userID: userId,
userName: userName,
})
// Store challenge for verification
challenges.set(userId, options.challenge)
return options
}
// 2. Registration: Verify response
async function handleRegistrationFinish(userId: string, credential: any) {
const expectedChallenge = challenges.get(userId)
const verification = await verifyRegistrationResponse(
credential,
expectedChallenge,
'https://example.com',
'example.com',
)
if (verification.verified && verification.registrationInfo) {
// Store credential for future authentication
userCredentials.set(userId, {
credentialId: verification.registrationInfo.credential.id,
publicKey: verification.registrationInfo.credential.publicKey,
counter: verification.registrationInfo.credential.counter,
})
return { success: true }
}
return { success: false }
}
// 3. Authentication: Generate options
function handleAuthenticationStart(userId: string) {
const stored = userCredentials.get(userId)
const options = generateAuthenticationOptions({
rpID: 'example.com',
allowCredentials: stored ? [{
id: stored.credentialId,
type: 'public-key',
}] : [],
})
challenges.set(userId, options.challenge)
return options
}
// 4. Authentication: Verify response
async function handleAuthenticationFinish(userId: string, credential: any) {
const stored = userCredentials.get(userId)
const expectedChallenge = challenges.get(userId)
const verification = await verifyAuthenticationResponse(
credential,
expectedChallenge,
'https://example.com',
'example.com',
stored.publicKey,
stored.counter,
)
if (verification.verified) {
// Update counter
stored.counter = verification.authenticationInfo!.newCounter
userCredentials.set(userId, stored)
return { success: true }
}
return { success: false }
}// === BROWSER SIDE ===
import { startRegistration, startAuthentication } from 'ts-auth'
// Registration
async function register() {
// Get options from server
const options = await fetch('/api/register/start').then(r => r.json())
// Create credential
const credential = await startRegistration(options)
// Send to server for verification
await fetch('/api/register/finish', {
method: 'POST',
body: JSON.stringify(credential),
})
}
// Authentication
async function login() {
// Get options from server
const options = await fetch('/api/auth/start').then(r => r.json())
// Get credential
const credential = await startAuthentication(options)
// Send to server for verification
await fetch('/api/auth/finish', {
method: 'POST',
body: JSON.stringify(credential),
})
}| Function | Description |
|---|---|
generateRegistrationOptions() |
Generate options for creating a new credential |
generateAuthenticationOptions() |
Generate options for authenticating with an existing credential |
verifyRegistrationResponse() |
Verify the registration response from the browser |
verifyAuthenticationResponse() |
Verify the authentication response from the browser |
startRegistration() |
Start the registration process in the browser |
startAuthentication() |
Start the authentication process in the browser |
browserSupportsWebAuthn() |
Check if the browser supports WebAuthn |
platformAuthenticatorIsAvailable() |
Check if a platform authenticator is available |
browserSupportsWebAuthnAutofill() |
Check if conditional UI is supported |
| Function | Description |
|---|---|
generateTOTPSecret() |
Generate a random base32-encoded secret |
generateTOTP() |
Generate a TOTP code |
verifyTOTP() |
Verify a TOTP code |
totpKeyUri() |
Generate an otpauth:// URI for authenticator apps |
| Function | Description |
|---|---|
generateQRCodeSVG() |
Generate a QR code as an SVG string |
generateQRCodeDataURL() |
Generate a QR code as a data URL |
createQRCode() |
Create a QR code instance attached to a DOM element |
All types are exported and available for use in your TypeScript projects:
import type {
// Configuration
AuthConfig,
AuthOptions,
// TOTP
TOTPOptions,
// WebAuthn
RegistrationOptions,
AuthenticationOptions,
RegistrationCredential,
AuthenticationCredential,
PublicKeyCredentialCreationOptions,
PublicKeyCredentialRequestOptions,
// QR Code
QRCodeOptions,
} from 'ts-auth'| Type | Description |
|---|---|
TOTPOptions |
Options for TOTP generation/verification (secret, step, digits, algorithm, window) |
RegistrationOptions |
Server-side options for WebAuthn registration |
AuthenticationOptions |
Server-side options for WebAuthn authentication |
QRCodeOptions |
Options for QR code generation (text, dimensions, colors, error correction) |
When implementing authentication, keep these security practices in mind:
- Always use HTTPS - WebAuthn only works in secure contexts
- Store challenges server-side - Generate challenges on the server and validate them; never trust client-provided challenges
- Validate the origin - Always verify the origin matches your expected domain
- Track credential counters - Store and validate the signature counter to detect cloned authenticators
- Use appropriate user verification - Set
userVerification: 'required'for sensitive operations
- Secure secret storage - Store TOTP secrets encrypted at rest
- Use timing-safe comparison - This library uses timing-safe comparison internally to prevent timing attacks
- Implement rate limiting - Protect against brute-force attacks on TOTP codes
- Consider backup codes - Provide users with backup codes in case they lose their authenticator
- Clock synchronization - The
windowparameter helps account for clock drift (default: 1 step = ±30 seconds)
- Transport security - Always use HTTPS/TLS for all authentication-related requests
- Session management - Implement secure session handling after successful authentication
- Audit logging - Log authentication attempts for security monitoring
You can configure ts-auth by creating an auth.config.ts file in your project root:
import type { AuthOptions } from 'ts-auth'
const config: AuthOptions = {
verbose: true, // Enable verbose logging
}
export default config| Option | Type | Default | Description |
|---|---|---|---|
verbose |
boolean |
false |
Enable verbose logging for debugging |
bun testPlease see our releases page for more information on what has changed recently.
Please see CONTRIBUTING for details.
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For casual chit-chat with others using this package:
Join the Stacks Discord Server
"Software that is free, but hopes for a postcard." We love receiving postcards from around the world showing where Stacks is being used! We showcase them on our website too.
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.
The MIT License (MIT). Please see LICENSE for more information.
Made with 💙
