passlessjs
v0.2.3
Published
Enterprise-grade authentication library with OAuth (Google, Yandex) and WebAuthn Passkeys
Downloads
638
Maintainers
Readme
PasslessJS - Enterprise Authentication Library
Full TypeScript implementation of OAuth (Google, Yandex) and WebAuthn Passkeys with SOLID architecture
✨ Features
- 🔐 OAuth 2.0 Support - Google & Yandex providers
- 🔑 WebAuthn Passkeys - Modern passwordless authentication
- 📘 Full TypeScript - Complete type safety with strict mode
- 🏗️ SOLID Architecture - Enterprise-grade design patterns
- 🔌 Extensible - Easy to add custom providers and stores
- 📦 Minimal Dependencies - Only requires @simplewebauthn/server
- ✅ Production Ready - Well-tested enterprise authentication
🚀 Quick Start
Installation
npm install passlessjs @simplewebauthn/server dotenvBasic Usage
import { Passless, PasskeyConfig } from 'passlessjs';
import dotenv from 'dotenv';
dotenv.config();
const passkeyConfig: PasskeyConfig = {
rpName: 'MyApp',
rpId: 'example.com',
origin: 'https://example.com',
};
const passless = new Passless({
oauth: {
google: {
clientId: process.env.PASSLESS_GOOGLE_CLIENT_ID!,
clientSecret: process.env.PASSLESS_GOOGLE_CLIENT_SECRET!,
redirectUri: 'https://example.com/oauth/google',
},
},
passkeyConfig,
});
// Get OAuth URL
const authUrl = passless.getAuthUrl('google', state);
// Exchange code for token
const { token, profile } = await passless.exchangeCode('google', code);
// Create passkey registration options
const options = await passless.passkeys.createRegistrationOptions(
userId, username, displayName
);
// Verify registration
const verification = await passless.passkeys.verifyRegistration(
response, challenge
);🏛️ Architecture Highlights
PasslessJS follows SOLID principles for enterprise-grade code:
- Single Responsibility - Each component has one clear purpose
- Open/Closed - Extend with custom providers without modifying core
- Liskov Substitution - Any provider/store implementation works seamlessly
- Interface Segregation - Depend only on what you need
- Dependency Inversion - Constructor-based dependency injection
🔧 Usage Examples
OAuth 2.0 Authentication
// Step 1: Get authorization URL
const authUrl = passless.getAuthUrl('google', state);
// Redirect user to authUrl
// Step 2: Handle callback
const { token, profile } = await passless.exchangeCode('google', code);
console.log(profile.email); // User's email from OAuth providerWebAuthn Passkey Registration
// Step 1: Create registration options
const options = await passless.passkeys.createRegistrationOptions(
userId,
username,
displayName
);
// Send options to client for credential creation
// Step 2: Verify registration response
const verification = await passless.passkeys.verifyRegistration(
clientResponse,
expectedChallenge
);
if (verification.verified) {
console.log('Passkey registered successfully');
}WebAuthn Passkey Authentication
// Step 1: Create authentication options
const options = await passless.passkeys.createAuthenticationOptions(userId);
// Send options to client for credential assertion
// Step 2: Verify authentication response
const verification = await passless.passkeys.verifyAuthentication(
clientResponse,
expectedChallenge
);
if (verification.verified) {
console.log('User authenticated via passkey');
}Custom OAuth Provider
import { BaseOAuthProvider } from 'passlessjs';
export class GitHubProvider extends BaseOAuthProvider {
readonly name = 'github';
getAuthUrl(state?: string): string {
return `https://github.com/login/oauth/authorize?client_id=${this.clientId}&state=${state}`;
}
getTokenEndpoint(): string {
return 'https://github.com/login/oauth/access_token';
}
getProfileEndpoint(): string {
return 'https://api.github.com/user';
}
}
const passless = new Passless({
passkeyConfig,
providers: [new GitHubProvider(clientId, clientSecret, redirectUri)],
});Custom Storage Backend
import { IChallengeStore } from 'passlessjs';
import Redis from 'redis';
export class RedisChallengeStore implements IChallengeStore {
constructor(private redis: Redis) {}
async save(challenge: string, data: any): Promise<void> {
await this.redis.setex(`challenge:${challenge}`, 300, JSON.stringify(data));
}
async get(challenge: string): Promise<any> {
const data = await this.redis.get(`challenge:${challenge}`);
return data ? JSON.parse(data) : null;
}
async delete(challenge: string): Promise<void> {
await this.redis.del(`challenge:${challenge}`);
}
}
const passless = new Passless({
passkeyConfig,
challengeStore: new RedisChallengeStore(redis),
});🔐 Security Best Practices
- Always save challenges - Store challenge before sending to client
- HTTPS only - WebAuthn requires secure context
- Challenge expiration - Implement timeout for stored challenges (recommended: 5-10 minutes)
- Rate limiting - Protect OAuth callback endpoints from brute force
- CSRF protection - Always validate state parameter in OAuth flow
- Secure storage - Don't store credentials in plain text; use database with proper encryption
📋 API Reference
Passless Class
new Passless(options: PasslessOptions)
// Methods
getAuthUrl(providerName: string, state?: string): string
exchangeCode(providerName: string, code: string): Promise<{ token, profile }>
getAvailableProviders(): string[]
// Properties
passkeys: PasskeyServicePasskeyService Class
// Registration
createRegistrationOptions(
userId: string,
username: string,
displayName: string
): Promise<PublicKeyCredentialCreationOptionsJSON>
verifyRegistration(
response: RegistrationResponseJSON,
expectedChallenge: string
): Promise<VerifiedRegistrationResponse>
// Authentication
createAuthenticationOptions(userId: string): Promise<PublicKeyCredentialRequestOptionsJSON>
verifyAuthentication(
response: AuthenticationResponseJSON,
expectedChallenge: string
): Promise<VerifiedAuthenticationResponse>
// Utilities
getUserCredentials(userId: string): Promise<CredentialRecord[]>Type Exports
import {
// Main classes
Passless,
PasskeyService,
BaseOAuthProvider,
GoogleProvider,
YandexProvider,
OAuthProviderFactory,
// Storage implementations
InMemoryChallengeStore,
InMemoryCredentialStore,
// Configuration types
PasskeyConfig,
PasslessOptions,
// Interfaces
IOAuthProvider,
IChallengeStore,
ICredentialStore,
CredentialRecord,
OAuthTokenResult,
// WebAuthn types (re-exported)
PublicKeyCredentialCreationOptionsJSON,
AuthenticationResponseJSON,
RegistrationResponseJSON,
VerifiedRegistrationResponse,
VerifiedAuthenticationResponse,
} from 'passlessjs';🐛 Common Issues
"Unknown or expired challenge"
Ensure you're saving the challenge before sending options to client:
const options = await passless.passkeys.createRegistrationOptions(...);
// SAVE THIS IMMEDIATELY
await yourStore.save(options.challenge, { userId, createdAt: Date.now() });"Unknown credential"
The credential is automatically saved after successful verification. Make sure the save operation completes before responding to client.
TypeScript Import Errors
Use named imports (not default import):
// ✅ Correct
import { Passless, PasskeyConfig } from 'passlessjs';
// ❌ Wrong
import Passless from 'passlessjs';📦 Package Contents
- dist/ - Compiled JavaScript (CommonJS format)
- index.d.ts - TypeScript type definitions
- Type definitions for all exported classes and interfaces
🤝 Contributing
This is an open-source project. Contributions are welcome!
📄 License
GPL-3.0-only
🙏 Built With
- SimpleWebAuthn - WebAuthn server library
- WebAuthn Level 3 - W3C standard
- OAuth 2.0 specifications from Google and Yandex
Enterprise authentication made simple 🔐
