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

@ofeklabs/horizon-auth

v1.0.8

Published

Production-ready NestJS authentication module with 2FA, device management, push notifications, and account management

Readme

@ofeklabs/horizon-auth

Production-ready NestJS authentication module with 2026 security standards. Deploy once, use everywhere.

Use Cases

1. Portfolio SSO (Recommended)

Deploy one auth service, use it across all your projects. Users sign in once and access everything.

auth.yourdomain.com (Auth Service)
    ↓ Shared Authentication
    ├── project1.yourdomain.com
    ├── project2.yourdomain.com
    └── project3.yourdomain.com

2. Embedded Auth

Each application has its own isolated authentication.

Features

  • 🔐 Modern Security: Argon2id password hashing, RS256 JWT signing
  • 🔄 Refresh Token Rotation: Automatic token rotation with reuse detection
  • 🚫 Redis Blacklisting: Revoked token management with TTL
  • 🏢 Multi-Tenant Support: Built-in tenant isolation
  • 🌍 Cross-Language: JWKS endpoint for polyglot microservices
  • Rate Limiting: Built-in protection against brute force attacks
  • 🎯 Type-Safe: Full TypeScript support
  • 📦 Zero Config: Sensible defaults, fully customizable

Quick Start

Portfolio SSO Setup (Recommended)

Deploy one auth service, use it across all your projects.

1. Deploy Auth Service (One Time)

# Clone and deploy packages/horizon-auth to auth.yourdomain.com
npm install
npm run build
npm run start:prod

Environment Variables:

DATABASE_URL=postgresql://...
REDIS_HOST=your-redis-host
JWT_PRIVATE_KEY=<your-private-key>
JWT_PUBLIC_KEY=<your-public-key>
COOKIE_DOMAIN=.yourdomain.com
NODE_ENV=production

2. Use in Your Projects

For each project:

npm install @ofeklabs/horizon-auth
// app.module.ts
HorizonAuthModule.forRoot({
  ssoMode: true,
  authServiceUrl: process.env.AUTH_SERVICE_URL,
  jwt: {
    publicKey: process.env.JWT_PUBLIC_KEY,
  },
  cookie: {
    domain: process.env.COOKIE_DOMAIN,
    secure: process.env.NODE_ENV === 'production',
  },
})
# .env
AUTH_SERVICE_URL=https://auth.yourdomain.com
JWT_PUBLIC_KEY=<paste-public-key>
COOKIE_DOMAIN=.yourdomain.com

Deploy to:

  • project1.yourdomain.com
  • project2.yourdomain.com
  • project3.yourdomain.com

Users sign in once, access all projects.


Embedded Auth Setup (Alternative)

Each application has its own authentication.

1. Install

npm install @ofeklabs/horizon-auth @prisma/client

2. CRITICAL: Add Required Prisma Models

The package requires specific Prisma models in your schema. Copy all models from node_modules/@ofeklabs/horizon-auth/prisma/schema.prisma to your prisma/schema.prisma:

// Your existing datasource and generator
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

// ADD these models from @ofeklabs/horizon-auth
model User {
  id                 String          @id @default(cuid())
  email              String          @unique
  fullName           String?
  passwordHash       String?
  emailVerified      Boolean         @default(false)
  emailVerifyToken   String?         @unique
  resetToken         String?         @unique
  resetTokenExpiry   DateTime?
  tenantId           String          @default("default")
  roles              String[]        @default(["user"])
  isActive           Boolean         @default(true)
  deactivationReason String?
  refreshTokens      RefreshToken[]
  socialAccounts     SocialAccount[]
  devices            Device[]
  pushTokens         PushToken[]
  twoFactorAuth      TwoFactorAuth?
  backupCodes        BackupCode[]
  createdAt          DateTime        @default(now())
  updatedAt          DateTime        @updatedAt

  @@index([email])
  @@index([tenantId])
  @@index([isActive])
  @@map("users")
}

model RefreshToken {
  // ... (see prisma/schema.prisma for complete definition)
}

// ... (copy all other models)

See node_modules/@ofeklabs/horizon-auth/prisma/README.md for complete schema and customization options.

Then generate Prisma Client and run migrations:

npx prisma generate
npx prisma migrate dev --name add-horizon-auth-models

3. Create PrismaModule

npm install @ofeklabs/horizon-auth @prisma/client ioredis passport passport-jwt
npm install -D prisma

2. Generate RSA Keys

# Generate private key
openssl genrsa -out private.pem 2048

# Generate public key
openssl rsa -in private.pem -pubout -out public.pem

3. Configure Module

// app.module.ts
import { Module } from '@nestjs/common';
import { HorizonAuthModule } from '@ofeklabs/horizon-auth';
import { readFileSync } from 'fs';
import { join } from 'path';

@Module({
  imports: [
    HorizonAuthModule.forRoot({
      database: {
        url: process.env.DATABASE_URL,
      },
      redis: {
        host: process.env.REDIS_HOST || 'localhost',
        port: parseInt(process.env.REDIS_PORT) || 6379,
        password: process.env.REDIS_PASSWORD,
      },
      jwt: {
        // For production: use environment variables
        privateKey: process.env.JWT_PRIVATE_KEY || readFileSync(join(__dirname, '../certs/private.pem'), 'utf8'),
        publicKey: process.env.JWT_PUBLIC_KEY || readFileSync(join(__dirname, '../certs/public.pem'), 'utf8'),
      },
    }),
  ],
})
export class AppModule {}

4. Set Up Database

# Start PostgreSQL and Redis
docker-compose up -d

# Run Prisma migrations
npx prisma migrate dev

5. Use in Controllers

import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
import {
  Public,
  CurrentUser,
  JwtAuthGuard,
  Roles,
  LoginDto,
  RegisterDto,
} from '@ofeklabs/horizon-auth';

@Controller()
export class AppController {
  // Public endpoint - no authentication required
  @Public()
  @Get()
  getHello() {
    return { message: 'Hello World' };
  }

  // Protected endpoint - requires JWT
  @UseGuards(JwtAuthGuard)
  @Get('profile')
  getProfile(@CurrentUser() user) {
    return user;
  }

  // Role-based access control
  @UseGuards(JwtAuthGuard)
  @Roles('admin')
  @Get('admin')
  adminOnly() {
    return { message: 'Admin access granted' };
  }
}

API Endpoints

The package automatically provides these endpoints:

Authentication

  • POST /auth/register - Register new user
  • POST /auth/login - Login with email/password
  • POST /auth/refresh - Refresh access token
  • POST /auth/logout - Logout and revoke tokens
  • GET /auth/profile - Get current user profile

Password Management

  • POST /auth/password-reset/request - Request password reset
  • POST /auth/password-reset/complete - Complete password reset
  • POST /auth/password-reset/verify-otp - Verify password reset OTP
  • POST /auth/verify-email - Verify email address (supports OTP and magic link)
  • POST /auth/resend-verification - Resend verification email

Cross-Language Support

  • GET /.well-known/jwks.json - Public keys for JWT verification

Configuration Options

interface HorizonAuthConfig {
  // Required
  database: {
    url: string;
  };
  redis: {
    host: string;
    port: number;
    password?: string;
    db?: number;
  };
  jwt: {
    privateKey: string; // RSA private key (PEM format)
    publicKey: string; // RSA public key (PEM format)
    accessTokenExpiry?: string; // Default: '15m'
    refreshTokenExpiry?: string; // Default: '7d'
    issuer?: string; // Default: 'horizon-auth'
    audience?: string; // Default: 'horizon-api'
  };

  // Optional
  email?: {
    from: string; // Sender email address
    customSender?: (to: string, subject: string, html: string) => Promise<void>;
    provider?: 'resend' | 'sendgrid';
    apiKey?: string; // Required if using provider
  };
  emailVerification?: {
    method: 'otp' | 'magic-link' | 'both'; // Default: 'otp'
    otpLength?: number; // 4-8 digits, default: 6
    otpExpiry?: number; // Minutes, default: 10
    tokenExpiry?: number; // Hours, default: 24
  };
  passwordReset?: {
    method: 'otp' | 'magic-link' | 'both'; // Default: 'otp'
    otpLength?: number; // 4-8 digits, default: 6
    otpExpiry?: number; // Minutes, default: 10
    tokenExpiry?: number; // Hours, default: 24
  };
  multiTenant?: {
    enabled: boolean;
    tenantIdExtractor?: 'header' | 'subdomain' | 'custom';
    defaultTenantId?: string;
  };
  rateLimit?: {
    login?: { limit: number; ttl: number };
    register?: { limit: number; ttl: number };
    passwordReset?: { limit: number; ttl: number };
  };
  guards?: {
    applyJwtGuardGlobally?: boolean;
  };
}

Email Configuration

The package supports email sending for password resets and email verification. You can use a custom sender function or one of the supported providers.

Dual Email Verification Support

The package supports three verification methods:

  1. OTP (One-Time Password) - Default method, 6-digit code sent via email
  2. Magic Link - Clickable link with UUID token
  3. Both - Users can choose between OTP or magic link

Configuration Example:

HorizonAuthModule.forRoot({
  // ... other config
  email: {
    from: '[email protected]',
    provider: 'resend',
    apiKey: process.env.RESEND_API_KEY,
  },
  emailVerification: {
    method: 'otp', // or 'magic-link' or 'both'
    otpLength: 6, // 4-8 digits
    otpExpiry: 10, // minutes
    tokenExpiry: 24, // hours (for magic link)
  },
  passwordReset: {
    method: 'otp', // or 'magic-link' or 'both'
    otpLength: 6,
    otpExpiry: 10,
    tokenExpiry: 24,
  },
});

Default Behavior: If no configuration is provided, OTP verification is used by default with 6-digit codes that expire in 10 minutes.

Security Features:

  • OTPs are generated using cryptographically secure crypto.randomInt()
  • 5 attempt limit per OTP (resets when new OTP is generated)
  • 60-second rate limit for resending verification emails
  • Automatic expiry tracking for both OTP and magic links

Provider Setup

Important: You must install the SDK for your chosen email provider:

# For Resend
npm install resend

# For SendGrid
npm install @sendgrid/mail

Using Resend

HorizonAuthModule.forRoot({
  // ... other config
  email: {
    from: '[email protected]',
    provider: 'resend',
    apiKey: process.env.RESEND_API_KEY,
  },
});

Using SendGrid

HorizonAuthModule.forRoot({
  // ... other config
  email: {
    from: '[email protected]',
    provider: 'sendgrid',
    apiKey: process.env.SENDGRID_API_KEY,
  },
});

Using Custom Sender

HorizonAuthModule.forRoot({
  // ... other config
  email: {
    from: '[email protected]',
    customSender: async (to: string, subject: string, html: string) => {
      // Your custom email sending logic
      await yourEmailService.send({ to, subject, html });
    },
  },
});

Development Mode

If no email configuration is provided, tokens will be logged to the console for development purposes.

Decorators

@Public()

Mark routes as publicly accessible (skip authentication)

@Public()
@Get('public')
publicRoute() {
  return { message: 'No auth required' };
}

@CurrentUser()

Inject authenticated user into controller

@Get('me')
getMe(@CurrentUser() user) {
  return user;
}

@Roles(...roles)

Require specific roles for access

@Roles('admin', 'moderator')
@Get('admin')
adminRoute() {
  return { message: 'Admin only' };
}

@CurrentTenant()

Get current tenant ID

@Get('tenant-data')
getTenantData(@CurrentTenant() tenantId: string) {
  return { tenantId };
}

Multi-Tenant Configuration

HorizonAuthModule.forRoot({
  // ... other config
  multiTenant: {
    enabled: true,
    tenantIdExtractor: 'header', // or 'subdomain' or 'custom'
    defaultTenantId: 'default',
  },
});

Dev SSO Mode

For local development with multiple microservices:

# docker-compose.dev-sso.yml
version: '3.8'
services:
  auth-service:
    build: .
    ports:
      - '3000:3000'
    environment:
      COOKIE_DOMAIN: '.localhost'
      REDIS_HOST: redis
    depends_on:
      - postgres
      - redis

All *.localhost:3000 apps will share the same authentication session.

Cross-Language Token Verification

C# Example

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

var jwks = await httpClient.GetStringAsync("http://auth-service/.well-known/jwks.json");
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(jwks);

var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
    IssuerSigningKeys = keys.Keys,
    ValidateIssuer = true,
    ValidIssuer = "horizon-auth",
    ValidateAudience = true,
    ValidAudience = "horizon-api"
};

var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);

Python Example

import jwt
import requests

# Fetch JWKS
jwks_url = "http://auth-service/.well-known/jwks.json"
jwks = requests.get(jwks_url).json()

# Verify token
token = "your-jwt-token"
decoded = jwt.decode(
    token,
    jwks,
    algorithms=["RS256"],
    issuer="horizon-auth",
    audience="horizon-api"
)

Security Best Practices

  1. Always use HTTPS in production
  2. Store RSA keys securely (environment variables, secrets manager)
  3. Enable rate limiting to prevent brute force attacks
  4. Monitor security events (failed logins, token reuse)
  5. Rotate JWT keys periodically
  6. Use strong Redis passwords in production

Environment Variables

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/horizon_auth

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password

# JWT Keys (for production - use multiline env vars)
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----"
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIB...\n-----END PUBLIC KEY-----"

# Application
NODE_ENV=production
PORT=3000

# Optional: SSO Mode
AUTH_SERVICE_URL=https://auth.ofeklabs.dev

Production Deployment

Portfolio SSO Architecture (Recommended)

Perfect for portfolios, demo sites, or related projects.

Benefits:

  • One login for all projects
  • Consistent user experience
  • No duplicate auth code
  • Single point of maintenance
  • Professional appearance

Setup:

  1. Deploy auth service to auth.yourdomain.com
  2. Install package in each project
  3. Configure SSO mode with AUTH_SERVICE_URL
  4. Deploy projects to subdomains

See PRODUCTION-DEPLOYMENT.md for complete guide.

Deploy to Render

1. Deploy Auth Service

Create a new Web Service on Render:

Environment Variables:

DATABASE_URL=<your-postgres-url>
REDIS_HOST=<your-redis-host>
REDIS_PORT=6379
REDIS_PASSWORD=<your-redis-password>
JWT_PRIVATE_KEY=<paste-private-key-with-\n>
JWT_PUBLIC_KEY=<paste-public-key-with-\n>
NODE_ENV=production
COOKIE_DOMAIN=.ofeklabs.dev
COOKIE_SECURE=true

Build Command: npm install && npm run build

Start Command: npm run start:prod

2. Deploy Your Apps (SSO Mode)

For each app (tasks, CRM, analytics):

// app.module.ts
HorizonAuthModule.forRoot({
  ssoMode: true,
  authServiceUrl: process.env.AUTH_SERVICE_URL || 'https://auth.ofeklabs.dev',
  jwt: {
    publicKey: process.env.JWT_PUBLIC_KEY,
  },
  cookie: {
    domain: process.env.COOKIE_DOMAIN || '.ofeklabs.dev',
    secure: process.env.NODE_ENV === 'production',
  },
})

Environment Variables:

AUTH_SERVICE_URL=https://auth.ofeklabs.dev
JWT_PUBLIC_KEY=<paste-public-key-with-\n>
COOKIE_DOMAIN=.ofeklabs.dev
NODE_ENV=production

3. Configure Custom Domains

On Render, add custom domains:

  • Auth service: auth.ofeklabs.dev
  • Tasks app: tasks.ofeklabs.dev
  • CRM app: crm.ofeklabs.dev

All apps will share authentication via cookies! 🎉

Deploy to AWS/Docker

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

EXPOSE 3000

CMD ["node", "dist/main"]
# Build and run
docker build -t horizon-auth .
docker run -p 3000:3000 \
  -e DATABASE_URL="postgresql://..." \
  -e REDIS_HOST="redis" \
  -e JWT_PRIVATE_KEY="$(cat certs/private.pem)" \
  -e JWT_PUBLIC_KEY="$(cat certs/public.pem)" \
  horizon-auth

Environment Variable Best Practices

  1. Never commit keys to Git
  2. Use secrets manager (AWS Secrets Manager, Render Secrets)
  3. Rotate keys periodically
  4. Use different keys for dev/staging/production
  5. Store keys as multiline strings with \n for newlines

Troubleshooting

"Invalid or expired access token"

  • Check that your JWT keys are correctly configured
  • Verify token hasn't expired (default 15 minutes)
  • Ensure token isn't blacklisted

"Redis connection error"

  • Verify Redis is running: docker ps
  • Check Redis connection settings
  • Test connection: redis-cli ping

"Token reuse detected"

  • This is a security feature - someone tried to reuse a revoked refresh token
  • All user tokens have been revoked for security
  • User needs to login again

Migration from Existing Auth

See MIGRATION.md for guides on:

  • Migrating from bcrypt to Argon2id
  • Migrating from HS256 to RS256
  • Database schema migration

License

MIT

Support

  • GitHub Issues: https://github.com/OfekItzhaki/horizon-auth-platform/issues
  • Documentation: https://github.com/OfekItzhaki/horizon-auth-platform

Credits

Created by Ofek Itzhaki