npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

passlessjs

v0.2.3

Published

Enterprise-grade authentication library with OAuth (Google, Yandex) and WebAuthn Passkeys

Downloads

638

Readme

PasslessJS - Enterprise Authentication Library

Full TypeScript implementation of OAuth (Google, Yandex) and WebAuthn Passkeys with SOLID architecture

TypeScript Node.js License: GPL-3.0

✨ 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 dotenv

Basic 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 provider

WebAuthn 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

  1. Always save challenges - Store challenge before sending to client
  2. HTTPS only - WebAuthn requires secure context
  3. Challenge expiration - Implement timeout for stored challenges (recommended: 5-10 minutes)
  4. Rate limiting - Protect OAuth callback endpoints from brute force
  5. CSRF protection - Always validate state parameter in OAuth flow
  6. 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: PasskeyService

PasskeyService 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


Enterprise authentication made simple 🔐