Better Auth
Better Auth is the recommended authentication solution for APSO backends. It provides a complete, type-safe authentication system.
Overview
| Feature | Support |
|---|---|
| Email/Password | âś… |
| OAuth Providers | âś… |
| Magic Links | âś… |
| Two-Factor Auth | âś… |
| Session Management | âś… |
| Password Reset | âś… |
| Email Verification | âś… |
Installation
npm install better-authConfiguration
Basic Setup
// src/lib/auth.ts
import { betterAuth } from 'better-auth';
import { Pool } from 'pg';
export const auth = betterAuth({
database: new Pool({
connectionString: process.env.DATABASE_URL,
}),
emailAndPassword: {
enabled: true,
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day
},
});
export type Session = typeof auth.$Infer.Session;With OAuth Providers
// src/lib/auth.ts
import { betterAuth } from 'better-auth';
export const auth = betterAuth({
database: new Pool({
connectionString: process.env.DATABASE_URL,
}),
emailAndPassword: {
enabled: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
});NestJS Integration
Auth Module
// src/modules/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { auth } from '../../lib/auth';
@Module({
controllers: [AuthController],
providers: [
AuthService,
{
provide: 'BETTER_AUTH',
useValue: auth,
},
],
exports: [AuthService],
})
export class AuthModule {}Auth Controller
// src/modules/auth/auth.controller.ts
import { Controller, Post, Body, Get, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';
import { auth } from '../../lib/auth';
@Controller('auth')
export class AuthController {
@Post('sign-up')
async signUp(
@Body() body: { email: string; password: string; name: string },
@Req() req: Request,
@Res() res: Response,
) {
return auth.api.signUpEmail({
body: {
email: body.email,
password: body.password,
name: body.name,
},
asResponse: true,
headers: req.headers,
});
}
@Post('sign-in')
async signIn(
@Body() body: { email: string; password: string },
@Req() req: Request,
@Res() res: Response,
) {
return auth.api.signInEmail({
body,
asResponse: true,
headers: req.headers,
});
}
@Post('sign-out')
async signOut(@Req() req: Request, @Res() res: Response) {
return auth.api.signOut({
asResponse: true,
headers: req.headers,
});
}
@Get('session')
async getSession(@Req() req: Request) {
const session = await auth.api.getSession({
headers: req.headers,
});
return session;
}
}Auth Guard
// src/common/guards/auth.guard.ts
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { auth } from '../../lib/auth';
@Injectable()
export class AuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
throw new UnauthorizedException();
}
request.user = session.user;
request.session = session.session;
return true;
}
}Organization Support
Custom User Fields
// src/lib/auth.ts
import { betterAuth } from 'better-auth';
export const auth = betterAuth({
database: new Pool({
connectionString: process.env.DATABASE_URL,
}),
user: {
additionalFields: {
organizationId: {
type: 'string',
required: true,
},
role: {
type: 'string',
defaultValue: 'member',
},
},
},
});Organization Middleware
// src/common/middleware/organization.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class OrganizationMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
if (req.user && !req.user.organizationId) {
// Handle users without organization
// Redirect to org setup or throw error
}
next();
}
}Frontend Integration
React Client
// src/lib/auth-client.ts
import { createAuthClient } from 'better-auth/react';
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_API_URL,
});
export const {
signIn,
signUp,
signOut,
useSession,
} = authClient;Login Component
// components/LoginForm.tsx
'use client';
import { useState } from 'react';
import { signIn } from '../lib/auth-client';
export function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await signIn.email({ email, password });
window.location.href = '/dashboard';
} catch (err) {
setError('Invalid credentials');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="Password"
/>
{error && <p className="text-red-500">{error}</p>}
<button type="submit">Sign In</button>
</form>
);
}Session Hook
// components/UserMenu.tsx
'use client';
import { useSession, signOut } from '../lib/auth-client';
export function UserMenu() {
const { data: session, isPending } = useSession();
if (isPending) return <div>Loading...</div>;
if (!session) return <a href="/login">Sign In</a>;
return (
<div>
<span>{session.user.name}</span>
<button onClick={() => signOut()}>Sign Out</button>
</div>
);
}Two-Factor Authentication
// src/lib/auth.ts
import { betterAuth } from 'better-auth';
import { twoFactor } from 'better-auth/plugins';
export const auth = betterAuth({
plugins: [
twoFactor({
issuer: 'YourApp',
}),
],
});Environment Variables
# .env
DATABASE_URL=postgresql://...
BETTER_AUTH_SECRET=your-secret-key
# OAuth Providers
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...Related
Last updated on