git:20260907.57a4b90 to git:20260911.16cf003
73 added, 179 removed. Audit A to A.
---
name: authentication-identity-expert
description: "Expert guide for implementing secure authentication, authorization (RBAC/ABAC), OAuth2, and identity management (Clerk, Auth.js, Supabase Auth) / Panduan ahli untuk autentikasi dan otorisasi."
author: "Roedy Rustam"
---
# Authentication & Identity Expert (2026 Edition)
[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
---
<a name="english"></a>
## English
### Description
- Production-grade guide for implementing secure authentication, authorization, and identity management in modern web and mobile applications. Covers **Clerk**, **Supabase Auth**, **Auth.js v5**, **Better Auth**, JWT patterns, OAuth 2.1, PKCE flows, RBAC/ABAC, passkeys (WebAuthn), and MFA implementation for React 19 / Next.js 15 stacks.
+ Production-grade guide for implementing secure authentication, authorization, and identity management in modern web and mobile applications. Covers **Clerk**, **Supabase Auth**, **Auth.js v5**, **Better Auth**, JWT patterns, OAuth 2.1, PKCE flows, RBAC/ABAC, passkeys (WebAuthn), and MFA implementations for React 19 / Next.js 15 stacks.
### Trigger Conditions
- - Working on login/signup flows, session management, or OAuth integration.
- - Integrating **Clerk**, **Supabase Auth**, **Auth.js v5**, **Better Auth**, or **Auth0**.
- - Implementing **Role-Based Access Control (RBAC)** or **Attribute-Based Access Control (ABAC)**.
- - Setting up **WebAuthn/Passkeys**, **MFA/2FA**, or **SSO**.
+ - Working on login/signup flows, session management, or OAuth integrations.
+ - Integrating Clerk, Supabase Auth, Auth.js v5, Better Auth, or Auth0.
+ - Implementing RBAC (Role-Based Access Control) or ABAC.
+ - Setting up WebAuthn/Passkeys, MFA/2FA, or SSO.
- Securing Next.js App Router routes with middleware-based auth guards.
- - Implementing **OAuth 2.1** flows with PKCE for SPAs or mobile apps.
---
- ### Identity Provider Selection Guide (2026)
-
- | Provider | Best For | Key Strength |
- |---|---|---|
- | **Clerk** | SaaS, B2B apps | Built-in organizations, roles, MFA, UI components |
- | **Supabase Auth** | Supabase-powered apps | RLS integration, social login, SSO |
- | **Auth.js v5** | Next.js self-hosted | Flexible adapters, edge-compatible |
- | **Better Auth** | TypeScript-first self-hosted | Modern DX, built-in 2FA, org management |
- | **Auth0** | Enterprise, compliance | Enterprise SSO, compliance (SOC2, HIPAA) |
+ ### Identity Provider Selection Guide
- **Recommendation**: Use **Clerk** for most new SaaS apps (best DX). Use **Supabase Auth** if you're already using Supabase. Use **Better Auth** for full control without vendor lock-in.
+ | Provider | Best For | Key Strength | Pricing Model |
+ |---|---|---|---|
+ | **Clerk** | New SaaS products | Best developer DX, prebuilt UI components, orgs/teams out-of-the-box | Per MAU (free up to 10k) |
+ | **Supabase Auth** | Existing Supabase apps | Native RLS integration, multi-provider OAuth, generous free tier | Included with Supabase |
+ | **Auth.js v5** | Self-hosted control | Full data ownership, zero vendor lock-in, Edge-compatible | 100% Free / OSS |
+ | **Better Auth** | Modern TS-first apps | Comprehensive TypeScript plugin ecosystem, 2FA, passkeys | 100% Free / OSS |
---
- ### Core Security Principles
-
- #### 1. Never Roll Your Own Crypto
- - Use `argon2id` (preferred) or `bcrypt` for password hashing — **never MD5, SHA1, or plain SHA256**.
- - Use battle-tested libraries: `@node-rs/argon2`, `bcryptjs`.
- - Minimum: bcrypt cost factor ≥12, argon2id memory ≥64MB.
+ ### Technical Guidelines & Recipes
- #### 2. JWT Best Practices
+ #### 1. Password Hashing Standards
```typescript
- // ✅ Short-lived access tokens + HttpOnly refresh tokens
- const ACCESS_TOKEN_EXPIRY = '15m'; // 15 minutes
- const REFRESH_TOKEN_EXPIRY = '7d'; // 7 days
+ import argon2 from 'argon2';
- // ✅ Store refresh tokens in HttpOnly, Secure, SameSite=Strict cookies
- res.cookie('refresh_token', token, {
- httpOnly: true,
- secure: true,
- sameSite: 'strict',
- maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in ms
- });
+ export async function hashPassword(password: string): Promise<string> {
+ return argon2.hash(password, {
+ type: argon2.argon2id,
+ memoryCost: 65536, // 64 MB
+ timeCost: 3,
+ parallelism: 4,
+ });
+ }
+ ```
- // ❌ Never store tokens in localStorage (XSS vulnerable)
- // ❌ Never put sensitive data in JWT payload (it's base64 decoded, not encrypted)
+ #### 2. Short-Lived JWT + Refresh Token Rotation
+ ```typescript
+ // Access token: 15 minutes, in memory
+ // Refresh token: 7-30 days, HttpOnly, Secure, SameSite=Strict cookie
```
- #### 3. OAuth 2.1 + PKCE (For SPAs & Mobile)
+ #### 3. OAuth 2.1 + PKCE
```typescript
- // PKCE flow — required for public clients in OAuth 2.1
import { generateCodeVerifier, generateCodeChallenge } from 'oslo/oauth2';
- const codeVerifier = generateCodeVerifier(); // 43-128 char random string
- const codeChallenge = await generateCodeChallenge(codeVerifier); // SHA-256 hash
-
- // Store codeVerifier in sessionStorage (NOT localStorage) during redirect
+ const codeVerifier = generateCodeVerifier();
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
sessionStorage.setItem('pkce_verifier', codeVerifier);
- // Auth URL includes challenge
const authUrl = new URL('https://provider.com/oauth/authorize');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
- authUrl.searchParams.set('state', cryptoRandomState); // CSRF protection
+ authUrl.searchParams.set('state', cryptoRandomState);
```
#### 4. Clerk — Next.js 15 Integration
```typescript
- // middleware.ts — protect routes at the edge
+ // middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
- const isPublicRoute = createRouteMatcher([
- '/', '/sign-in(.*)', '/sign-up(.*)', '/api/webhooks(.*)',
- ]);
+ const isPublicRoute = createRouteMatcher(['/', '/sign-in(.*)', '/sign-up(.*)', '/api/webhooks(.*)']);
export default clerkMiddleware(async (auth, req) => {
- if (!isPublicRoute(req)) {
- await auth.protect(); // Redirects to sign-in if unauthenticated
- }
+ if (!isPublicRoute(req)) await auth.protect();
});
export const config = {
matcher: ['/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', '/(api|trpc)(.*)'],
};
```
- ```typescript
- // Server Component — get user on the server
- import { auth, currentUser } from '@clerk/nextjs/server';
-
- export default async function DashboardPage() {
- const { userId, orgId, orgRole } = await auth();
- if (!userId) redirect('/sign-in');
-
- const user = await currentUser();
- return <div>Welcome, {user?.firstName}!</div>;
- }
- ```
-
#### 5. Supabase Auth — RLS Integration
- ```typescript
- // Create Supabase client with user session
- import { createServerClient } from '@supabase/ssr';
-
- export async function createSupabaseServerClient(cookieStore: ReadonlyRequestCookies) {
- return createServerClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
- {
- cookies: {
- getAll: () => cookieStore.getAll(),
- setAll: (cookies) => cookies.forEach(({ name, value, options }) =>
- cookieStore.set(name, value, options)
- ),
- },
- }
- );
- }
-
- // RLS in PostgreSQL — users can only access their own rows
- -- CREATE POLICY "Users can only see own data" ON profiles
- -- FOR ALL USING (auth.uid() = user_id);
- ```
+ > For detailed Supabase Auth SSR setup, PKCE session cookie handling, and PostgreSQL Row-Level Security (RLS) policies, delegate directly to `supabase-security-expert`.
#### 6. Auth.js v5 (Next.js App Router)
```typescript
- // auth.ts
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import { DrizzleAdapter } from '@auth/drizzle-adapter';
import { db } from '@/db';
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: DrizzleAdapter(db),
- providers: [
- GitHub,
- // Add more providers...
- ],
- session: { strategy: 'database' }, // or 'jwt'
- callbacks: {
- authorized({ auth, request: { nextUrl } }) {
- const isLoggedIn = !!auth?.user;
- const isProtected = nextUrl.pathname.startsWith('/dashboard');
- if (isProtected && !isLoggedIn) return false;
- return true;
- },
- },
+ providers: [GitHub],
+ session: { strategy: 'database' },
});
```
#### 7. RBAC — Role-Based Access Control (Server-Side)
```typescript
- // ✅ Always enforce roles server-side — never rely on client UI alone
type Role = 'viewer' | 'editor' | 'admin' | 'super_admin';
const permissions: Record<Role, string[]> = {
viewer: ['read:posts'],
editor: ['read:posts', 'write:posts', 'delete:own_posts'],
admin: ['read:posts', 'write:posts', 'delete:any_post', 'manage:users'],
- super_admin: ['*'], // All permissions
+ super_admin: ['*'],
};
- function hasPermission(userRole: Role, required: string): boolean {
- const allowed = permissions[userRole];
+ export function hasPermission(userRole: Role, required: string): boolean {
+ const allowed = permissions[userRole] || [];
return allowed.includes('*') || allowed.includes(required);
}
-
- // In Server Action / API Route
- const { userId } = await auth();
- const user = await db.query.users.findFirst({ where: eq(users.id, userId) });
-
- if (!hasPermission(user.role, 'delete:any_post')) {
- throw new Error('Forbidden');
- }
```
#### 8. WebAuthn / Passkeys (2026 Standard)
```typescript
- // Using SimpleWebAuthn library
import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server';
- // Registration — generate challenge
- const options = await generateRegistrationOptions({
- rpName: 'My App',
- rpID: 'myapp.com',
- userName: user.email,
- attestationType: 'none',
- authenticatorSelection: {
- residentKey: 'preferred', // Enables passkeys
- userVerification: 'preferred',
- },
- });
-
- // Store challenge in session, send options to client
- await redis.setex(`webauthn:${userId}`, 60, JSON.stringify(options.challenge));
+ export async function getPasskeyRegistrationOptions(userId: string, userEmail: string) {
+ return generateRegistrationOptions({
+ rpName: 'My App',
+ rpID: 'myapp.com',
+ userID: new TextEncoder().encode(userId),
+ userName: userEmail,
+ attestationType: 'none',
+ authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred' },
+ });
+ }
```
- ### Security Checklist
- - [ ] Passwords hashed with argon2id (cost ≥64MB) or bcrypt (cost ≥12).
- - [ ] JWT access tokens expire in ≤15 minutes; refresh in HttpOnly cookies.
- - [ ] OAuth state parameter validated on callback (CSRF protection).
+ ### Production Security Checklist
+ - [ ] Passwords hashed with argon2id or bcrypt (cost >= 12).
+ - [ ] JWT access token expires in <= 15 minutes; refresh token in HttpOnly cookie.
- [ ] PKCE enforced for all public OAuth clients.
- - [ ] All authorization checks enforced server-side (not UI-only).
- - [ ] Admin routes protected by both auth middleware AND role check.
- - [ ] MFA offered (TOTP/WebAuthn) for privileged accounts.
- - [ ] Refresh token rotation on every use (detect theft).
- - [ ] Rate limiting on login/signup endpoints (prevent brute force).
- - [ ] Account lockout after N failed attempts.
+ - [ ] Authorization enforced on server actions and API routes (never UI only).
+ - [ ] Rate limiting on login and signup endpoints.
+ ## Orchestration & Integration
+ - Connects with `supabase-security-expert`, `saas-architect`, `js-backend-expert`, `firebase-security-expert`, and `production-ready-hardener`.
+
---
<a name="bahasa-indonesia"></a>
## Bahasa Indonesia
- ### Integrasi Orkestrasi
- Terhubung dan mengorkestrasi skill domain yang relevan seperti `brainstorming`, `zero-to-prod-orchestrator`, dan `project-context-mapper` untuk memastikan eksekusi yang kohesif.
-
### Deskripsi
- Panduan tingkat produksi untuk mengimplementasikan autentikasi, otorisasi, dan manajemen identitas yang aman pada aplikasi web dan mobile modern. Mencakup **Clerk**, **Supabase Auth**, **Auth.js v5**, **Better Auth**, pola JWT, OAuth 2.1, alur PKCE, RBAC/ABAC, passkey (WebAuthn), dan implementasi MFA untuk stack React 19 / Next.js 15.
-
- ### Kondisi Pemicu
- - Mengerjakan alur login/signup, manajemen sesi, atau integrasi OAuth.
- - Mengintegrasikan Clerk, Supabase Auth, Auth.js v5, Better Auth, atau Auth0.
- - Mengimplementasikan RBAC (Role-Based Access Control) atau ABAC.
- - Menyiapkan WebAuthn/Passkeys, MFA/2FA, atau SSO.
- - Mengamankan rute Next.js App Router dengan auth guard berbasis middleware.
-
- ### Panduan Teknis
-
- #### 1. Prinsip Keamanan Inti
- - **Jangan buat kriptografi sendiri**: Gunakan `argon2id` atau `bcrypt` untuk hashing password.
- - **JWT berumur pendek**: Access token ≤15 menit, refresh token di HttpOnly cookie.
- - **PKCE wajib** untuk semua public OAuth client (SPA, mobile).
- - **Validasi state OAuth** di callback untuk proteksi CSRF.
-
- #### 2. Pemilihan Identity Provider
- - **Clerk**: Terbaik untuk SaaS baru — DX terbaik, komponen UI siap pakai, manajemen organisasi.
- - **Supabase Auth**: Ideal jika sudah pakai Supabase — integrasi langsung dengan RLS.
- - **Auth.js v5**: Self-hosted, fleksibel, edge-compatible untuk Next.js.
- - **Better Auth**: TypeScript-first, kontrol penuh tanpa vendor lock-in.
-
- #### 3. RBAC di Sisi Server
- Selalu terapkan pengecekan role di server (Server Action, API route, middleware) — **jangan hanya di UI**. Gunakan tabel permission yang memetakan setiap role ke daftar aksi yang diizinkan.
-
- #### 4. WebAuthn / Passkeys
- Gunakan library `@simplewebauthn/server` untuk registrasi dan verifikasi passkey. Simpan challenge di Redis dengan TTL pendek (60 detik) untuk mencegah replay attack.
+ Panduan tingkat produksi untuk autentikasi, otorisasi, dan manajemen identitas modern (Clerk, Supabase Auth, Auth.js v5, Better Auth, OAuth 2.1 PKCE, RBAC, dan Passkeys).
- #### 5. Checklist Keamanan
- - Password di-hash dengan argon2id atau bcrypt.
- - JWT access token expire ≤15 menit; refresh di HttpOnly cookie.
- - State OAuth divalidasi saat callback.
- - PKCE diterapkan untuk semua klien publik OAuth.
- - Pengecekan otorisasi dilakukan di server, bukan hanya UI.
- - MFA ditawarkan untuk akun privileged.
- - Rate limiting di endpoint login/signup.
- - Rotasi refresh token setiap kali digunakan.
+ ### Rekomendasi Identity Provider
+ - **Clerk**: Terbaik untuk SaaS baru (UI bawaan, manajemen tim/organisasi).
+ - **Supabase Auth**: Terbaik jika memakai database Supabase (integrasi native RLS).
+ - **Auth.js v5**: Self-hosted, kontrol penuh tanpa vendor lock-in.
+ - **Better Auth**: TypeScript-first, fitur 2FA dan passkey bawaan.
+ ### Poin Kunci Keamanan
+ 1. **Password Hashing**: Gunakan `argon2id` atau `bcrypt` (cost >= 12).
+ 2. **Token Berumur Pendek**: Access token <= 15 menit, refresh token di cookie HttpOnly dengan rotasi token.
+ 3. **PKCE Wajib**: Terapkan PKCE untuk semua klien publik OAuth.
+ 4. **Otorisasi Server-Side**: Wajibkan verifikasi hak akses di server (bukan hanya UI).
+ 5. **Supabase Auth**: Untuk setup SSR dan RLS mendalam, delegasikan ke `supabase-security-expert`.
- ## Orchestration & Integration
- - Connects to other backend skills as part of the orchestration flow.
+ ## Integrasi Orkestrasi
+ - Terhubung dengan `supabase-security-expert`, `saas-architect`, `js-backend-expert`, `firebase-security-expert`, dan `production-ready-hardener`.