authrix
Version:
Lightweight, flexible authentication library for Node.js and TypeScript.
1,869 lines (1,599 loc) ⢠78.4 kB
Markdown
# Authrix
<div align="center">
<img src="./logo/logo.svg" alt="Authrix Logo" width="250" height="200">
</div>
> A production-ready, framework-agnostic authentication library for Node.js and TypeScript
[](https://www.npmjs.com/package/authrix)
[](https://opensource.org/licenses/MIT)
[](https://www.typescriptlang.org/)
[](https://jestjs.io/)
**Authrix** is a comprehensive authentication library designed for enterprise-grade applications. Built with TypeScript-first architecture, it provides complete authentication flows including JWT tokens, OAuth SSO, 2FA email verification, forgot password systems, and user profile management across any JavaScript framework or runtime environment.
## šļø Architecture Overview
Authrix follows a modular, adapter-based architecture that separates concerns and enables flexible integration:
```
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Authrix Core ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Authentication ā SSO/OAuth ā Password Recovery ā
ā Layer ā Layer ā Layer ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Framework Adapters ā
ā Next.js ā Express ā React ā Universal ā Custom ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Database Adapters ā
ā MongoDB ā PostgreSQL ā Custom ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Email Service Layer ā
ā Gmail ā SendGrid ā Resend ā SMTP ā Console ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
```
## ⨠Core Features
### š **Complete Authentication System**
- **JWT Token Management**: Secure token generation, verification, and refresh
- **Session Management**: Framework-agnostic session handling with HttpOnly cookies
- **Password Security**: bcryptjs hashing with configurable salt rounds and validation
- **Rate Limiting**: Built-in protection against brute force attacks
### š **SSO & OAuth Integration**
- **Multi-Provider Support**: Google, GitHub with extensible provider architecture
- **State Management**: Secure OAuth state verification and CSRF protection
- **User Provisioning**: Automatic user creation and profile syncing
- **Framework Integration**: Ready-to-use handlers for all supported frameworks
### š§ **2FA & Email Verification**
- **Multi-Factor Authentication**: Email-based verification codes
- **Email Templates**: Customizable email templates with multiple providers
- **Code Management**: Secure code generation, hashing, and expiration
- **Rate Limiting**: Configurable request throttling and abuse prevention
### š **Password Recovery System**
- **Secure Reset Flow**: Verification code-based password reset
- **Rate Limiting**: Configurable delays between reset requests
- **Password Validation**: Prevent password reuse and enforce strength requirements
- **Email Integration**: Seamless integration with email service providers
### š¤ **User Profile Management**
- **Extended Profiles**: Username, firstName, lastName fields with validation
- **Flexible Updates**: Partial profile updates with conflict resolution
- **Database Migration**: Backwards-compatible schema evolution
- **Username System**: Unique usernames with case-insensitive lookups
### š **Framework Agnostic Design**
- **Universal Core**: Framework-independent authentication logic
- **Adapter Pattern**: Pluggable integrations for any framework or database
- **Edge Runtime**: Compatible with modern edge computing environments
- **TypeScript First**: Complete type safety and developer experience
## š¦ Installation & Setup
### Package Installation
```bash
# npm
npm install authrix
# yarn
yarn add authrix
# pnpm
pnpm add authrix
# bun
bun add authrix
```
### Environment Configuration
Create your environment configuration:
```bash
# .env
JWT_SECRET=your-super-secure-jwt-secret-key-min-32-chars
DATABASE_URL=mongodb://localhost:27017/myapp
# or
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
# OAuth Configuration (optional)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
OAUTH_REDIRECT_URI=http://localhost:3000/api/auth/callback
# Email Configuration (optional)
EMAIL_PROVIDER=gmail # or sendgrid, resend, smtp
GMAIL_EMAIL=your-email@gmail.com
GMAIL_APP_PASSWORD=your-app-password
```
### Database Adapter Setup
Choose and configure your database adapter:
```typescript
// MongoDB Setup
import { mongoAdapter } from "authrix/adapters/mongo";
// PostgreSQL Setup
import { postgresqlAdapter, initializePostgreSQLTables } from "authrix/adapters/postgresql";
// Initialize PostgreSQL tables (run once)
await initializePostgreSQLTables();
// Supabase Setup
import { supabaseAdapter } from "authrix/adapters/supabase";
```
## š Quick Start Guide
### Basic Express.js Integration
```typescript
import express from "express";
import cookieParser from "cookie-parser";
import { initAuth, signup, signin, getCurrentUser, authMiddleware } from "authrix";
import { mongoAdapter } from "authrix/adapters/mongo";
const app = express();
// Initialize Authrix
initAuth({
jwtSecret: process.env.JWT_SECRET!,
db: mongoAdapter,
cookieName: "auth_token" // optional, defaults to "auth_token"
});
app.use(express.json());
app.use(cookieParser());
// Authentication Routes
app.post("/api/auth/signup", async (req, res) => {
try {
const { email, password, username, firstName, lastName } = req.body;
const user = await signup(email, password, res, {
username,
firstName,
lastName
});
res.status(201).json({
success: true,
user,
message: "Account created successfully"
});
} catch (error) {
res.status(400).json({
success: false,
error: { message: error.message }
});
}
});
app.post("/api/auth/signin", async (req, res) => {
try {
const { email, password } = req.body;
const user = await signin(email, password, res);
res.json({
success: true,
user,
message: "Signed in successfully"
});
} catch (error) {
res.status(401).json({
success: false,
error: { message: error.message }
});
}
});
app.get("/api/auth/me", async (req, res) => {
try {
const user = await getCurrentUser(req);
if (!user) {
return res.status(401).json({
success: false,
error: { message: "Not authenticated" }
});
}
res.json({ success: true, user });
} catch (error) {
res.status(500).json({
success: false,
error: { message: error.message }
});
}
});
// Protected routes
app.get("/api/user/profile", authMiddleware, (req, res) => {
res.json({
success: true,
user: req.user,
message: "Profile retrieved successfully"
});
});
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
```
## š Forgot Password & Recovery System
### Password Reset Configuration
```typescript
// config/forgot-password.ts
export const forgotPasswordConfig = {
// Rate limiting configuration
rateLimit: {
maxAttempts: 3,
windowMinutes: 15,
cooldownMinutes: 60
},
// Verification code settings
verificationCode: {
length: 6,
expirationMinutes: 15,
numericOnly: true
},
// Security settings
security: {
preventPasswordReuse: true,
requireStrongPassword: true,
hashRounds: 12
},
// Email template settings
emailTemplate: {
from: process.env.FROM_EMAIL!,
subject: 'Password Reset Code',
template: 'forgot-password'
}
};
```
### Complete Forgot Password Implementation
```typescript
// app/api/auth/forgot-password/route.ts
import { initiateForgotPassword } from "authrix";
export async function POST(request: Request) {
try {
const { email } = await request.json();
if (!email) {
return Response.json({
error: { message: 'Email is required' }
}, { status: 400 });
}
// Initiate forgot password process
await initiateForgotPassword(email, {
rateLimitDelay: 60, // 1 minute between requests
codeExpiration: 15 * 60, // 15 minutes code validity
emailConfig: {
from: process.env.FROM_EMAIL!,
subject: 'Password Reset Code',
template: 'forgot-password'
}
});
return Response.json({
message: "If an account with this email exists, a password reset code has been sent."
});
} catch (error) {
console.error('Forgot password error:', error);
// Return generic message for security
if (error.message.includes('rate limit')) {
return Response.json({
error: { message: 'Too many requests. Please try again later.' }
}, { status: 429 });
}
return Response.json({
error: { message: 'An error occurred. Please try again.' }
}, { status: 500 });
}
}
```
```typescript
// app/api/auth/reset-password/route.ts
import { resetPasswordWithCode } from "authrix";
export async function POST(request: Request) {
try {
const { email, code, newPassword } = await request.json();
if (!email || !code || !newPassword) {
return Response.json({
error: { message: 'Email, code, and new password are required' }
}, { status: 400 });
}
// Validate password strength
if (newPassword.length < 8) {
return Response.json({
error: { message: 'Password must be at least 8 characters long' }
}, { status: 400 });
}
// Reset password with verification code
const result = await resetPasswordWithCode(email, code, newPassword, {
preventReuse: true,
invalidateAllSessions: true
});
return Response.json({
message: "Password reset successfully",
user: {
id: result.user.id,
email: result.user.email,
username: result.user.username
}
});
} catch (error) {
console.error('Reset password error:', error);
if (error.message.includes('Invalid code')) {
return Response.json({
error: { message: 'Invalid or expired verification code' }
}, { status: 400 });
}
if (error.message.includes('User not found')) {
return Response.json({
error: { message: 'Invalid request' }
}, { status: 400 });
}
return Response.json({
error: { message: 'An error occurred. Please try again.' }
}, { status: 500 });
}
}
```
### Framework-Specific Implementations
```typescript
// Express.js Forgot Password Routes
import { nextForgotPassword, expressReset } from "authrix/frameworks";
// Initiate forgot password
app.post("/forgot-password", async (req, res) => {
try {
const { email } = req.body;
await initiateForgotPassword(email, {
rateLimitDelay: 60,
codeExpiration: 15 * 60,
emailConfig: {
from: process.env.FROM_EMAIL!,
subject: 'Reset Your Password',
template: 'password-reset'
}
});
res.json({
message: "Password reset instructions sent to your email"
});
} catch (error) {
if (error.message.includes('rate limit')) {
return res.status(429).json({
error: 'Too many requests. Please wait before trying again.'
});
}
res.status(500).json({ error: 'Failed to process request' });
}
});
// Reset password with code
app.post("/reset-password", expressReset);
```
### React Forgot Password Components
```typescript
// components/ForgotPasswordForm.tsx
import { useState } from 'react';
interface ForgotPasswordFormProps {
onSuccess?: () => void;
onError?: (error: string) => void;
}
export function ForgotPasswordForm({ onSuccess, onError }: ForgotPasswordFormProps) {
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const [sent, setSent] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/api/auth/forgot-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
const data = await response.json();
if (response.ok) {
setSent(true);
onSuccess?.();
} else {
onError?.(data.error?.message || 'Failed to send reset email');
}
} catch (error) {
onError?.('Network error. Please try again.');
} finally {
setLoading(false);
}
};
if (sent) {
return (
<div className="forgot-password-success">
<h3>Check Your Email</h3>
<p>If an account with email <strong>{email}</strong> exists, we've sent password reset instructions.</p>
<button onClick={() => setSent(false)}>Send Another Email</button>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="forgot-password-form">
<h3>Reset Your Password</h3>
<div className="form-group">
<label htmlFor="email">Email Address</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
placeholder="Enter your email address"
disabled={loading}
/>
</div>
<button type="submit" disabled={loading || !email}>
{loading ? 'Sending...' : 'Send Reset Instructions'}
</button>
</form>
);
}
// components/ResetPasswordForm.tsx
import { useState } from 'react';
interface ResetPasswordFormProps {
email: string;
onSuccess?: (user: any) => void;
onError?: (error: string) => void;
}
export function ResetPasswordForm({ email, onSuccess, onError }: ResetPasswordFormProps) {
const [code, setCode] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (newPassword !== confirmPassword) {
onError?.('Passwords do not match');
return;
}
if (newPassword.length < 8) {
onError?.('Password must be at least 8 characters long');
return;
}
setLoading(true);
try {
const response = await fetch('/api/auth/reset-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, code, newPassword })
});
const data = await response.json();
if (response.ok) {
onSuccess?.(data.user);
} else {
onError?.(data.error?.message || 'Failed to reset password');
}
} catch (error) {
onError?.('Network error. Please try again.');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="reset-password-form">
<h3>Reset Your Password</h3>
<div className="form-group">
<label htmlFor="code">Verification Code</label>
<input
id="code"
type="text"
value={code}
onChange={(e) => setCode(e.target.value)}
required
placeholder="Enter the 6-digit code"
maxLength={6}
disabled={loading}
/>
</div>
<div className="form-group">
<label htmlFor="newPassword">New Password</label>
<input
id="newPassword"
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
placeholder="Enter new password"
minLength={8}
disabled={loading}
/>
</div>
<div className="form-group">
<label htmlFor="confirmPassword">Confirm Password</label>
<input
id="confirmPassword"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
placeholder="Confirm new password"
disabled={loading}
/>
</div>
<button type="submit" disabled={loading || !code || !newPassword || !confirmPassword}>
{loading ? 'Resetting...' : 'Reset Password'}
</button>
</form>
);
}
```
## š§ Next.js App Router Integration
```typescript
// app/api/auth/signup/route.ts
import { initAuth, signupNextApp } from "authrix/nextjs";
import { postgresqlAdapter } from "authrix/adapters/postgresql";
initAuth({
jwtSecret: process.env.JWT_SECRET!,
db: postgresqlAdapter,
});
export async function POST(request: Request) {
try {
const { email, password, username, firstName, lastName } = await request.json();
const result = await signupNextApp(email, password, {
username,
firstName,
lastName
});
const response = Response.json({
success: true,
user: result.user
}, { status: 201 });
// Set HTTP-only cookie
response.headers.set('Set-Cookie',
`auth_token=${result.token}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=604800`
);
return response;
} catch (error) {
return Response.json({
success: false,
error: { message: error.message }
}, { status: 400 });
}
}
```
```typescript
// app/api/auth/signin/route.ts
import { signinNextApp } from "authrix/nextjs";
export async function POST(request: Request) {
try {
const { email, password } = await request.json();
const result = await signinNextApp(email, password);
const response = Response.json({
success: true,
user: result.user
});
response.headers.set('Set-Cookie',
`auth_token=${result.token}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=604800`
);
return response;
} catch (error) {
return Response.json({
error: { message: error.message }
}, { status: 401 });
}
}
```
```typescript
// app/api/auth/me/route.ts
import { getCurrentUserNextApp } from "authrix/nextjs";
export async function GET() {
try {
const user = await getCurrentUserNextApp();
if (!user) {
return Response.json({
success: false,
error: { message: "Not authenticated" }
}, { status: 401 });
}
return Response.json({ success: true, user });
} catch (error) {
return Response.json({
success: false,
error: { message: error.message }
}, { status: 500 });
}
}
```
```typescript
// app/api/auth/logout/route.ts
import { logoutNextApp } from "authrix/nextjs";
export async function POST() {
try {
await logoutNextApp();
const response = Response.json({
success: true,
message: "Logged out successfully"
});
// Clear the auth cookie
response.headers.set('Set-Cookie',
`auth_token=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0`
);
return response;
} catch (error) {
return Response.json({
success: false,
error: { message: error.message }
}, { status: 500 });
}
}
```
### Protected Page Components
```typescript
// app/dashboard/page.tsx
import { getCurrentUserNextApp } from "authrix/nextjs";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const user = await getCurrentUserNextApp();
if (!user) {
redirect('/login');
}
return (
<div className="dashboard">
<h1>Welcome to your Dashboard</h1>
<div className="user-info">
<h2>Hello, {user.firstName || user.username || user.email}!</h2>
<p>Email: {user.email}</p>
{user.username && <p>Username: {user.username}</p>}
</div>
<div className="dashboard-content">
{/* Dashboard content here */}
</div>
</div>
);
}
```
```typescript
// components/AuthButton.tsx
"use client";
import { useState, useEffect } from 'react';
interface User {
id: string;
email: string;
username?: string;
}
export function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
checkAuthStatus();
}, []);
const checkAuthStatus = async () => {
try {
const authenticated = await isAuthenticatedReact();
setIsAuthenticated(authenticated);
if (authenticated) {
const currentUser = await getCurrentUserReact();
setUser(currentUser);
}
} catch (error) {
console.error('Auth check failed:', error);
setIsAuthenticated(false);
setUser(null);
}
};
const signup = async (
email: string,
password: string,
options?: {
username?: string;
firstName?: string;
lastName?: string;
}
) => {
setLoading(true);
try {
const result = await signupReact(email, password, options);
setUser(result.user);
setIsAuthenticated(true);
return result;
} catch (error) {
throw error;
} finally {
setLoading(false);
}
};
const signin = async (email: string, password: string) => {
setLoading(true);
try {
const result = await signinReact(email, password);
setUser(result.user);
setIsAuthenticated(true);
return result;
} catch (error) {
throw error;
} finally {
setLoading(false);
}
};
const logout = async () => {
setLoading(true);
try {
await logoutReact();
setUser(null);
setIsAuthenticated(false);
} catch (error) {
console.error('Logout failed:', error);
} finally {
setLoading(false);
}
};
return {
user,
loading,
isAuthenticated,
signup,
signin,
logout,
checkAuthStatus
};
}
```
### React Authentication Components
```typescript
// components/LoginForm.tsx
import { useState } from 'react';
import { useAuth } from '../hooks/useAuth';
interface LoginFormProps {
onSuccess?: () => void;
onError?: (error: string) => void;
}
export function LoginForm({ onSuccess, onError }: LoginFormProps) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { signin, loading } = useAuth();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await signin(email, password);
onSuccess?.();
} catch (error) {
onError?.(error instanceof Error ? error.message : 'Login failed');
}
};
return (
<form onSubmit={handleSubmit} className="login-form">
<h2>Sign In</h2>
<div className="form-group">
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
disabled={loading}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={loading}
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Signing In...' : 'Sign In'}
</button>
<div className="form-links">
<a href="/forgot-password">Forgot Password?</a>
<a href="/signup">Don't have an account? Sign Up</a>
</div>
</form>
);
}
// components/SignupForm.tsx
import { useState } from 'react';
import { useAuth } from '../hooks/useAuth';
interface SignupFormProps {
onSuccess?: () => void;
onError?: (error: string) => void;
}
export function SignupForm({ onSuccess, onError }: SignupFormProps) {
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: '',
username: '',
firstName: '',
lastName: ''
});
const { signup, loading } = useAuth();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData(prev => ({
...prev,
[e.target.name]: e.target.value
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (formData.password !== formData.confirmPassword) {
onError?.('Passwords do not match');
return;
}
try {
await signup(formData.email, formData.password, {
username: formData.username || undefined,
firstName: formData.firstName || undefined,
lastName: formData.lastName || undefined
});
onSuccess?.();
} catch (error) {
onError?.(error instanceof Error ? error.message : 'Signup failed');
}
};
return (
<form onSubmit={handleSubmit} className="signup-form">
<h2>Create Account</h2>
<div className="form-row">
<div className="form-group">
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
value={formData.firstName}
onChange={handleChange}
disabled={loading}
/>
</div>
<div className="form-group">
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
value={formData.lastName}
onChange={handleChange}
disabled={loading}
/>
</div>
</div>
<div className="form-group">
<label htmlFor="username">Username (optional)</label>
<input
id="username"
name="username"
type="text"
value={formData.username}
onChange={handleChange}
disabled={loading}
/>
</div>
<div className="form-group">
<label htmlFor="email">Email *</label>
<input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
required
disabled={loading}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password *</label>
<input
id="password"
name="password"
type="password"
value={formData.password}
onChange={handleChange}
required
minLength={8}
disabled={loading}
/>
</div>
<div className="form-group">
<label htmlFor="confirmPassword">Confirm Password *</label>
<input
id="confirmPassword"
name="confirmPassword"
type="password"
value={formData.confirmPassword}
onChange={handleChange}
required
disabled={loading}
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Creating Account...' : 'Create Account'}
</button>
<div className="form-links">
<a href="/login">Already have an account? Sign In</a>
</div>
</form>
);
}
// components/ProtectedRoute.tsx
import { useAuth } from '../hooks/useAuth';
import { useEffect, useState } from 'react';
interface ProtectedRouteProps {
children: React.ReactNode;
fallback?: React.ReactNode;
redirectTo?: string;
}
export function ProtectedRoute({
children,
fallback = <div>Loading...</div>,
redirectTo = '/login'
}: ProtectedRouteProps) {
const { isAuthenticated, loading } = useAuth();
const [isChecking, setIsChecking] = useState(true);
useEffect(() => {
// Give auth check a moment to complete
const timer = setTimeout(() => {
setIsChecking(false);
}, 100);
return () => clearTimeout(timer);
}, []);
if (loading || isChecking) {
return <>{fallback}</>;
}
if (!isAuthenticated) {
window.location.href = redirectTo;
return <>{fallback}</>;
}
return <>{children}</>;
}
export function AuthButton() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
checkAuth();
}, []);
const checkAuth = async () => {
try {
const response = await fetch('/api/auth/me');
const data = await response.json();
if (data.success) {
setUser(data.user);
} else {
setUser(null);
}
} catch (error) {
setUser(null);
} finally {
setLoading(false);
}
};
const handleLogout = async () => {
try {
await fetch('/api/auth/logout', { method: 'POST' });
setUser(null);
window.location.href = '/';
} catch (error) {
console.error('Logout error:', error);
}
};
if (loading) {
return <div>Loading...</div>;
};
if (user) {
return (
<div className="auth-button">
<span>Welcome, {user.firstName || user.username || user.email}</span>
<button onClick={handleLogout}>Sign Out</button>
</div>
);
};
return (
<div className="auth-button">
<a href="/login">Sign In</a>
<a href="/signup">Sign Up</a>
</div>
);
}
```
## šļø Database Adapters & Configuration
### MongoDB Adapter
```typescript
// config/database.ts
import { mongoAdapter } from "authrix/adapters/mongo";
export const dbConfig = mongoAdapter({
connectionString: process.env.MONGODB_URI!,
options: {
useNewUrlParser: true,
useUnifiedTopology: true,
maxPoolSize: 10,
bufferMaxEntries: 0,
},
collections: {
users: 'users',
sessions: 'sessions',
passwordResets: 'password_resets',
ssoProviders: 'sso_providers'
}
});
// Initialize database
import { initAuth } from "authrix";
initAuth({
jwtSecret: process.env.JWT_SECRET!,
db: dbConfig,
session: {
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'lax'
}
});
```
### PostgreSQL Adapter
```typescript
// config/database.ts
import { postgresqlAdapter } from "authrix/adapters/postgresql";
export const dbConfig = postgresqlAdapter({
host: process.env.DB_HOST!,
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME!,
username: process.env.DB_USERNAME!,
password: process.env.DB_PASSWORD!,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
tables: {
users: 'users',
sessions: 'sessions',
passwordResets: 'password_resets',
ssoProviders: 'sso_providers'
},
pool: {
max: 20,
min: 5,
acquire: 30000,
idle: 10000
}
});
// Database migrations
export const runMigrations = async () => {
try {
await dbConfig.migrate();
console.log('Database migrations completed successfully');
} catch (error) {
console.error('Migration failed:', error);
process.exit(1);
}
};
```
### Supabase Adapter
```typescript
// config/database.ts
import { supabaseAdapter } from "authrix/adapters/supabase";
export const dbConfig = supabaseAdapter({
url: process.env.SUPABASE_URL!,
anonKey: process.env.SUPABASE_ANON_KEY!,
serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY!,
options: {
auth: {
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false
},
global: {
headers: {
'X-Client-Info': 'authrix-supabase-adapter'
}
}
},
tables: {
users: 'auth_users',
sessions: 'auth_sessions',
passwordResets: 'auth_password_resets',
ssoProviders: 'auth_sso_providers'
}
});
// RLS (Row Level Security) policies
export const setupRLS = async () => {
const { error } = await dbConfig.rpc('setup_authrix_rls');
if (error) {
console.error('RLS setup failed:', error);
} else {
console.log('RLS policies configured successfully');
}
};
```
### Custom Database Adapter
```typescript
// adapters/custom.ts
import { DatabaseAdapter, User, Session } from "authrix/types";
export class CustomDatabaseAdapter implements DatabaseAdapter {
private connection: any;
constructor(connectionOptions: any) {
this.connection = connectionOptions;
}
async connect(): Promise<void> {
// Initialize your database connection
}
async disconnect(): Promise<void> {
// Close database connection
}
async createUser(userData: {
email: string;
passwordHash: string;
username?: string;
firstName?: string;
lastName?: string;
}): Promise<User> {
// Implement user creation logic
const user = await this.connection.users.create(userData);
return {
id: user.id,
email: user.email,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
isActive: true,
createdAt: user.createdAt,
updatedAt: user.updatedAt
};
}
async getUserByEmail(email: string): Promise<User | null> {
// Implement user retrieval by email
const user = await this.connection.users.findByEmail(email);
return user ? this.mapToUser(user) : null;
}
async getUserById(id: string): Promise<User | null> {
// Implement user retrieval by ID
const user = await this.connection.users.findById(id);
return user ? this.mapToUser(user) : null;
}
async updateUser(id: string, updates: Partial<User>): Promise<User> {
// Implement user update logic
const user = await this.connection.users.update(id, updates);
return this.mapToUser(user);
}
async deleteUser(id: string): Promise<void> {
// Implement user deletion logic
await this.connection.users.delete(id);
}
// Session management methods
async createSession(session: Session): Promise<Session> {
return await this.connection.sessions.create(session);
}
async getSession(sessionId: string): Promise<Session | null> {
return await this.connection.sessions.findById(sessionId);
}
async deleteSession(sessionId: string): Promise<void> {
await this.connection.sessions.delete(sessionId);
}
// Password reset methods
async createPasswordReset(data: {
email: string;
code: string;
expiresAt: Date;
}): Promise<void> {
await this.connection.passwordResets.create(data);
}
async getPasswordReset(email: string, code: string): Promise<any> {
return await this.connection.passwordResets.findByEmailAndCode(email, code);
}
async deletePasswordReset(email: string): Promise<void> {
await this.connection.passwordResets.deleteByEmail(email);
}
// SSO provider methods
async createSSOUser(data: {
email: string;
provider: string;
providerId: string;
username?: string;
firstName?: string;
lastName?: string;
}): Promise<User> {
const user = await this.connection.users.create({
...data,
isActive: true,
isEmailVerified: true
});
await this.connection.ssoProviders.create({
userId: user.id,
provider: data.provider,
providerId: data.providerId
});
return this.mapToUser(user);
}
async getSSOUser(provider: string, providerId: string): Promise<User | null> {
const ssoRecord = await this.connection.ssoProviders.findByProvider(provider, providerId);
if (!ssoRecord) return null;
const user = await this.connection.users.findById(ssoRecord.userId);
return user ? this.mapToUser(user) : null;
}
private mapToUser(userData: any): User {
return {
id: userData.id,
email: userData.email,
username: userData.username,
firstName: userData.firstName,
lastName: userData.lastName,
isActive: userData.isActive,
isEmailVerified: userData.isEmailVerified,
role: userData.role,
createdAt: userData.createdAt,
updatedAt: userData.updatedAt
};
}
}
// Usage
import { initAuth } from "authrix";
import { CustomDatabaseAdapter } from "./adapters/custom";
const customDb = new CustomDatabaseAdapter({
// Your connection options
});
initAuth({
jwtSecret: process.env.JWT_SECRET!,
db: customDb
});
```
## š”ļø Security Features & Best Practices
### Password Security
```typescript
// config/security.ts
export const securityConfig = {
password: {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSymbols: true,
preventCommonPasswords: true,
preventPasswordReuse: 5, // Last 5 passwords
hashRounds: 12
},
session: {
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
renewalThreshold: 24 * 60 * 60 * 1000, // 24 hours
maxConcurrentSessions: 3,
invalidateOnPasswordChange: true
},
rateLimit: {
loginAttempts: {
maxAttempts: 5,
windowMinutes: 15,
lockoutMinutes: 30
},
passwordReset: {
maxAttempts: 3,
windowMinutes: 60,
cooldownMinutes: 15
},
signupAttempts: {
maxAttempts: 3,
windowMinutes: 60
}
},
jwt: {
issuer: process.env.JWT_ISSUER || 'authrix',
audience: process.env.JWT_AUDIENCE || 'authrix-app',
algorithm: 'HS256',
expiresIn: '7d'
}
};
```
### Input Validation & Sanitization
```typescript
// utils/validation.ts
import { z } from 'zod';
export const userValidationSchemas = {
signup: z.object({
email: z.string()
.email('Invalid email format')
.max(255, 'Email too long')
.trim()
.toLowerCase(),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.max(128, 'Password too long')
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[a-z]/, 'Password must contain lowercase letter')
.regex(/[0-9]/, 'Password must contain number')
.regex(/[^A-Za-z0-9]/, 'Password must contain special character'),
username: z.string()
.min(3, 'Username must be at least 3 characters')
.max(30, 'Username too long')
.regex(/^[a-zA-Z0-9_-]+$/, 'Username can only contain letters, numbers, hyphens, and underscores')
.optional(),
firstName: z.string()
.min(1, 'First name is required')
.max(50, 'First name too long')
.regex(/^[a-zA-Z\s'-]+$/, 'First name contains invalid characters')
.optional(),
lastName: z.string()
.min(1, 'Last name is required')
.max(50, 'Last name too long')
.regex(/^[a-zA-Z\s'-]+$/, 'Last name contains invalid characters')
.optional()
}),
signin: z.object({
email: z.string().email('Invalid email format').trim().toLowerCase(),
password: z.string().min(1, 'Password is required')
}),
forgotPassword: z.object({
email: z.string().email('Invalid email format').trim().toLowerCase()
}),
resetPassword: z.object({
email: z.string().email('Invalid email format').trim().toLowerCase(),
code: z.string().length(6, 'Code must be 6 digits').regex(/^[0-9]+$/, 'Code must be numeric'),
newPassword: z.string()
.min(8, 'Password must be at least 8 characters')
.max(128, 'Password too long')
})
};
// Validation middleware
export const validateInput = (schema: z.ZodSchema) => {
return (req: any, res: any, next: any) => {
try {
const validated = schema.parse(req.body);
req.body = validated;
next();
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: {
message: 'Validation failed',
details: error.errors.map(err => ({
field: err.path.join('.'),
message: err.message
}))
}
});
}
next(error);
}
};
};
// Usage in Express routes
import { validateInput, userValidationSchemas } from './utils/validation';
app.post('/api/auth/signup',
validateInput(userValidationSchemas.signup),
async (req, res) => {
// Request body is now validated and sanitized
const { email, password, username, firstName, lastName } = req.body;
// ... signup logic
}
);
```
### CSRF Protection
```typescript
// middleware/csrf.ts
import { createHash, randomBytes } from 'crypto';
export const csrfProtection = {
generateToken: (): string => {
return randomBytes(32).toString('hex');
},
verifyToken: (token: string, sessionToken: string): boolean => {
if (!token || !sessionToken) return false;
return token === sessionToken;
},
middleware: (req: any, res: any, next: any) => {
if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
return next();
}
const token = req.headers['x-csrf-token'] || req.body._csrf;
const sessionToken = req.session?.csrfToken;
if (!csrfProtection.verifyToken(token, sessionToken)) {
return res.status(403).json({
error: { message: 'Invalid CSRF token' }
});
}
next();
}
};
// Next.js CSRF implementation
// app/api/csrf/route.ts
export async function GET() {
const token = csrfProtection.generateToken();
const response = Response.json({ csrfToken: token });
response.headers.set('Set-Cookie',
`csrf_token=${token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600`
);
return response;
}
```
```
## š React SPA Integration
```typescript
// hooks/useAuth.tsx
import { useState, useEffect } from 'react';
import {
signupReact,
signinReact,
logoutReact,
getCurrentUserReact,
isAuthenticatedReact
} from "authrix/react";
interface User {
id: string;
email: string;
username?: string;
firstName?: string;
lastName?: string;
}
export function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
checkAuthStatus();
}, []);
const checkAuthStatus = async () => {
try {
const currentUser = await getCurrentUserReact();
setUser(currentUser);
setIsAuthenticated(!!currentUser);
} catch (error) {
console.error('Auth check failed:', error);
setUser(null);
setIsAuthenticated(false);
} finally {
setLoading(false);
}
};
const signup = async (userData: {
email: string;
password: string;
username?: string;
firstName?: string;
lastName?: string;
}) => {
try {
const result = await signupReact(
userData.email,
userData.password,
'/api/auth/signup'
);
setUser(result.user);
setIsAuthenticated(true);
return result;
} catch (error) {
throw error;
}
};
const signin = async (email: string, password: string) => {
try {
const result = await signinReact(email, password, '/api/auth/signin');
setUser(result.user);
setIsAuthenticated(true);
return result;
} catch (error) {
throw error;
}
};
const logout = async () => {
try {
await logoutReact('/api/auth/logout');
setUser(null);
setIsAuthenticated(false);
} catch (error) {
console.error('Logout failed:', error);
}
};
return {
user,
loading,
isAuthenticated,
signup,
signin,
logout,
checkAuthStatus
};
}
```
```typescript
// components/AuthForm.tsx
import React, { useState } from 'react';
import { useAuth } from '../hooks/useAuth';
export function AuthForm() {
const { signup, signin, isAuthenticated, user } = useAuth();
const [isSignup, setIsSignup] = useState(false);
const [formData, setFormData] = useState({
email: '',
password: '',
username: '',
firstName: '',
lastName: ''
});
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
try {
if (isSignup) {
await signup(formData);
} else {
await signin(formData.email, formData.password);
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Authentication failed');
} finally {
setLoading(false);
}
};
if (isAuthenticated) {
return (
<div className="auth-success">
<h2>Welcome, {user?.firstName || user?.email}!</h2>
<p>You are successfully authenticated.</p>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="auth-form">
<h2>{isSignup ? 'Sign Up' : 'Sign In'}</h2>
{error && <div className="error">{error}</div>}
<input
type="email"
placeholder="Email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
required
/>
<input
type="password"
placeholder="Password"
value={formData.password}
onChange={(e) => setFormData({...formData, password: e.target.value})}
required
/>
{isSignup && (
<>
<input
type="text"
placeholder="Username (optional)"
value={formData.username}
onChange={(e) => setFormData({...formData, username: e.target.value})}
/>
<input
type="text"
placeholder="First Name (optional)"
value={formData.firstName}
onChange={(e) => setFormData({...formData, firstName: e.target.value})}
/>
<input
type="text"
placeholder="Last Name (optional)"
value={formData.lastName}
onChange={(e) => setFormData({...formData, lastName: e.target.value})}
/>
</>
)}
<button type="submit" disabled={loading}>
{loading ? 'Loading...' : (isSignup ? 'Sign Up' : 'Sign In')}
</button>
<button
type="button"
onClick={() => setIsSignup(!isSignup)}
className="link-button"
>
{isSignup ? 'Already have an account? Sign In' : 'Need an account? Sign Up'}
</button>
</form>
);
}
```
## š Complete SSO & OAuth Implementation
### OAuth Provider Configuration
```typescript
// config/oauth.ts
export const oAuthConfig = {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
redirectUri: `${process.env.BASE_URL}/api/auth/google/callback`,
scope: 'openid profile email'
},
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
redirectUri: `${process.env.BASE_URL}/api/auth/github/callback`,
scope: 'read:user user:email'
}
};
```
### SSO Authentication Flow
```typescript
// app/api/auth/[provider]/route.ts
import { getGoogleOAuthURL, getGitHubOAuthURL } from "authrix/oauth";
import { generateSSOState } from "authrix";
export async function GET(
request: Request,
{ params }: { params: { provider: string } }
) {
try {
const { provider } = params;
const url = new URL(request.url);
const redirectUrl = url.searchParams.get('redirect') || '/dashboard';
// Generate secure state parameter
const state = generateSSOState({
provider,
redirectUrl,
timestamp: Date.now()
});
let authUrl: string;
switch (provider) {
case 'google':
authUrl = getGoogleOAuthURL(state);
break;
case 'github':
authUrl = getGitHubOAuthURL(state);
break;
default:
return Response.json({
error: { message: 'Unsupported provider' }
}, { status: 400 });
}
return Response.redirect(authUrl);
} catch (error) {
return Response.json({