Authentication Patterns
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”ทำ optimistic check เบา ๆ ใน middleware เพื่อ redirect request ที่ไม่ auth ชัด ๆ ออกไป แต่ทำ authorization จริงให้ใกล้ data ที่สุด ใน Data Access Layer ที่ verify session ก่อนจะ return อะไรออกมา
Sessions and cookies
หัวข้อที่มีชื่อว่า “Sessions and cookies”auth ส่วนใหญ่วิ่งอยู่บน session ที่เก็บใน cookie แบบ HTTP-only ฝั่ง server เราอ่าน cookie นั้นด้วย cookies() จาก next/headers
// app/lib/session.ts — read the session cookie on the serverimport { cookies } from 'next/headers'import { decrypt } from '@/app/lib/crypto'
export async function getSession() { const cookie = (await cookies()).get('session')?.value if (!cookie) return null return decrypt(cookie)}Optimistic check in middleware
หัวข้อที่มีชื่อว่า “Optimistic check in middleware”middleware เป็นที่ที่เหมาะกับ gate แบบ optimistic ราคาถูก ถ้าไม่มี session ชัด ๆ ก็ redirect ก่อน render วิธีนี้เร็วก็จริง แต่แค่ redirect ห้ามใช้เป็นด่านเดียว
// middleware.ts — optimistic redirect onlyimport { NextResponse } from 'next/server'import type { NextRequest } from 'next/server'
const protectedRoutes = ['/dashboard']
export function middleware(request: NextRequest) { const path = request.nextUrl.pathname const hasSession = request.cookies.has('session') if (protectedRoutes.some((p) => path.startsWith(p)) && !hasSession) { return NextResponse.redirect(new URL('/login', request.url)) } return NextResponse.next()}Real authorization lives in a Data Access Layer
หัวข้อที่มีชื่อว่า “Real authorization lives in a Data Access Layer”middleware อย่างเดียวไม่พอ ไม่ได้รันบนทุก data path และ layout ก็ไม่ได้ re-run ทุกครั้งที่ navigate เพราะฉะนั้น request ที่เก่าหรือถูกปลอมอาจหลุดผ่านไปได้ ทางแก้คือ Data Access Layer (DAL) จุดเดียวที่ verify session ก่อน return data ห่อด้วย React cache เพื่อให้รันครั้งเดียวต่อ request
// app/lib/dal.ts — verify the session close to the dataimport 'server-only'import { cache } from 'react'import { redirect } from 'next/navigation'import { getSession } from '@/app/lib/session'
export const verifySession = cache(async () => { const session = await getSession() if (!session?.userId) { redirect('/login') } return { isAuth: true, userId: session.userId }})จากนั้นทุก page, Server Action หรือ route handler ที่แตะ data ที่ป้องกันไว้จะเรียก verifySession() ก่อน สำหรับโปรเจกต์จริง library อย่าง NextAuth (Auth.js), Clerk หรือ Lucia จัดการ session, provider และ token ให้ แต่ pattern DAL สำหรับ authorization ก็ยังต้องวางทับ library พวกนี้อยู่ดี
graph TD A["Request"] --> B["middleware: optimistic redirect"] B --> C["Page / Server Action"] C --> D["verifySession() in DAL"] D -->|no session| E["redirect to /login"] D -->|valid| F["Return protected data"]