Comprehensive authentication patterns and examples for the Asterisms JS SDK Backend.
Important Note: The examples in this document use pseudo-code for data storage operations. The SDK Backend storage service is specifically for file storage, not for general key-value data storage. In a real implementation, you should use:
The examples below demonstrate the authentication patterns and flows, but storage operations like storage.list('users') should be replaced with actual database operations in your implementation.
This guide provides detailed examples of authentication implementation using the Asterisms JS SDK Backend. It covers various authentication scenarios, from basic token validation to advanced permission-based access control.
// Basic token validation
import { getAsterismsBackendSDK } from '@asterisms/sdk-backend';
const sdk = getAsterismsBackendSDK(props);
await sdk.boot();
const auth = sdk.authorization();
// Validate a token
async function validateUserToken(token: string) {
try {
const user = await auth.validateToken(token);
console.log('Authenticated user:', user);
return user;
} catch (error) {
console.error('Token validation failed:', error);
return null;
}
}// src/hooks.server.ts
import { redirect } from '@sveltejs/kit';
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
const sdk = event.locals.sdk;
if (!sdk) {
throw new Error('SDK not available');
}
// Skip authentication for public routes
const publicRoutes = ['/login', '/register', '/api/public'];
const isPublicRoute = publicRoutes.some((route) => event.url.pathname.startsWith(route));
if (!isPublicRoute) {
// Check for authentication token
const authToken =
event.cookies.get('auth_token') ||
event.request.headers.get('Authorization')?.replace('Bearer ', '');
if (!authToken) {
if (event.url.pathname.startsWith('/api/')) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
throw redirect(302, '/login');
}
try {
const auth = sdk.authorization();
const user = await auth.validateToken(authToken);
event.locals.user = user;
} catch (error) {
console.error('Authentication failed:', error);
// Clear invalid token
event.cookies.delete('auth_token');
if (event.url.pathname.startsWith('/api/')) {
return new Response(JSON.stringify({ error: 'Invalid token' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
throw redirect(302, '/login');
}
}
const response = await resolve(event);
return response;
};// src/routes/api/auth/login/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request, locals, cookies }) => {
try {
const { email, password } = await request.json();
if (!email || !password) {
return json({ error: 'Email and password are required' }, { status: 400 });
}
const sdk = locals.sdk;
if (!sdk) {
return json({ error: 'SDK not available' }, { status: 503 });
}
const auth = sdk.authorization();
// Authenticate user
const result = await auth.login({ email, password });
if (result.success) {
// Set authentication cookie
cookies.set('auth_token', result.token, {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7 // 7 days
});
return json({
success: true,
user: result.user,
token: result.token
});
} else {
return json({ error: 'Invalid credentials' }, { status: 401 });
}
} catch (error) {
console.error('Login error:', error);
return json({ error: 'Authentication failed' }, { status: 500 });
}
};// routes/auth.ts
import express from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
const router = express.Router();
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email and password are required' });
}
const sdk = req.sdk;
const storage = sdk.storage();
// Find user by email
const users = await storage.list('users');
const user = users.find((u) => u.email === email);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Verify password
const isValidPassword = await bcrypt.compare(password, user.passwordHash);
if (!isValidPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Use SDK authorization to create token
const auth = sdk.authorization();
const token = await auth.createToken(user);
res.json({
success: true,
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role
},
token
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Authentication failed' });
}
});
export default router;// src/routes/api/auth/register/+server.ts
import { json } from '@sveltejs/kit';
import bcrypt from 'bcrypt';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request, locals }) => {
try {
const { email, password, name } = await request.json();
// Validate input
if (!email || !password || !name) {
return json({ error: 'Email, password, and name are required' }, { status: 400 });
}
if (password.length < 8) {
return json({ error: 'Password must be at least 8 characters' }, { status: 400 });
}
const sdk = locals.sdk;
if (!sdk) {
return json({ error: 'SDK not available' }, { status: 503 });
}
const storage = sdk.storage();
// Check if user already exists
const existingUsers = await storage.list('users');
const existingUser = existingUsers.find((u) => u.email === email);
if (existingUser) {
return json({ error: 'User already exists' }, { status: 409 });
}
// Hash password
const saltRounds = 12;
const passwordHash = await bcrypt.hash(password, saltRounds);
// Create user
const newUser = await storage.create('users', {
email,
name,
passwordHash,
role: 'user',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
});
// Create authentication token
const auth = sdk.authorization();
const token = await auth.createToken(newUser);
return json(
{
success: true,
user: {
id: newUser.id,
email: newUser.email,
name: newUser.name,
role: newUser.role
},
token
},
{ status: 201 }
);
} catch (error) {
console.error('Registration error:', error);
return json({ error: 'Registration failed' }, { status: 500 });
}
};// src/lib/middleware/permissions.ts
import { json } from '@sveltejs/kit';
import type { RequestEvent } from '@sveltejs/kit';
export function createPermissionMiddleware(requiredPermissions: string[]) {
return async (event: RequestEvent) => {
const sdk = event.locals.sdk;
const user = event.locals.user;
if (!sdk || !user) {
return json({ error: 'Unauthorized' }, { status: 401 });
}
const auth = sdk.authorization();
// Check each required permission
for (const permission of requiredPermissions) {
const hasPermission = await auth.hasPermission(permission);
if (!hasPermission) {
return json(
{
error: `Missing permission: ${permission}`
},
{ status: 403 }
);
}
}
return null; // Permission granted
};
}// src/lib/middleware/rbac.ts
import { json } from '@sveltejs/kit';
import type { RequestEvent } from '@sveltejs/kit';
export function createRoleMiddleware(allowedRoles: string[]) {
return async (event: RequestEvent) => {
const user = event.locals.user;
if (!user) {
return json({ error: 'Unauthorized' }, { status: 401 });
}
if (!allowedRoles.includes(user.role)) {
return json(
{
error: `Access denied. Required role: ${allowedRoles.join(' or ')}`
},
{ status: 403 }
);
}
return null; // Access granted
};
}// src/routes/admin/users/+server.ts
import { json } from '@sveltejs/kit';
import { createPermissionMiddleware } from '$lib/middleware/permissions';
import { createRoleMiddleware } from '$lib/middleware/rbac';
import type { RequestHandler } from './$types';
const requireAdminPermissions = createPermissionMiddleware([
'admin.users.read',
'admin.system.access'
]);
const requireAdminRole = createRoleMiddleware(['admin', 'super_admin']);
export const GET: RequestHandler = async (event) => {
// Check role
const roleCheck = await requireAdminRole(event);
if (roleCheck) return roleCheck;
// Check permissions
const permissionCheck = await requireAdminPermissions(event);
if (permissionCheck) return permissionCheck;
// Main route logic
const sdk = event.locals.sdk;
const storage = sdk.storage();
try {
const users = await storage.list('users');
return json({ users });
} catch (error) {
console.error('Failed to fetch users:', error);
return json({ error: 'Failed to fetch users' }, { status: 500 });
}
};// src/routes/api/auth/refresh/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request, locals, cookies }) => {
try {
const { refreshToken } = await request.json();
if (!refreshToken) {
return json({ error: 'Refresh token required' }, { status: 400 });
}
const sdk = locals.sdk;
const auth = sdk.authorization();
// Verify refresh token and get new access token
const result = await auth.refreshToken(refreshToken);
if (result.success) {
// Update authentication cookie
cookies.set('auth_token', result.accessToken, {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7 // 7 days
});
return json({
success: true,
accessToken: result.accessToken,
refreshToken: result.refreshToken
});
} else {
return json({ error: 'Invalid refresh token' }, { status: 401 });
}
} catch (error) {
console.error('Token refresh error:', error);
return json({ error: 'Token refresh failed' }, { status: 500 });
}
};// src/routes/api/auth/mfa/verify/+server.ts
import { json } from '@sveltejs/kit';
import speakeasy from 'speakeasy';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request, locals }) => {
try {
const { email, password, mfaCode } = await request.json();
if (!email || !password || !mfaCode) {
return json({ error: 'Email, password, and MFA code are required' }, { status: 400 });
}
const sdk = locals.sdk;
const storage = sdk.storage();
// Find user
const users = await storage.list('users');
const user = users.find((u) => u.email === email);
if (!user) {
return json({ error: 'Invalid credentials' }, { status: 401 });
}
// Verify password
const isValidPassword = await bcrypt.compare(password, user.passwordHash);
if (!isValidPassword) {
return json({ error: 'Invalid credentials' }, { status: 401 });
}
// Verify MFA code
const isValidMFA = speakeasy.totp.verify({
secret: user.mfaSecret,
encoding: 'base32',
token: mfaCode
});
if (!isValidMFA) {
return json({ error: 'Invalid MFA code' }, { status: 401 });
}
// Create authentication token
const auth = sdk.authorization();
const token = await auth.createToken(user);
return json({
success: true,
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role
},
token
});
} catch (error) {
console.error('MFA verification error:', error);
return json({ error: 'Authentication failed' }, { status: 500 });
}
};// src/lib/session.ts
import type { AsterismsBackendSDK } from '@asterisms/sdk-backend';
export class SessionManager {
constructor(private sdk: AsterismsBackendSDK) {}
async createSession(userId: string): Promise<string> {
const storage = this.sdk.storage();
const sessionId = crypto.randomUUID();
const session = {
id: sessionId,
userId,
createdAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days
active: true
};
await storage.set(`session:${sessionId}`, session);
return sessionId;
}
async getSession(sessionId: string): Promise<any> {
const storage = this.sdk.storage();
const session = await storage.get(`session:${sessionId}`);
if (!session) {
return null;
}
// Check if session is expired
if (new Date(session.expiresAt) < new Date()) {
await this.destroySession(sessionId);
return null;
}
return session;
}
async destroySession(sessionId: string): Promise<void> {
const storage = this.sdk.storage();
await storage.delete(`session:${sessionId}`);
}
async destroyAllUserSessions(userId: string): Promise<void> {
const storage = this.sdk.storage();
const sessions = await storage.list('sessions');
const userSessions = sessions.filter((s) => s.userId === userId);
for (const session of userSessions) {
await storage.delete(`session:${session.id}`);
}
}
}// src/lib/middleware/session.ts
import { SessionManager } from '$lib/session';
import type { RequestEvent } from '@sveltejs/kit';
export async function validateSession(event: RequestEvent): Promise<boolean> {
const sessionId = event.cookies.get('session_id');
if (!sessionId) {
return false;
}
const sessionManager = new SessionManager(event.locals.sdk);
const session = await sessionManager.getSession(sessionId);
if (!session) {
event.cookies.delete('session_id');
return false;
}
// Get user from session
const storage = event.locals.sdk.storage();
const user = await storage.get('users', session.userId);
if (!user) {
await sessionManager.destroySession(sessionId);
event.cookies.delete('session_id');
return false;
}
event.locals.user = user;
event.locals.session = session;
return true;
}// src/routes/api/auth/oauth/[provider]/+server.ts
import { redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ params, url, locals }) => {
const provider = params.provider;
const sdk = locals.sdk;
const auth = sdk.authorization();
try {
const authUrl = await auth.getOAuthAuthorizationUrl(provider, {
redirectUri: `${url.origin}/api/auth/oauth/${provider}/callback`,
scope: 'email profile'
});
throw redirect(302, authUrl);
} catch (error) {
console.error('OAuth initiation error:', error);
throw redirect(302, '/login?error=oauth_init_failed');
}
};// src/routes/api/auth/oauth/[provider]/callback/+server.ts
import { redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ params, url, locals, cookies }) => {
const provider = params.provider;
const code = url.searchParams.get('code');
const error = url.searchParams.get('error');
if (error || !code) {
throw redirect(302, '/login?error=oauth_callback_failed');
}
const sdk = locals.sdk;
const auth = sdk.authorization();
try {
// Exchange code for tokens
const tokenResult = await auth.exchangeOAuthCode(provider, code);
if (!tokenResult.success) {
throw redirect(302, '/login?error=oauth_token_exchange_failed');
}
// Get user info from provider
const userInfo = await auth.getOAuthUserInfo(provider, tokenResult.accessToken);
// Find or create user
const storage = sdk.storage();
const users = await storage.list('users');
let user = users.find((u) => u.email === userInfo.email);
if (!user) {
user = await storage.create('users', {
email: userInfo.email,
name: userInfo.name,
role: 'user',
oauthProvider: provider,
oauthId: userInfo.id,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
});
}
// Create authentication token
const token = await auth.createToken(user);
// Set authentication cookie
cookies.set('auth_token', token, {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7 // 7 days
});
throw redirect(302, '/dashboard');
} catch (error) {
console.error('OAuth callback error:', error);
throw redirect(302, '/login?error=oauth_callback_failed');
}
};// src/lib/middleware/rateLimiter.ts
import type { RequestEvent } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';
interface RateLimitConfig {
windowMs: number;
max: number;
message?: string;
}
export function createRateLimiter(config: RateLimitConfig) {
const requests = new Map<string, number[]>();
return async (event: RequestEvent) => {
const clientId = event.getClientAddress();
const now = Date.now();
const windowStart = now - config.windowMs;
// Get existing requests for this client
const clientRequests = requests.get(clientId) || [];
// Filter out old requests
const recentRequests = clientRequests.filter((time) => time > windowStart);
// Check if limit exceeded
if (recentRequests.length >= config.max) {
return json(
{
error: config.message || 'Rate limit exceeded'
},
{ status: 429 }
);
}
// Add current request
recentRequests.push(now);
requests.set(clientId, recentRequests);
return null; // Allow request
};
}
// Usage
const loginRateLimiter = createRateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 login attempts
message: 'Too many login attempts, please try again later'
});// src/lib/security/password.ts
import bcrypt from 'bcrypt';
export class PasswordSecurity {
private static readonly SALT_ROUNDS = 12;
private static readonly MIN_LENGTH = 8;
static async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, this.SALT_ROUNDS);
}
static async verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
static validatePassword(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < this.MIN_LENGTH) {
errors.push(`Password must be at least ${this.MIN_LENGTH} characters long`);
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain at least one number');
}
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
errors.push('Password must contain at least one special character');
}
return {
valid: errors.length === 0,
errors
};
}
}// src/lib/auth.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { createMockSDK } from '../test/utils/mock-sdk';
import { PasswordSecurity } from './security/password';
describe('Authentication', () => {
let mockSDK: ReturnType<typeof createMockSDK>;
beforeEach(() => {
mockSDK = createMockSDK();
});
describe('Password Security', () => {
it('should hash passwords correctly', async () => {
const password = 'TestPassword123!';
const hash = await PasswordSecurity.hashPassword(password);
expect(hash).toBeDefined();
expect(hash).not.toBe(password);
expect(hash.length).toBeGreaterThan(50);
});
it('should verify passwords correctly', async () => {
const password = 'TestPassword123!';
const hash = await PasswordSecurity.hashPassword(password);
const isValid = await PasswordSecurity.verifyPassword(password, hash);
expect(isValid).toBe(true);
const isInvalid = await PasswordSecurity.verifyPassword('WrongPassword', hash);
expect(isInvalid).toBe(false);
});
it('should validate password strength', () => {
const weakPassword = 'weak';
const strongPassword = 'StrongPassword123!';
const weakResult = PasswordSecurity.validatePassword(weakPassword);
expect(weakResult.valid).toBe(false);
expect(weakResult.errors.length).toBeGreaterThan(0);
const strongResult = PasswordSecurity.validatePassword(strongPassword);
expect(strongResult.valid).toBe(true);
expect(strongResult.errors).toHaveLength(0);
});
});
describe('Token Validation', () => {
it('should validate valid tokens', async () => {
const mockUser = { id: '123', email: 'test@example.com', name: 'Test User' };
mockSDK.authorization().validateToken.mockResolvedValue(mockUser);
const auth = mockSDK.authorization();
const result = await auth.validateToken('valid-token');
expect(result).toEqual(mockUser);
});
it('should reject invalid tokens', async () => {
mockSDK.authorization().validateToken.mockRejectedValue(new Error('Invalid token'));
const auth = mockSDK.authorization();
await expect(auth.validateToken('invalid-token')).rejects.toThrow('Invalid token');
});
});
});// src/routes/api/auth/login/+server.test.ts
import { describe, it, expect } from 'vitest';
import { POST } from './+server';
import { createMockSDK } from '../../../../test/utils/mock-sdk';
describe('Login API', () => {
it('should login with valid credentials', async () => {
const mockSDK = createMockSDK();
const mockUser = { id: '123', email: 'test@example.com', name: 'Test User' };
mockSDK.authorization().login.mockResolvedValue({
success: true,
user: mockUser,
token: 'valid-token'
});
const mockEvent = {
request: {
json: async () => ({ email: 'test@example.com', password: 'password' })
},
locals: { sdk: mockSDK },
cookies: { set: vi.fn() }
};
const response = await POST(mockEvent as any);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(data.user).toEqual(mockUser);
});
it('should reject invalid credentials', async () => {
const mockSDK = createMockSDK();
mockSDK.authorization().login.mockResolvedValue({
success: false,
error: 'Invalid credentials'
});
const mockEvent = {
request: {
json: async () => ({ email: 'test@example.com', password: 'wrong-password' })
},
locals: { sdk: mockSDK },
cookies: { set: vi.fn() }
};
const response = await POST(mockEvent as any);
const data = await response.json();
expect(response.status).toBe(401);
expect(data.error).toBe('Invalid credentials');
});
});