Skip to Content
🚀 APSO is now in public beta. Get started →
Get StartedCore ConceptsAuthentication (BYOA)

Authentication (BYOA)

APSO follows a Bring Your Own Auth philosophy. Use our recommended Better Auth integration, or plug in any authentication provider.

Philosophy

BYOA: Bring Your Own Auth

APSO doesn’t lock you into a specific auth provider. Generated code validates JWTs—you decide who issues them.

Why BYOA?

  1. Flexibility — Use Auth0, Clerk, Firebase, Supabase, or self-hosted
  2. No lock-in — Switch providers without changing APSO code
  3. Existing systems — Integrate with your current auth infrastructure
  4. Full control — Own your user data and authentication flow

Better Auth is our recommended solution for new projects. It’s open source, self-hosted, and integrates seamlessly with APSO.

What is Better Auth?

  • Open-source authentication library
  • Self-hosted (runs in your APSO backend)
  • Email/password, OAuth, magic links
  • Session management built-in
  • TypeScript-native

Quick Setup

.apsorc
{ "auth": { "provider": "better-auth", "sessionStrategy": "jwt", "entities": { "user": "User", "session": "Session", "account": "Account" } } }
# Scaffold includes Better Auth when configured apso server scaffold

Generated auth entities:

// User entity - your app's user model @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') id: string; @Column({ unique: true }) email: string; @Column() name: string; @Column({ select: false }) passwordHash: string; @Column({ default: false }) emailVerified: boolean; } // Session entity - active sessions @Entity('sessions') export class Session { @PrimaryGeneratedColumn('uuid') id: string; @Column() userId: string; @Column() token: string; @Column() expiresAt: Date; }

JWT Validation

Regardless of auth provider, APSO validates JWTs on protected routes:

// Generated guard @Injectable() export class AuthGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const token = this.extractToken(request); if (!token) { throw new UnauthorizedException(); } const payload = await this.jwtService.verifyAsync(token, { secret: this.configService.get('JWT_SECRET'), }); request.user = payload; return true; } }

Using Other Providers

Auth0

src/extensions/auth/auth0.guard.ts
import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class Auth0Guard extends AuthGuard('jwt') { // Configure Auth0 JWT validation }
.env
AUTH0_DOMAIN=your-tenant.auth0.com AUTH0_AUDIENCE=https://your-api.com

Clerk

src/extensions/auth/clerk.guard.ts
import { clerkClient } from '@clerk/clerk-sdk-node'; @Injectable() export class ClerkGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const sessionToken = request.headers.authorization?.replace('Bearer ', ''); const session = await clerkClient.sessions.verifySession(sessionToken); request.user = session; return true; } }

Firebase Auth

src/extensions/auth/firebase.guard.ts
import * as admin from 'firebase-admin'; @Injectable() export class FirebaseGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const token = request.headers.authorization?.replace('Bearer ', ''); const decodedToken = await admin.auth().verifyIdToken(token); request.user = decodedToken; return true; } }

Organization/Tenant Context

APSO uses headers for multi-tenant context:

# Every request includes organization context curl https://api.example.com/customers \ -H "Authorization: Bearer <jwt>" \ -H "X-Organization-Id: org_123"

The organization guard validates:

  1. User is authenticated (JWT valid)
  2. User belongs to the organization
  3. Request is scoped to that organization’s data

Auth Configuration Reference

.apsorc
{ "auth": { "provider": "better-auth", // or "custom" "sessionStrategy": "jwt", // or "session" "jwtSecret": "env:JWT_SECRET", // From environment "jwtExpiry": "7d", // Token expiration "refreshTokenExpiry": "30d", // Refresh token expiration "entities": { "user": "User", // Your user entity name "session": "Session", // Session entity (if using sessions) "account": "Account" // OAuth accounts }, "oauth": { "google": { "enabled": true, "clientId": "env:GOOGLE_CLIENT_ID", "clientSecret": "env:GOOGLE_CLIENT_SECRET" } } } }

Next Steps

Last updated on