Skip to content

🔐 Authentication Architecture Guide

The ERP-Unlocked platform uses a layered authentication architecture that provides consistent security across all applications while maintaining flexibility for different use cases.

┌─────────────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Webapp │ │ CRM │ │ Marketing │ │
│ │ (Astro App) │ │ (Astro App) │ │ (Astro App) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Middleware Layer │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Enhanced Shared Middleware │ │
│ │ ┌─────────────────┐ ┌─────────────────────────────────┐ │ │
│ │ │ Auth Logic │ │ Security Features │ │ │
│ │ │ • Route Protection│ │ • CSP Headers │ │ │
│ │ │ • Admin Checking │ │ • Security Headers │ │ │
│ │ │ • Org Context │ │ • Rate Limiting │ │ │
│ │ └─────────────────┘ └─────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Core Auth Package │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Server │ │Organization │ │ Astro │ │
│ │ Utils │ │ Utils │ │ Utils │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Clerk Authentication │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Users │ │Organizations│ │ Sessions │ │
│ │ & Roles │ │ & Roles │ │ & Tokens │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

🎯 Available Utilities & When to Use Them

Section titled “🎯 Available Utilities & When to Use Them”

1. @repo/auth - Core Authentication Package

Section titled “1. @repo/auth - Core Authentication Package”

Purpose: Server-side authentication and organization management Safe For: ✅ Server-side code, API routes, middleware Not For: ❌ Client-side components, Astro pages

import {
authenticateRequest,
getUserFromSession,
isOrganizationAdmin,
getCurrentOrganization,
} from '@repo/auth';
// Server-side authentication
const auth = await authenticateRequest(request);
const user = await getUserFromSession(auth);
// Organization management
const isAdmin = await isOrganizationAdmin(auth, orgId);
const org = await getCurrentOrganization(auth, orgSlug);

2. @repo/auth/middleware - Enhanced Shared Middleware

Section titled “2. @repo/auth/middleware - Enhanced Shared Middleware”

Purpose: Configurable authentication middleware for Astro applications Safe For: ✅ Astro middleware, route protection Not For: ❌ API routes, server-side logic

import { createAuthMiddleware } from '@repo/auth/middleware';
const config = {
protectedRoutes: ['/dashboard', '/admin'],
publicRoutes: ['/', '/sign-in'],
adminRoutes: ['/admin'],
organizationRequiredRoutes: ['/billing'],
organizationOptionalRoutes: ['/dashboard'],
enableOrganizationContext: true,
signInPath: '/sign-in',
dashboardPath: '/dashboard',
onboardingPath: '/onboarding/organization',
};
export const onRequest = createAuthMiddleware(config);

3. @repo/auth/astro - Astro-Specific Utilities

Section titled “3. @repo/auth/astro - Astro-Specific Utilities”

Purpose: Authentication utilities for Astro pages, layouts, and components Safe For: ✅ Astro pages, layouts, components Not For: ❌ API routes, server-side logic

import { getUser, requireAdmin } from '@repo/auth/astro';
// In .astro files
const user = getUser(Astro);
const adminUser = requireAdmin(Astro); // Throws if not admin

Purpose: Base class for API routes with built-in authentication and authorization Safe For: ✅ API route implementations Not For: ❌ Non-API code, client-side logic

import { BaseAPIHandler } from '@/lib/api/base-handler';
class MyAPIHandler extends BaseAPIHandler {
async POST({ locals, request }: Parameters<APIRoute>[0]) {
return this.handleRequest(async () => {
const { userId } = await this.requireAuth(locals, request);
const { userId: adminUserId } = await this.requireAdmin(locals, request);
// Your API logic here
return APIResponse.success({ message: 'Success' });
});
}
}
  1. Middleware Layer: Route-level protection and user context setup
  2. API Layer: Server-side authentication enforcement via BaseAPIHandler
  3. Page Layer: UI-level checks via @repo/auth/astro (for display only)
  • System Admin: Global admin role ('admin' in user.publicMetadata.roles)
  • Organization Admin: Organization-specific admin role ('org:admin', 'owner', 'org:owner')
  • API Enforcement: All admin checks enforced server-side
  • UI Display: Admin UI elements controlled by @repo/auth/astro utilities
  • Required Routes: Must have organization context (e.g., /billing, /organization)
  • Optional Routes: Can work with or without organization context (e.g., /dashboard, /orders)
  • Admin Routes: Always require organization context for proper role checking

The platform now uses a single, enhanced middleware that supports:

  • Basic Apps: Simple protected/public/admin route handling
  • Advanced Apps: Organization context, complex route categorization
  • Flexible Configuration: Enable/disable features as needed

Applications use Astro’s built-in sequence() function to combine multiple middleware functions in a specified order:

  1. Auth Middleware: Route protection and user context
  2. Security Middleware: CSP headers, security headers, rate limiting
import { sequence } from 'astro:middleware';
// Use Astro's built-in sequence() function for proper middleware chaining
export const onRequest = sequence(authMiddleware, securityMiddleware);

Benefits of using sequence():

  • Type Safety: Built-in TypeScript support
  • Performance: Optimized execution order
  • Idiomatic: Follows Astro best practices
  • Maintainable: Clear separation of concerns
  • Testable: Each middleware can be tested independently
const config = {
protectedRoutes: ['/dashboard', '/orders', '/erp', '/admin', '/billing', '/organization'],
publicRoutes: ['/', '/sign-in', '/sign-up', '/waitlist', '/contact', '/demo'],
adminRoutes: ['/admin'],
organizationRequiredRoutes: ['/billing', '/organization'],
organizationOptionalRoutes: ['/dashboard', '/orders', '/erp'],
enableOrganizationContext: true, // Enable advanced org-aware features
// CSP configuration for Clerk and other services
cspConfig: {
clerkDomains: [
'https://notable-marlin-37.accounts.dev',
'https://notable-marlin-37.clerk.accounts.dev',
'https://rapid-marten-5.accounts.dev',
'https://rapid-marten-5.clerk.accounts.dev',
'https://*.clerk.accounts.dev', // Wildcard for any Clerk instance
'https://*.clerk.com',
],
stripeDomains: ['https://api.stripe.com', 'https://*.js.stripe.com', 'https://js.stripe.com'],
sentryDomains: [
'https://*.sentry.io',
'https://js.sentry-cdn.com',
'https://browser.sentry-cdn.com',
],
googleDomains: [
'https://maps.googleapis.com',
'https://fonts.googleapis.com',
'https://fonts.gstatic.com',
],
},
};
const config = {
protectedRoutes: ['/dashboard', '/companies', '/contacts', '/tasks'],
publicRoutes: ['/', '/sign-in', '/sign-up'],
adminRoutes: [], // No admin routes yet
enableOrganizationContext: false, // Simple auth only
// CSP configuration for Clerk and other services
cspConfig: {
clerkDomains: [
'https://notable-marlin-37.accounts.dev',
'https://notable-marlin-37.clerk.accounts.dev',
'https://*.clerk.accounts.dev', // Wildcard for any Clerk instance
'https://*.clerk.com',
],
stripeDomains: ['https://api.stripe.com', 'https://*.js.stripe.com', 'https://js.stripe.com'],
sentryDomains: [
'https://*.sentry.io',
'https://js.sentry-cdn.com',
'https://browser.sentry-cdn.com',
],
googleDomains: [
'https://maps.googleapis.com',
'https://fonts.googleapis.com',
'https://fonts.gstatic.com',
],
},
};

The enhanced shared middleware provides comprehensive CSP configuration for security and service integration:

cspConfig: {
clerkDomains?: string[]; // Clerk authentication domains
stripeDomains?: string[]; // Stripe payment domains
sentryDomains?: string[]; // Sentry error tracking domains
googleDomains?: string[]; // Google services domains
additionalConnectSrc?: string[]; // Additional connect-src domains
additionalScriptSrc?: string[]; // Additional script-src domains
additionalFrameSrc?: string[]; // Additional frame-src domains
}

If no CSP configuration is provided, the middleware uses sensible defaults:

  • Clerk: https://*.clerk.accounts.dev, https://*.clerk.com
  • Stripe: https://api.stripe.com, https://*.js.stripe.com
  • Sentry: https://*.sentry.io, https://js.sentry-cdn.com
  • Google: https://maps.googleapis.com, https://fonts.googleapis.com

The middleware automatically generates comprehensive CSP headers using the generateCSPHeaders() function:

import { generateCSPHeaders } from '@repo/auth/middleware';
// Generate CSP headers with custom configuration
const cspHeader = generateCSPHeaders(authConfig.cspConfig);
response.headers.set('Content-Security-Policy', cspHeader);
// ❌ Direct metadata access (bypasses centralized logic)
const isAdmin = user.publicMetadata.roles?.includes('admin');
// ❌ Direct locals access (bypasses middleware)
const user = context.locals.user;
// ❌ Client-side admin enforcement (can be bypassed)
if (user.role === 'admin') {
/* sensitive operation */
}
// ✅ Use centralized utilities
const isAdmin = await isOrganizationAdmin(auth, orgId);
// ✅ Use middleware-provided context
const user = getUser(Astro); // From @repo/auth/astro
// ✅ Server-side enforcement
const { userId } = await this.requireAdmin(locals, request);

From Old Middleware to Enhanced Shared Middleware

Section titled “From Old Middleware to Enhanced Shared Middleware”
  1. Replace custom middleware logic with createAuthMiddleware(config)
  2. Configure route categories (protected, public, admin, org-required, org-optional)
  3. Enable organization context if needed (enableOrganizationContext: true)
  4. Use middleware chaining for additional security features

From Direct Auth Access to Centralized Utilities

Section titled “From Direct Auth Access to Centralized Utilities”
  1. Replace context.locals.user with getUser(Astro) from @repo/auth/astro
  2. Replace metadata role checks with requireAdmin(Astro) or isOrganizationAdmin()
  3. Use BaseAPIHandler for all new API routes
  4. Update imports to use @repo/auth packages
  • Middleware: Use createAuthMiddleware() with appropriate config
  • Middleware Chaining: Use sequence() from astro:middleware for multiple middleware
  • API Routes: Extend BaseAPIHandler for authentication
  • Pages: Use @repo/auth/astro utilities for user context
  • Admin Access: Implement via requireAdmin() in API layer
  • Organization Context: Configure routes appropriately
  • Audit: Check for direct metadata access or bypass patterns
  • Update: Replace with centralized utilities
  • Test: Verify authentication and authorization still work
  • Document: Update any custom auth logic documentation
Terminal window
# Test auth package
cd packages/auth && pnpm test
# Test webapp build
pnpm --filter webapp build
# Test CRM build
pnpm --filter crm build
# Test all apps
pnpm build
  • All tests pass in @repo/auth package
  • Webapp builds successfully with new middleware
  • CRM builds successfully with new middleware
  • No TypeScript errors in authentication code
  • Middleware chaining works correctly
  • Organization context handling works as expected

Remember: Always use the appropriate authentication layer for your use case. When in doubt, refer to this guide or check existing implementations in the codebase.