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

@bernierllc/auth-service

v1.0.5

Published

Complete authentication and authorization service with multi-provider support and session management

Readme

@bernierllc/auth-service

Complete authentication and authorization service with multi-provider support and session management.

Features

  • 🔐 Multi-Provider Authentication - OAuth2, SAML, LDAP, local authentication
  • 🎫 JWT Token Management - Secure JWT generation and verification using crypto-utils
  • 🔄 Refresh Tokens - Automatic token renewal
  • 🛡️ Security Features - Rate limiting, account lockout, password policies
  • 📧 Password Reset - Magic link based password reset using crypto-utils
  • 👥 User Management - Registration, profile management
  • 📊 Audit Logging - Comprehensive authentication event logging
  • 🔒 Role-Based Access - RBAC with permissions (coming soon)

Installation

npm install @bernierllc/auth-service

Quick Start

import { AuthService } from '@bernierllc/auth-service';

// Initialize auth service
const authService = new AuthService({
  jwt: {
    secret: process.env.JWT_SECRET || 'your-secret-key',
    expiresIn: '15m',
    refreshExpiresIn: '7d'
  },
  password: {
    minLength: 8,
    requireUppercase: true,
    requireLowercase: true,
    requireNumbers: true,
    requireSymbols: true
  },
  security: {
    maxLoginAttempts: 5,
    lockoutDuration: 15 * 60 * 1000, // 15 minutes
    requireEmailVerification: false
  }
});

// Register a new user
const registerResult = await authService.register({
  username: 'johndoe',
  email: '[email protected]',
  password: 'SecurePass123!',
  firstName: 'John',
  lastName: 'Doe',
  acceptTerms: true
});

if (registerResult.success) {
  console.log('User registered:', registerResult.user);
  console.log('Access token:', registerResult.token);
}

// Login user
const loginResult = await authService.login({
  email: '[email protected]',
  password: 'SecurePass123!'
});

if (loginResult.success) {
  console.log('User logged in:', loginResult.user);
  console.log('Access token:', loginResult.token);
  console.log('Refresh token:', loginResult.refreshToken);
}

// Verify token
const user = await authService.verifyToken(loginResult.token!);
if (user) {
  console.log('Token valid for user:', user.email);
}

// Refresh token
const refreshResult = await authService.refreshToken(loginResult.refreshToken!);
if (refreshResult.success) {
  console.log('New access token:', refreshResult.token);
}

Password Reset Flow

// Generate password reset link
const resetResult = await authService.generatePasswordResetLink('[email protected]');
if (resetResult.success) {
  console.log('Password reset link sent');
  // In production, this would send an email with the reset link
}

// Reset password using token from email link
const newPassword = 'NewSecurePass123!';
const resetPasswordResult = await authService.resetPassword(resetToken, newPassword);

if (resetPasswordResult.success) {
  console.log('Password reset successfully');
}

Configuration

JWT Configuration

{
  jwt: {
    secret: string;                    // JWT signing secret
    issuer?: string;                   // JWT issuer claim
    audience?: string;                 // JWT audience claim
    expiresIn?: string | number;       // Access token expiry (default: '15m')
    refreshExpiresIn?: string | number; // Refresh token expiry (default: '7d')
    algorithm?: 'HS256' | 'HS384' | 'HS512' | 'RS256' | 'RS384' | 'RS512';
  }
}

Password Policy

{
  password: {
    minLength: number;          // Minimum password length
    requireUppercase: boolean;  // Require uppercase letters
    requireLowercase: boolean;  // Require lowercase letters
    requireNumbers: boolean;    // Require numbers
    requireSymbols: boolean;    // Require special characters
    preventReuse: number;       // Prevent reusing last N passwords
    maxAge?: number;           // Password expiry in milliseconds
  }
}

Security Settings

{
  security: {
    maxLoginAttempts: number;        // Max failed attempts before lockout
    lockoutDuration: number;         // Account lockout duration (ms)
    sessionTimeout: number;          // Session timeout (ms)
    requireEmailVerification: boolean; // Require email verification
    allowMultipleSessions: boolean;  // Allow multiple concurrent sessions
  }
}

MFA Configuration (Coming Soon)

{
  mfa: {
    enabled: boolean;
    issuer?: string;
    window?: number;
    backupCodes: {
      enabled: boolean;
      count: number;
      length: number;
    };
  }
}

API Reference

Class: AuthService

register(credentials: RegisterCredentials): Promise<AuthResult>

Register a new user account.

Parameters:

  • credentials.username - Unique username
  • credentials.email - Email address
  • credentials.password - Password (must meet policy requirements)
  • credentials.firstName - Optional first name
  • credentials.lastName - Optional last name
  • credentials.acceptTerms - Must be true
  • credentials.emailMarketing - Optional marketing consent

Returns: Promise resolving to AuthResult with user, token, and refreshToken

login(credentials: LoginCredentials): Promise<AuthResult>

Authenticate user login.

Parameters:

  • credentials.email or credentials.username - User identifier
  • credentials.password - Password
  • credentials.rememberMe - Optional remember me flag

Returns: Promise resolving to AuthResult with user, token, and refreshToken

verifyToken(token: string): Promise<User | null>

Verify and decode JWT access token.

Returns: Promise resolving to User object or null if invalid

refreshToken(refreshToken: string): Promise<AuthResult>

Generate new access token using refresh token.

Returns: Promise resolving to AuthResult with new tokens

generatePasswordResetLink(email: string): Promise<{success: boolean; error?: string}>

Generate password reset link for user.

resetPassword(token: string, newPassword: string): Promise<AuthResult>

Reset user password using magic link token.

Third-Party Authentication Adapters

The auth-service supports integration with third-party authentication providers through a flexible adapter pattern. This allows you to:

  • Use external authentication services (Clerk, Auth0, Firebase, etc.) for user authentication
  • Maintain internal authorization and permission management
  • Seamlessly integrate multiple authentication methods

Supported Adapters

  • ClerkAdapter - Clerk authentication service integration
  • 🔄 Auth0Adapter - Auth0 platform integration (coming soon)
  • 🔄 FirebaseAdapter - Firebase Authentication (coming soon)
  • 🔄 AzureADAdapter - Microsoft Azure Active Directory (coming soon)

🚀 Quick Start: Adding to Existing Authentication

Already have Clerk, Auth0, or another auth provider? You can add auth-service for authorization and permission management without changing your existing authentication flow.

Scenario: You Already Have Clerk Authentication

If you already have Clerk set up in your application, you can add auth-service to handle permissions and role-based access control:

Step 1: Install and Configure

// your-auth.ts - Add alongside your existing Clerk setup
import { AuthService, ClerkAdapter } from '@bernierllc/auth-service';

export const authService = new AuthService({
  jwt: {
    secret: process.env.JWT_SECRET!, // Different from Clerk's secret
    expiresIn: '1h'
  },
  
  // Configure the Clerk adapter
  adapters: [
    new ClerkAdapter({
      secretKey: process.env.CLERK_SECRET_KEY!, // Same as your existing Clerk setup
      publishableKey: process.env.CLERK_PUBLISHABLE_KEY!, // Same as your existing Clerk setup
      jwtKey: process.env.CLERK_JWT_KEY, // Optional - for better performance
      
      // Define your application's permissions
      roleMapping: {
        // Map Clerk roles/orgs to your app's permissions
        'admin': ['*'], // Full access
        'content-manager': [
          'content:read', 'content:write', 'content:delete',
          'workflow:approve', 'analytics:view'
        ],
        'editor': [
          'content:read', 'content:write', 'workflow:approve'
        ],
        'author': [
          'content:read', 'content:write'
        ],
        'viewer': [
          'content:read'
        ]
      }
    })
  ],
  
  // Disable local authentication - using Clerk for that
  providers: [],
  
  security: {
    maxLoginAttempts: 10, // Clerk handles rate limiting
    lockoutDuration: 30 * 60 * 1000,
    requireEmailVerification: false // Clerk handles this
  }
});

Step 2: Update Your Middleware (Keep Existing Clerk Code)

// middleware.ts - Your existing Clerk middleware with added permissions
import { auth } from '@clerk/nextjs/server';
import { authService } from './your-auth';

// Your existing Clerk middleware (keep as-is)
export default auth((req) => {
  // Your existing Clerk logic...
});

// Add permission checking helpers
export async function checkPermission(permission: string) {
  const { userId, sessionClaims } = auth();
  
  if (!userId) return false;
  
  // Use auth-service for permission checking
  return await authService.hasPermission(userId, permission);
}

export async function requirePermission(permission: string) {
  const hasPermission = await checkPermission(permission);
  
  if (!hasPermission) {
    throw new Error(`Permission denied: ${permission}`);
  }
}

Step 3: Add Permission Checks to Your Routes

// app/api/content/route.ts - Example API route
import { auth } from '@clerk/nextjs/server';
import { authService } from '@/lib/your-auth';
import { NextResponse } from 'next/server';

export async function GET() {
  const { userId, getToken } = auth();
  
  if (!userId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  // Check permission using auth-service
  const canRead = await authService.hasPermission(userId, 'content:read');
  if (!canRead) {
    return NextResponse.json({ error: 'Permission denied' }, { status: 403 });
  }
  
  // Your existing content logic...
  return NextResponse.json({ content: 'Your content here' });
}

export async function POST(request: Request) {
  const { userId } = auth();
  
  if (!userId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  // Check write permission
  const canWrite = await authService.hasPermission(userId, 'content:write');
  if (!canWrite) {
    return NextResponse.json({ error: 'Permission denied' }, { status: 403 });
  }
  
  // Your content creation logic...
  return NextResponse.json({ success: true });
}

Step 4: Frontend Permission Checks (React/Next.js)

// hooks/usePermissions.ts - Custom hook for frontend permission checking
import { useAuth, useUser } from '@clerk/nextjs';
import { useState, useEffect } from 'react';

export function usePermission(permission: string) {
  const { userId, getToken } = useAuth();
  const [hasPermission, setHasPermission] = useState<boolean | null>(null);
  
  useEffect(() => {
    async function checkPermission() {
      if (!userId) {
        setHasPermission(false);
        return;
      }
      
      try {
        const token = await getToken();
        
        // Call your API to check permission
        const response = await fetch('/api/auth/check-permission', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ permission })
        });
        
        const result = await response.json();
        setHasPermission(result.hasPermission);
      } catch (error) {
        console.error('Permission check failed:', error);
        setHasPermission(false);
      }
    }
    
    checkPermission();
  }, [userId, permission]);
  
  return hasPermission;
}

// API route: app/api/auth/check-permission/route.ts
import { auth } from '@clerk/nextjs/server';
import { authService } from '@/lib/your-auth';
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const { userId } = auth();
  
  if (!userId) {
    return NextResponse.json({ hasPermission: false });
  }
  
  const { permission } = await request.json();
  const hasPermission = await authService.hasPermission(userId, permission);
  
  return NextResponse.json({ hasPermission });
}

Step 5: Component-Level Permission Control

// components/ProtectedContent.tsx - Conditional rendering based on permissions
import { usePermission } from '@/hooks/usePermissions';
import { useUser } from '@clerk/nextjs';

interface ProtectedContentProps {
  permission: string;
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

export function ProtectedContent({ permission, children, fallback }: ProtectedContentProps) {
  const { isLoaded, isSignedIn } = useUser();
  const hasPermission = usePermission(permission);
  
  if (!isLoaded) {
    return <div>Loading...</div>;
  }
  
  if (!isSignedIn) {
    return fallback || <div>Please sign in</div>;
  }
  
  if (hasPermission === null) {
    return <div>Checking permissions...</div>;
  }
  
  if (!hasPermission) {
    return fallback || <div>Access denied</div>;
  }
  
  return <>{children}</>;
}

// Usage in your components
export function ContentManager() {
  return (
    <div>
      <h1>Content Manager</h1>
      
      {/* Show create button only if user can write */}
      <ProtectedContent permission="content:write">
        <button>Create New Content</button>
      </ProtectedContent>
      
      {/* Show approve button only if user can approve */}
      <ProtectedContent permission="workflow:approve">
        <button>Approve Content</button>
      </ProtectedContent>
      
      {/* Show analytics only for managers */}
      <ProtectedContent permission="analytics:view">
        <AnalyticsPanel />
      </ProtectedContent>
    </div>
  );
}

Alternative: Token-Based Integration (Any Auth Provider)

If you're using a different auth provider or want a more generic approach:

// auth-integration.ts - Generic token-based integration
import { AuthService } from '@bernierllc/auth-service';

const authService = new AuthService({
  jwt: { secret: process.env.JWT_SECRET! },
  
  // No adapters needed for generic integration
  adapters: [],
  providers: [],
  
  security: {
    maxLoginAttempts: 5,
    lockoutDuration: 15 * 60 * 1000,
    requireEmailVerification: false
  }
});

// Custom permission checker that works with any auth provider
export async function checkUserPermission(userId: string, permission: string): Promise<boolean> {
  // You can implement custom logic here:
  
  // Option 1: Check against a database
  const user = await getUserFromDatabase(userId);
  return user.permissions.includes(permission) || user.roles.some(role => 
    getRolePermissions(role).includes(permission)
  );
  
  // Option 2: Use auth-service's built-in permission system
  return await authService.hasPermission(userId, permission);
}

// Middleware for any framework
export function requirePermissionMiddleware(permission: string) {
  return async (req: any, res: any, next: any) => {
    const userId = req.user?.id; // However you get user ID from your auth provider
    
    if (!userId) {
      return res.status(401).json({ error: 'Unauthorized' });
    }
    
    const hasPermission = await checkUserPermission(userId, permission);
    
    if (!hasPermission) {
      return res.status(403).json({ error: `Permission denied: ${permission}` });
    }
    
    next();
  };
}

Key Benefits of This Approach

  1. 🔄 No Migration Required - Keep your existing authentication flow
  2. 🛡️ Enhanced Security - Add fine-grained permissions without touching auth
  3. 📈 Scalable - Easily add new permissions as your app grows
  4. 🔗 Framework Agnostic - Works with Next.js, Express, Fastify, etc.
  5. ⚡ Performance - Efficient permission checking with caching support
  6. 🧪 Testable - Easy to test permission logic independently

Migration Timeline

Week 1: Install and configure auth-service Week 2: Add permission checks to critical routes
Week 3: Implement frontend permission components Week 4: Roll out to all features

This approach lets you gradually add authorization features without disrupting your existing authentication setup.

Basic Adapter Usage

import { AuthService, ClerkAdapter } from '@bernierllc/auth-service';

const authService = new AuthService({
  // Standard configuration...
  jwt: {
    secret: process.env.JWT_SECRET!,
    expiresIn: '15m'
  },
  
  // Add third-party adapters
  adapters: [
    new ClerkAdapter({
      secretKey: process.env.CLERK_SECRET_KEY!,
      publishableKey: process.env.CLERK_PUBLISHABLE_KEY!,
      jwtKey: process.env.CLERK_JWT_KEY, // Optional - for networkless verification
      roleMapping: {
        'admin': ['*'], // Admin role gets all permissions
        'editor': ['content:read', 'content:write', 'workflow:approve'],
        'viewer': ['content:read']
      }
    })
  ],
  
  // RBAC configuration for internal authorization
  security: {
    maxLoginAttempts: 5,
    lockoutDuration: 15 * 60 * 1000,
    requireEmailVerification: false
  }
});

// Verify third-party tokens seamlessly
const user = await authService.verifyToken(clerkJwtToken);
if (user) {
  // Check permissions managed internally
  const canApprove = await authService.hasPermission(user.id, 'workflow:approve');
  console.log('User can approve workflows:', canApprove);
}

Clerk Integration Example

import { AuthService, ClerkAdapter } from '@bernierllc/auth-service';

// Configure Clerk adapter with role mapping
const clerkAdapter = new ClerkAdapter({
  secretKey: process.env.CLERK_SECRET_KEY!,
  publishableKey: process.env.CLERK_PUBLISHABLE_KEY!,
  jwtKey: process.env.CLERK_JWT_KEY, // For networkless token verification
  domain: 'your-domain.com', // Optional custom domain
  
  // Map Clerk roles to internal permissions
  roleMapping: {
    'admin': ['*'], // Admin gets all permissions
    'content-manager': ['content:read', 'content:write', 'content:delete'],
    'editor': ['content:read', 'content:write', 'workflow:approve'],
    'author': ['content:read', 'content:write'],
    'viewer': ['content:read']
  }
});

const authService = new AuthService({
  jwt: {
    secret: process.env.JWT_SECRET!,
    expiresIn: '1h'
  },
  adapters: [clerkAdapter],
  
  // No local providers needed - using Clerk for authentication
  providers: [],
  
  security: {
    maxLoginAttempts: 10, // Clerk handles this, but kept for local fallback
    lockoutDuration: 30 * 60 * 1000,
    requireEmailVerification: false // Clerk handles email verification
  }
});

// Usage in your application
export class ContentController {
  constructor(private authService: AuthService) {}
  
  async approveContent(req: Request, res: Response) {
    // Extract JWT from Authorization header
    const token = req.headers.authorization?.replace('Bearer ', '');
    if (!token) {
      return res.status(401).json({ error: 'No token provided' });
    }
    
    // Verify token (will use Clerk adapter automatically)
    const user = await this.authService.verifyToken(token);
    if (!user) {
      return res.status(401).json({ error: 'Invalid token' });
    }
    
    // Check permission (managed internally by auth-service)
    const canApprove = await this.authService.hasPermission(user.id, 'workflow:approve');
    if (!canApprove) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    // Proceed with content approval
    // ... your business logic here
    
    res.json({ success: true, message: 'Content approved' });
  }
}

Express Middleware Integration

import express from 'express';
import { AuthService, ClerkAdapter } from '@bernierllc/auth-service';

const app = express();

// Initialize auth service with Clerk
const authService = new AuthService({
  jwt: { secret: process.env.JWT_SECRET! },
  adapters: [
    new ClerkAdapter({
      secretKey: process.env.CLERK_SECRET_KEY!,
      publishableKey: process.env.CLERK_PUBLISHABLE_KEY!,
      roleMapping: {
        'admin': ['*'],
        'editor': ['content:read', 'content:write', 'workflow:approve'],
        'viewer': ['content:read']
      }
    })
  ]
});

// Authentication middleware
const authenticate = async (req: any, res: any, next: any) => {
  try {
    const token = req.headers.authorization?.replace('Bearer ', '');
    if (!token) {
      return res.status(401).json({ error: 'No token provided' });
    }
    
    const user = await authService.verifyToken(token);
    if (!user) {
      return res.status(401).json({ error: 'Invalid token' });
    }
    
    req.user = user; // Attach user to request
    next();
  } catch (error) {
    res.status(500).json({ error: 'Authentication error' });
  }
};

// Permission middleware
const requirePermission = (permission: string) => {
  return async (req: any, res: any, next: any) => {
    try {
      if (!req.user) {
        return res.status(401).json({ error: 'No authenticated user' });
      }
      
      const hasPermission = await authService.hasPermission(req.user.id, permission);
      if (!hasPermission) {
        return res.status(403).json({ error: `Permission denied: ${permission}` });
      }
      
      next();
    } catch (error) {
      res.status(500).json({ error: 'Permission check failed' });
    }
  };
};

// Protected routes
app.get('/api/content', authenticate, requirePermission('content:read'), (req, res) => {
  res.json({ content: 'Your content here' });
});

app.post('/api/content', authenticate, requirePermission('content:write'), (req, res) => {
  // Create content logic
  res.json({ success: true });
});

app.post('/api/content/:id/approve', authenticate, requirePermission('workflow:approve'), (req, res) => {
  // Approve content logic
  res.json({ success: true, message: 'Content approved' });
});

Creating Custom Adapters

You can create custom adapters for any authentication provider by implementing the AuthAdapter interface:

import { AuthAdapter, User, BaseAuthAdapter } from '@bernierllc/auth-service';

export class CustomAuthAdapter extends BaseAuthAdapter {
  name = 'custom-provider';
  type = 'custom' as const;
  
  constructor(private config: CustomAdapterConfig) {
    super();
    this.validateConfig(config);
  }
  
  async verifyToken(token: string): Promise<User | null> {
    try {
      // 1. Verify token with your auth provider
      const tokenData = await this.provider.verifyJWT(token);
      
      // 2. Get user information from token or API call
      const userData = await this.provider.getUser(tokenData.userId);
      
      // 3. Map to auth-service User format
      return {
        id: userData.id,
        username: userData.username,
        email: userData.email,
        emailVerified: userData.emailVerified,
        roles: this.mapRoles(userData.roles, this.config.roleMapping),
        permissions: this.mapPermissions(userData.roles, this.config.roleMapping),
        profile: userData.profile,
        preferences: userData.preferences || {},
        security: userData.security || {},
        createdAt: new Date(userData.createdAt),
        updatedAt: new Date(userData.updatedAt),
        lastLoginAt: userData.lastLoginAt ? new Date(userData.lastLoginAt) : undefined
      };
    } catch (error) {
      console.error('Token verification failed:', error);
      return null;
    }
  }
  
  async getUserById(userId: string): Promise<User | null> {
    // Implement user lookup by ID
    return null;
  }
}

// Use your custom adapter
const authService = new AuthService({
  adapters: [
    new CustomAuthAdapter({
      apiKey: process.env.CUSTOM_AUTH_API_KEY!,
      roleMapping: {
        'superuser': ['*'],
        'contributor': ['content:read', 'content:write']
      }
    })
  ]
});

Best Practices

  1. Security: Always validate tokens server-side
  2. Role Mapping: Define clear role-to-permission mappings
  3. Error Handling: Handle adapter failures gracefully
  4. Logging: Monitor authentication and authorization events
  5. Testing: Test with real tokens from your auth provider

Integration with Crypto-Utils

This service leverages @bernierllc/crypto-utils for:

  • JWT Generation & Verification - Secure token handling
  • Magic Links - Password reset functionality
  • API Key Generation - Secure random key generation
  • Password Hashing - Using bcrypt for secure password storage

Dependencies

  • @bernierllc/crypto-utils - Cryptographic utilities
  • @bernierllc/logger - Structured logging
  • bcrypt - Password hashing
  • uuid - Unique ID generation

Integration Status

  • Logger: integrated - Uses @bernierllc/logger for authentication event logging and security monitoring
  • NeverHub: not-applicable - Auth service operates independently without service discovery requirements
  • Docs-Suite: ready - Complete documentation with TypeScript examples, API reference, and integration guides

License

Copyright (c) 2025 Bernier LLC. All rights reserved.