Authentication Examples

Comprehensive authentication patterns and examples for the Asterisms JS SDK Backend.

Overview

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:

  • A proper database (PostgreSQL, MySQL, etc.) for user data
  • Redis or similar for session management
  • The SDK Backend's authorization service for token validation
  • Proper persistence adapters for client-side storage

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 Authentication

Token Validation

// 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;
  }
}

SvelteKit Authentication Hook

// 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;
};

Login Implementation

SvelteKit Login Route

// 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 });
  }
};

Express.js Login Route

// 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;

Registration Implementation

User Registration

// 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 });
  }
};

Permission-Based Access Control

Permission Middleware

// 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
  };
}

Role-Based Access Control

// 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
  };
}

Protected Route Example

// 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 });
  }
};

Advanced Authentication Patterns

JWT Token Refresh

// 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 });
  }
};

Multi-Factor Authentication

// 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 });
  }
};

Session Management

Session Storage

// 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}`);
    }
  }
}

Session Middleware

// 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;
}

OAuth Integration

OAuth Login Flow

// 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');
  }
};

OAuth Callback Handler

// 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');
  }
};

Security Best Practices

Rate Limiting

// 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'
});

Password Security

// 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
    };
  }
}

Testing Authentication

Unit Tests

// 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');
    });
  });
});

Integration Tests

// 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');
  });
});

Next Steps