Package Exports
- @ofeklabs/horizon-auth
- @ofeklabs/horizon-auth/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@ofeklabs/horizon-auth) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@ofeklabs/horizon-auth
Production-ready NestJS authentication module with 2026 security standards. One package, two modes: embedded auth or SSO - configured entirely through ENV variables.
๐ฏ Use Cases
1. Embedded Auth
Each application has its own isolated authentication. Perfect for standalone apps.
myapp.com (App + Auth)2. 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.comSame package, different ENV variables! No code changes needed.
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
๐ Enterprise Features (v0.3.0)
- ๐ Two-Factor Authentication: TOTP-based 2FA with QR codes and backup codes
- ๐ฑ Device Management: Track, list, and revoke user devices
- ๐ Push Notifications: Register and manage push tokens (FCM/APNS)
- ๐ค Account Management: Deactivate, reactivate, and delete accounts
- ๐ Social Login: Google and Facebook OAuth integration
๐ Quick Start
Installation
npm install @ofeklabs/horizon-auth @prisma/client ioredis passport passport-jwt
npm install -D prismaGenerate RSA Keys
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pemConfiguration Options
Option 1: Environment Variables Only (Recommended โญ)
Zero code configuration - just set ENV variables and you're done!
Embedded Mode (Standalone App)
# .env
AUTH_MODE=full
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
REDIS_HOST=localhost
REDIS_PORT=6379
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
# Optional: Enable features
ENABLE_2FA=true
ENABLE_DEVICE_MGMT=true
ENABLE_PUSH=true
ENABLE_ACCOUNT_MGMT=true
APP_NAME=MyApp// app.module.ts
import { HorizonAuthModule } from '@ofeklabs/horizon-auth';
@Module({
imports: [
HorizonAuthModule.forRoot(), // ๐ No config needed! Uses ENV variables
],
})
export class AppModule {}SSO Mode (Multiple Projects, One Auth)
Auth Service (auth.yourdomain.com):
AUTH_MODE=full
DATABASE_URL=postgresql://user:password@host:5432/auth_db
REDIS_HOST=redis.yourdomain.com
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
COOKIE_DOMAIN=.yourdomain.com
NODE_ENV=production
ENABLE_2FA=true
ENABLE_DEVICE_MGMT=true
APP_NAME=YourCompanyYour Projects (project1.yourdomain.com, project2.yourdomain.com):
AUTH_MODE=sso
AUTH_SERVICE_URL=https://auth.yourdomain.com
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
COOKIE_DOMAIN=.yourdomain.com
NODE_ENV=production// app.module.ts (same for all projects)
import { HorizonAuthModule } from '@ofeklabs/horizon-auth';
@Module({
imports: [
HorizonAuthModule.forRoot(), // ๐ Automatically uses SSO mode from ENV
],
})
export class AppModule {}That's it! Deploy your auth service once, then use the same package in all your projects with different ENV variables. No code changes needed!
๐ See ENV-CONFIGURATION.md for complete ENV reference.
๐ See DEPLOYMENT-EXAMPLES.md for real-world deployment scenarios.
Option 2: Code Configuration (Advanced)
If you prefer code-based configuration or need dynamic config:
Option 2: Code Configuration (Advanced)
If you prefer code-based configuration or need dynamic config:
Embedded Mode
// app.module.ts
import { Module } from '@nestjs/common';
import { HorizonAuthModule } from '@ofeklabs/horizon-auth';
@Module({
imports: [
HorizonAuthModule.forRoot({
database: {
url: process.env.DATABASE_URL,
},
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT) || 6379,
},
jwt: {
privateKey: process.env.JWT_PRIVATE_KEY,
publicKey: process.env.JWT_PUBLIC_KEY,
},
features: {
twoFactor: {
enabled: true,
issuer: 'YourApp',
},
deviceManagement: {
enabled: true,
},
},
}),
],
})
export class AppModule {}SSO Mode
// app.module.ts
import { HorizonAuthModule } from '@ofeklabs/horizon-auth';
@Module({
imports: [
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',
},
}),
],
})
export class AppModule {}Note: ENV variables are still loaded automatically. Code config overrides ENV values.
๐๏ธ Database Setup
# Start PostgreSQL and Redis (using Docker)
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:15
docker run -d -p 6379:6379 redis:7-alpine
# Run Prisma migrations
npx prisma migrate dev๐จ Use in Your Application
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
import {
Public,
CurrentUser,
JwtAuthGuard,
Roles,
} 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:
Core Authentication
POST /auth/register- Register new userPOST /auth/login- Login with email/passwordPOST /auth/refresh- Refresh access tokenPOST /auth/logout- Logout and revoke tokensGET /auth/profile- Get current user profilePOST /auth/password-reset/request- Request password resetPOST /auth/password-reset/complete- Complete password resetPOST /auth/verify-email- Verify email address
Enterprise Features (v0.3.0+)
Two-Factor Authentication:
POST /auth/2fa/enable- Enable 2FA and get QR codePOST /auth/2fa/verify- Verify 2FA setupPOST /auth/2fa/verify-login- Complete login with 2FAPOST /auth/2fa/disable- Disable 2FAPOST /auth/2fa/backup-codes/regenerate- Regenerate backup codes
Device Management:
GET /auth/devices- List user's active devicesPOST /auth/devices/:deviceId/revoke- Revoke specific device
Push Notifications:
POST /auth/push-tokens- Register push notification tokenDELETE /auth/push-tokens/:tokenId- Revoke push token
Account Management:
POST /auth/account/deactivate- Deactivate accountPOST /auth/account/reactivate- Reactivate accountDELETE /auth/account- Permanently delete account
Social Login:
POST /auth/social/google- Google OAuth callbackPOST /auth/social/facebook- Facebook OAuth callback
Cross-Language Support
GET /.well-known/jwks.json- Public keys for JWT verification
๐ฏ How SSO Mode Works
When you deploy in SSO mode, here's what happens:
Auth Service (auth.yourdomain.com):
- Runs in
AUTH_MODE=full - Has database and Redis
- Handles login, registration, 2FA, etc.
- Issues JWT tokens
- Sets cookies for
.yourdomain.com
- Runs in
Your Projects (project1.yourdomain.com, project2.yourdomain.com):
- Run in
AUTH_MODE=sso - No database or Redis needed
- Only verify JWT tokens using public key
- Read cookies from
.yourdomain.com - Users are automatically authenticated!
- Run in
User Experience:
- User visits
project1.yourdomain.com - Not logged in โ Your frontend redirects to
auth.yourdomain.com/login - User logs in at auth service
- Cookie set for
.yourdomain.com - Redirect back to
project1.yourdomain.com - User is now logged in!
- User visits
project2.yourdomain.comโ Already logged in! (same cookie)
- User visits
Important: This package is API-only. You need to build your own login/register UI that calls the auth endpoints.
๐ง Configuration Reference
๐ง Configuration Reference
Complete Configuration Interface
interface HorizonAuthConfig {
// Mode Selection
ssoMode?: boolean; // false = full auth service, true = token verification only
authServiceUrl?: string; // Required when ssoMode=true
// Database (required when ssoMode=false)
database?: {
url: string;
};
// Redis (required when ssoMode=false)
redis?: {
host: string;
port: number;
password?: string;
db?: number;
};
// JWT (always required)
jwt: {
privateKey?: string; // Required when ssoMode=false
publicKey: string; // Always required
accessTokenExpiry?: string; // Default: '15m'
refreshTokenExpiry?: string; // Default: '7d'
issuer?: string; // Default: 'horizon-auth'
audience?: string; // Default: 'horizon-api'
kid?: string; // Default: 'horizon-auth-key-1'
};
// Cookie Configuration
cookie?: {
domain?: string; // e.g., '.yourdomain.com' for SSO
secure?: boolean; // Default: true in production
sameSite?: 'strict' | 'lax' | 'none'; // Default: 'lax'
};
// Multi-Tenant Support
multiTenant?: {
enabled: boolean;
tenantIdExtractor?: 'header' | 'subdomain' | 'custom';
defaultTenantId?: string;
};
// Rate Limiting
rateLimit?: {
login?: { limit: number; ttl: number };
register?: { limit: number; ttl: number };
passwordReset?: { limit: number; ttl: number };
};
// Email Configuration
email?: {
provider: 'resend' | 'sendgrid' | 'custom';
apiKey?: string;
from?: string;
};
// Global Guards
guards?: {
applyJwtGuardGlobally?: boolean;
};
// Enterprise Features
features?: {
twoFactor?: {
enabled: boolean;
issuer?: string; // Shows in authenticator apps
};
deviceManagement?: {
enabled: boolean;
maxDevicesPerUser?: number;
};
pushNotifications?: {
enabled: boolean;
};
accountManagement?: {
enabled: boolean;
allowReactivation?: boolean;
};
socialLogin?: {
google?: {
clientId: string;
clientSecret: string;
callbackUrl: string;
};
facebook?: {
appId: string;
appSecret: string;
callbackUrl: string;
};
};
};
}๐ก Tip: Use ENV variables instead of code config for maximum flexibility!
๐ญ 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 };
}Enterprise Features Usage
Two-Factor Authentication
// Enable 2FA for a user
const setup = await fetch('/auth/2fa/enable', {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
});
const { secret, qrCode } = await setup.json();
// Display QR code to user (qrCode is a data URL)
// User scans with Google Authenticator or Authy
// Verify setup with code from authenticator
await fetch('/auth/2fa/verify', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ code: '123456' }),
});
// Login with 2FA
const loginResponse = await fetch('/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
if (loginResponse.requiresTwoFactor) {
// Prompt user for 2FA code
await fetch('/auth/2fa/verify-login', {
method: 'POST',
body: JSON.stringify({
userId: loginResponse.userId,
code: '123456', // or backup code
}),
});
}Device Management
// List user's devices (automatically tracked on login)
const devices = await fetch('/auth/devices', {
headers: { Authorization: `Bearer ${accessToken}` },
}).then(r => r.json());
// Revoke a specific device
await fetch(`/auth/devices/${deviceId}/revoke`, {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
});Push Notifications
// Register push token (requires device from login)
await fetch('/auth/push-tokens', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: fcmToken, // From Firebase Cloud Messaging
tokenType: 'FCM', // or 'APNS' for iOS
deviceId: deviceId, // From device list
}),
});Account Management
// Deactivate account
await fetch('/auth/account/deactivate', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
reason: 'Taking a break',
}),
});
// Reactivate account (no auth required)
await fetch('/auth/account/reactivate', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
// Permanently delete account
await fetch('/auth/account', {
method: 'DELETE',
headers: { Authorization: `Bearer ${accessToken}` },
});๐ Cross-Language Token Verification
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](./PRODUCTION-DEPLOYMENT.md) for complete guide.
### Deploy to Render
#### 1. Deploy Auth Service
Create a new Web Service on Render:
**Environment Variables**:
```env
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=trueBuild 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=production3. 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-authEnvironment Variable Best Practices
- Never commit keys to Git
- Use secrets manager (AWS Secrets Manager, Render Secrets)
- Rotate keys periodically
- Use different keys for dev/staging/production
- Store keys as multiline strings with
\nfor 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
"Device ID required for push tokens"
- Push tokens require a valid deviceId
- Devices are created automatically on login (not registration)
- Login first, then get device list, then register push token
"Feature disabled" error
- The feature you're trying to use is not enabled in configuration
- Enable it in
HorizonAuthModule.forRoot({ features: { ... } })
Testing Status
โ Fully Tested:
- User registration and login
- JWT token generation and validation
- Refresh token rotation
- 2FA setup (QR code generation)
- Account deactivation/reactivation
- Login protection for deactivated accounts
โ ๏ธ Requires Manual Testing:
- 2FA login flow (requires actual authenticator app)
- Device tracking (automatic on login)
- Push token registration (requires valid device)
- Social login (requires OAuth credentials from Google/Facebook)
- Backup codes usage during login
๐ Cross-Platform Support
Works with ANY backend language! Your auth service issues JWT tokens that can be verified by:
- โ NestJS (use package in SSO mode)
- โ .NET / C# (JWT Bearer Auth)
- โ Python (PyJWT)
- โ Go (golang-jwt)
- โ Java / Spring Boot (OAuth2 Resource Server)
- โ PHP (Firebase JWT)
- โ Any language with JWT support!
๐ See CROSS-PLATFORM-INTEGRATION.md for complete integration guides with code examples.
๐ Cross-Language Token Verification
Your other services (in any language) can verify tokens using the JWKS endpoint:
C# Example
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
var jwks = await httpClient.GetStringAsync("https://auth.yourdomain.com/.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 = "https://auth.yourdomain.com/.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"
)๐ Troubleshooting
"AUTH_SERVICE_URL is required when AUTH_MODE=sso"
Set AUTH_SERVICE_URL in your ENV file when using SSO mode.
"JWT_PRIVATE_KEY is required when AUTH_MODE=full"
Make sure you've set JWT_PRIVATE_KEY when running in full mode (auth service).
"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 in Redis
"Redis connection error"
- Verify Redis is running:
docker psor check your cloud Redis service - Check Redis connection settings in ENV
- 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
"Device ID required for push tokens"
- Push tokens require a valid deviceId
- Devices are created automatically on login (not registration)
- Login first, then get device list, then register push token
"Feature disabled" error
- The feature you're trying to use is not enabled
- Enable it via ENV:
ENABLE_2FA=true,ENABLE_DEVICE_MGMT=true, etc.
Cookies not working across subdomains
- Make sure
COOKIE_DOMAINstarts with a dot:.yourdomain.com - Verify
COOKIE_SECURE=truein production (requires HTTPS) - Check that all apps use the same cookie domain
๐ Documentation
- ENV-CONFIGURATION.md - Complete environment variable reference
- DEPLOYMENT-EXAMPLES.md - Real-world deployment scenarios
- CROSS-PLATFORM-INTEGRATION.md - Integration with .NET, Python, Go, Java, PHP
- GitHub Repository - Source code and issues
๐งช Testing Status
โ Fully Tested:
- User registration and login
- JWT token generation and validation
- Refresh token rotation
- 2FA setup (QR code generation)
- Account deactivation/reactivation
- Login protection for deactivated accounts
- SSO mode token verification
โ ๏ธ Requires Manual Testing:
- 2FA login flow (requires actual authenticator app)
- Device tracking (automatic on login)
- Push token registration (requires valid device)
- Social login (requires OAuth credentials from Google/Facebook)
- Backup codes usage during login
๐ฆ What's New
v0.4.1 (Latest)
- ๐ Cross-platform integration guide - Complete examples for .NET, Python, Go, Java, PHP
- ๐ Enhanced README with cross-platform support section
- ๐ JWKS endpoint documentation for polyglot architectures
v0.4.0
- โจ ENV-based configuration - Zero code changes needed!
- โจ Flexible deployment - Same package for embedded and SSO modes
- ๐ Complete ENV variable documentation
- ๐ Real-world deployment examples
- ๐ง Improved configuration merging
v0.3.0
- โจ Two-Factor Authentication (TOTP)
- โจ Device Management
- โจ Push Notifications
- โจ Account Management
- โจ Social Login (Google, Facebook)
v0.2.1
- ๐ Fixed SSO mode JWT strategy
- ๐ง Dynamic module configuration
๐ License
MIT
๐ค Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
๐จโ๐ป Author
Created by Ofek Itzhaki
โญ If this package helps you, consider giving it a star on GitHub!