Authentication & Authorization

Learn how to implement user authentication and authorization with the Asterisms JS SDK Backend.

Overview

The authorization service provides user authentication, permission checking, and role-based access control for your Asterisms applications.

Basic Usage

Getting the Authorization Service

const auth = sdk.authorization();

Get Current User

try {
  const user = await auth.getCurrentUser();
  if (user) {
    console.log('User:', user.name, user.email);
  } else {
    console.log('No user authenticated');
  }
} catch (error) {
  console.error('Authentication error:', error);
}

Check Permissions

const canRead = await auth.hasPermission('resource.read');
const canWrite = await auth.hasPermission('resource.write');
const canDelete = await auth.hasPermission('resource.delete');

if (canRead) {
  // User can read the resource
}

Authentication Patterns

SvelteKit Authentication

1. Layout Load Function

// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  const sdk = locals.sdk;

  if (!sdk) {
    return { user: null };
  }

  try {
    const auth = sdk.authorization();
    const user = await auth.getCurrentUser();

    return {
      user: user
        ? {
            id: user.id,
            name: user.name,
            email: user.email,
            roles: user.roles
          }
        : null
    };
  } catch (error) {
    console.error('Auth error in layout:', error);
    return { user: null };
  }
};

2. Protected Page

// src/routes/admin/+page.server.ts
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ locals }) => {
  const sdk = locals.sdk;

  if (!sdk) {
    throw redirect(302, '/login');
  }

  try {
    const auth = sdk.authorization();
    const user = await auth.getCurrentUser();

    if (!user) {
      throw redirect(302, '/login');
    }

    // Check admin permission
    const isAdmin = await auth.hasPermission('admin.access');
    if (!isAdmin) {
      throw redirect(302, '/unauthorized');
    }

    return {
      user: {
        id: user.id,
        name: user.name,
        email: user.email
      }
    };
  } catch (error) {
    console.error('Admin page auth error:', error);
    throw redirect(302, '/login');
  }
};

3. Authentication Component

<!-- src/lib/components/AuthGuard.svelte -->
<script lang="ts">
    import { page } from '$app/stores';
    import { goto } from '$app/navigation';
    import { onMount } from 'svelte';

    export let requiredPermission: string | null = null;
    export let requireAuth = true;

    $: user = $page.data.user;
    $: isAuthenticated = !!user;

    let hasPermission = false;

    onMount(async () => {
        if (requireAuth && !isAuthenticated) {
            goto('/login');
            return;
        }

        if (requiredPermission && isAuthenticated) {
            try {
                const response = await fetch('/api/auth/check-permission', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ permission: requiredPermission })
                });

                const result = await response.json();
                hasPermission = result.hasPermission;

                if (!hasPermission) {
                    goto('/unauthorized');
                }
            } catch (error) {
                console.error('Permission check error:', error);
                goto('/unauthorized');
            }
        } else {
            hasPermission = true;
        }
    });
</script>

{#if isAuthenticated && hasPermission}
    <slot />
{:else if requireAuth && !isAuthenticated}
    <div class="auth-required">
        <p>Authentication required</p>
        <a href="/login">Login</a>
    </div>
{:else if requiredPermission && !hasPermission}
    <div class="permission-required">
        <p>Insufficient permissions</p>
        <p>Required: {requiredPermission}</p>
    </div>
{/if}

API Route Protection

1. Permission Check API

// src/routes/api/auth/check-permission/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async ({ request, locals }) => {
  try {
    const sdk = locals.sdk;
    if (!sdk) {
      return json({ hasPermission: false }, { status: 503 });
    }

    const { permission } = await request.json();

    if (!permission) {
      return json({ hasPermission: false }, { status: 400 });
    }

    const auth = sdk.authorization();
    const hasPermission = await auth.hasPermission(permission);

    return json({ hasPermission });
  } catch (error) {
    console.error('Permission check error:', error);
    return json({ hasPermission: false }, { status: 500 });
  }
};

2. Protected API Route

// src/routes/api/admin/users/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ locals }) => {
  try {
    const sdk = locals.sdk;
    if (!sdk) {
      return json({ error: 'SDK not available' }, { status: 503 });
    }

    const auth = sdk.authorization();

    // Check authentication
    const user = await auth.getCurrentUser();
    if (!user) {
      return json({ error: 'Authentication required' }, { status: 401 });
    }

    // Check admin permission
    const isAdmin = await auth.hasPermission('admin.users.read');
    if (!isAdmin) {
      return json({ error: 'Insufficient permissions' }, { status: 403 });
    }

    // Fetch users (example)
    const users = await getUserList(); // Your implementation

    return json({ users });
  } catch (error) {
    console.error('Admin users API error:', error);
    return json({ error: 'Internal server error' }, { status: 500 });
  }
};

Middleware Integration

Express Middleware

import { createAuthorizerMiddleware } from '@asterisms/sdk-backend';

const app = express();
const sdk = getAsterismsBackendSDK(props);

// Create authorization middleware
const authMiddleware = createAuthorizerMiddleware(sdk.authorization());

// Use middleware
app.use('/api/protected', authMiddleware);

// Protected routes
app.get('/api/protected/data', (req, res) => {
  // User is authenticated here
  const user = req.user; // Added by middleware
  res.json({ user });
});

Custom Permission Middleware

function requirePermission(permission: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      const sdk = req.locals?.sdk;
      if (!sdk) {
        return res.status(503).json({ error: 'SDK not available' });
      }

      const auth = sdk.authorization();
      const hasPermission = await auth.hasPermission(permission);

      if (!hasPermission) {
        return res.status(403).json({ error: 'Insufficient permissions' });
      }

      next();
    } catch (error) {
      console.error('Permission middleware error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  };
}

// Usage
app.get('/api/admin/settings', requirePermission('admin.settings.read'), (req, res) => {
  // User has admin.settings.read permission
  res.json({ settings: {} });
});

User Management

User Information

const auth = sdk.authorization();
const user = await auth.getCurrentUser();

if (user) {
  console.log('User ID:', user.id);
  console.log('Name:', user.name);
  console.log('Email:', user.email);
  console.log('Roles:', user.roles);
  console.log('Permissions:', user.permissions);
  console.log('Created:', user.createdAt);
  console.log('Last Login:', user.lastLoginAt);
}

Role-Based Access

async function checkUserRole(requiredRole: string): Promise<boolean> {
  try {
    const auth = sdk.authorization();
    const user = await auth.getCurrentUser();

    if (!user) {
      return false;
    }

    return user.roles.includes(requiredRole);
  } catch (error) {
    console.error('Role check error:', error);
    return false;
  }
}

// Usage
const isAdmin = await checkUserRole('admin');
const isModerator = await checkUserRole('moderator');

Permission System

Permission Hierarchy

// Permission examples
const permissions = [
  'resource.read', // Read access
  'resource.write', // Write access
  'resource.delete', // Delete access
  'admin.users.read', // Read users
  'admin.users.write', // Modify users
  'admin.system.config', // System configuration
  'workspace.create', // Create workspaces
  'workspace.manage' // Manage workspaces
];

Batch Permission Check

async function checkMultiplePermissions(permissions: string[]): Promise<Record<string, boolean>> {
  const auth = sdk.authorization();
  const results: Record<string, boolean> = {};

  for (const permission of permissions) {
    try {
      results[permission] = await auth.hasPermission(permission);
    } catch (error) {
      console.error(`Permission check failed for ${permission}:`, error);
      results[permission] = false;
    }
  }

  return results;
}

// Usage
const permissions = await checkMultiplePermissions([
  'resource.read',
  'resource.write',
  'admin.access'
]);

console.log('User permissions:', permissions);

Error Handling

Authentication Errors

import { AsterismsSDKError } from '@asterisms/sdk-backend';

try {
  const auth = sdk.authorization();
  const user = await auth.getCurrentUser();
} catch (error) {
  if (error instanceof AsterismsSDKError) {
    switch (error.code) {
      case 'AUTH_TOKEN_EXPIRED':
        // Handle token expiration
        break;
      case 'AUTH_TOKEN_INVALID':
        // Handle invalid token
        break;
      case 'AUTH_USER_NOT_FOUND':
        // Handle user not found
        break;
      default:
        console.error('Authentication error:', error.message);
    }
  } else {
    console.error('Unknown auth error:', error);
  }
}

Permission Errors

try {
  const hasPermission = await auth.hasPermission('admin.access');
} catch (error) {
  if (error instanceof AsterismsSDKError) {
    switch (error.code) {
      case 'PERMISSION_NOT_FOUND':
        // Permission doesn't exist
        break;
      case 'USER_NOT_AUTHENTICATED':
        // User not logged in
        break;
      default:
        console.error('Permission error:', error.message);
    }
  }
}

Testing Authentication

Unit Tests

import { describe, it, expect, beforeEach } from 'vitest';
import { getAsterismsBackendSDK } from '@asterisms/sdk-backend';

describe('Authentication Service', () => {
  let sdk: AsterismsBackendSDK;

  beforeEach(async () => {
    sdk = getAsterismsBackendSDK(testProps);
    await sdk.boot();
  });

  it('should return current user when authenticated', async () => {
    const auth = sdk.authorization();
    const user = await auth.getCurrentUser();

    expect(user).toBeDefined();
    expect(user.id).toBeDefined();
    expect(user.email).toBeDefined();
  });

  it('should check permissions correctly', async () => {
    const auth = sdk.authorization();
    const hasPermission = await auth.hasPermission('resource.read');

    expect(typeof hasPermission).toBe('boolean');
  });
});

Integration Tests

import { test, expect } from '@playwright/test';

test('protected page redirects when not authenticated', async ({ page }) => {
  await page.goto('/admin');
  await expect(page).toHaveURL('/login');
});

test('authenticated user can access protected page', async ({ page }) => {
  // Login first
  await page.goto('/login');
  await page.fill('[name="email"]', 'test@example.com');
  await page.fill('[name="password"]', 'password');
  await page.click('[type="submit"]');

  // Access protected page
  await page.goto('/admin');
  await expect(page).toHaveURL('/admin');
  await expect(page.locator('h1')).toContainText('Admin Panel');
});

Security Best Practices

1. Always Validate Permissions

// ❌ Bad: Trust client-side checks
if (user.roles.includes('admin')) {
  // Dangerous - client can modify this
}

// ✅ Good: Server-side validation
const isAdmin = await auth.hasPermission('admin.access');
if (isAdmin) {
  // Safe - validated on server
}

2. Use Specific Permissions

// ❌ Bad: Broad permissions
await auth.hasPermission('admin');

// ✅ Good: Specific permissions
await auth.hasPermission('admin.users.delete');

3. Handle Errors Gracefully

// ❌ Bad: Expose error details
catch (error) {
    res.status(500).json({ error: error.message });
}

// ✅ Good: Generic error messages
catch (error) {
    console.error('Auth error:', error);
    res.status(401).json({ error: 'Authentication required' });
}

4. Implement Rate Limiting

// Example rate limiting for auth endpoints
const rateLimit = require('express-rate-limit');

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // limit each IP to 5 requests per windowMs
  message: 'Too many authentication attempts'
});

app.use('/api/auth', authLimiter);

Next Steps