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?
- Flexibility — Use Auth0, Clerk, Firebase, Supabase, or self-hosted
- No lock-in — Switch providers without changing APSO code
- Existing systems — Integrate with your current auth infrastructure
- Full control — Own your user data and authentication flow
Recommended: Better Auth
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 scaffoldGenerated 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.comClerk
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:
- User is authenticated (JWT valid)
- User belongs to the organization
- 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