auth-flow Skill

Implement secure authentication patterns including OAuth, session management, and protected routes.

Overview#

The auth-flow skill generates production-ready authentication code following security best practices. It supports multiple authentication providers and patterns.

Usage#

Use the auth-flow skill to implement OAuth authentication with Google and GitHub.

Parameters#

ParameterTypeRequiredDescription
providerstringYesAuth provider: clerk, nextauth, custom
strategiesarrayNoLogin strategies: oauth, credentials, magic-link
providersarrayNoOAuth providers: google, github, apple
sessionTypestringNoSession strategy: jwt, database

Generated Output#

Server-Side Auth Check (Clerk)#

1// lib/auth.ts 2import { auth } from '@clerk/nextjs/server' 3 4export async function requireAuth() { 5 const { userId } = await auth() 6 7 if (!userId) { 8 throw new Error('UNAUTHORIZED') 9 } 10 11 return userId 12} 13 14// Usage in Server Action 15export async function myAction() { 16 const userId = await requireAuth() 17 // Continue with authenticated user 18}

Protected Layout#

1// app/(protected)/layout.tsx 2import { auth } from '@clerk/nextjs/server' 3import { redirect } from 'next/navigation' 4 5export default async function ProtectedLayout({ 6 children 7}: { 8 children: React.ReactNode 9}) { 10 const { userId } = await auth() 11 12 if (!userId) { 13 redirect('/sign-in') 14 } 15 16 return <>{children}</> 17}

Get Current User with Database Sync#

1// lib/auth.ts 2import { currentUser } from '@clerk/nextjs/server' 3import { prisma } from '@/lib/db' 4 5export async function getCurrentUser() { 6 const user = await currentUser() 7 if (!user) return null 8 9 // Get or create database user 10 const dbUser = await prisma.user.upsert({ 11 where: { clerkId: user.id }, 12 update: { 13 email: user.emailAddresses[0]?.emailAddress, 14 name: `${user.firstName} ${user.lastName}`.trim() 15 }, 16 create: { 17 clerkId: user.id, 18 email: user.emailAddresses[0]?.emailAddress ?? '', 19 name: `${user.firstName} ${user.lastName}`.trim() 20 } 21 }) 22 23 return dbUser 24}

NextAuth.js Setup#

1// auth.ts 2import NextAuth from 'next-auth' 3import GitHub from 'next-auth/providers/github' 4import Google from 'next-auth/providers/google' 5import { PrismaAdapter } from '@auth/prisma-adapter' 6import { prisma } from '@/lib/db' 7 8export const { handlers, auth, signIn, signOut } = NextAuth({ 9 adapter: PrismaAdapter(prisma), 10 session: { strategy: 'jwt' }, 11 providers: [ 12 GitHub({ 13 clientId: process.env.GITHUB_ID!, 14 clientSecret: process.env.GITHUB_SECRET! 15 }), 16 Google({ 17 clientId: process.env.GOOGLE_ID!, 18 clientSecret: process.env.GOOGLE_SECRET! 19 }) 20 ], 21 callbacks: { 22 async jwt({ token, user }) { 23 if (user) { 24 token.id = user.id 25 } 26 return token 27 }, 28 async session({ session, token }) { 29 if (session.user) { 30 session.user.id = token.id as string 31 } 32 return session 33 } 34 }, 35 pages: { 36 signIn: '/login', 37 error: '/login' 38 } 39})

Custom JWT Session Management#

1// lib/session.ts 2import { SignJWT, jwtVerify } from 'jose' 3import { cookies } from 'next/headers' 4 5const secret = new TextEncoder().encode(process.env.JWT_SECRET) 6const COOKIE_NAME = 'session' 7 8export async function createSession(userId: string) { 9 const token = await new SignJWT({ userId }) 10 .setProtectedHeader({ alg: 'HS256' }) 11 .setIssuedAt() 12 .setExpirationTime('7d') 13 .sign(secret) 14 15 cookies().set(COOKIE_NAME, token, { 16 httpOnly: true, 17 secure: process.env.NODE_ENV === 'production', 18 sameSite: 'lax', 19 maxAge: 60 * 60 * 24 * 7, // 7 days 20 path: '/' 21 }) 22 23 return token 24} 25 26export async function getSession() { 27 const token = cookies().get(COOKIE_NAME)?.value 28 29 if (!token) return null 30 31 try { 32 const { payload } = await jwtVerify(token, secret) 33 return payload as { userId: string } 34 } catch { 35 return null 36 } 37} 38 39export async function destroySession() { 40 cookies().delete(COOKIE_NAME) 41}

Middleware Configuration#

1// middleware.ts 2import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server' 3 4const isProtectedRoute = createRouteMatcher([ 5 '/dashboard(.*)', 6 '/settings(.*)', 7 '/api/user(.*)' 8]) 9 10const isPublicRoute = createRouteMatcher([ 11 '/', 12 '/sign-in(.*)', 13 '/sign-up(.*)', 14 '/api/webhooks(.*)' 15]) 16 17export default clerkMiddleware(async (auth, req) => { 18 if (isProtectedRoute(req)) { 19 await auth.protect() 20 } 21}) 22 23export const config = { 24 matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'] 25}

OAuth Social Buttons#

1// components/auth/SocialButtons.tsx 2'use client' 3 4import { signIn } from 'next-auth/react' 5import { Github } from 'lucide-react' 6 7export function SocialButtons() { 8 return ( 9 <div className="space-y-3"> 10 <button 11 onClick={() => signIn('google', { callbackUrl: '/dashboard' })} 12 className="flex w-full items-center justify-center gap-2 rounded-lg border py-2 hover:bg-gray-50" 13 > 14 <GoogleIcon className="h-5 w-5" /> 15 Continue with Google 16 </button> 17 18 <button 19 onClick={() => signIn('github', { callbackUrl: '/dashboard' })} 20 className="flex w-full items-center justify-center gap-2 rounded-lg border py-2 hover:bg-gray-50" 21 > 22 <Github className="h-5 w-5" /> 23 Continue with GitHub 24 </button> 25 </div> 26 ) 27}

Client-Side Auth Hook#

1// hooks/use-user.ts 2'use client' 3import { useUser } from '@clerk/nextjs' 4 5export function useCurrentUser() { 6 const { user, isLoaded, isSignedIn } = useUser() 7 8 return { 9 user: isSignedIn ? user : null, 10 isLoading: !isLoaded, 11 isAuthenticated: isSignedIn 12 } 13}

Features Included#

  • Server-side authentication checks
  • Protected route layouts
  • JWT session management
  • OAuth provider integration
  • Middleware protection
  • Database user sync
  • Client-side hooks

Customization Options#

Use the auth-flow skill with: - provider: "nextauth" - strategies: ["oauth", "credentials"] - providers: ["google", "github"] - sessionType: "jwt"

Best Practices#

Security Considerations#

  • Always use httpOnly cookies for session tokens
  • Set secure: true in production
  • Use sameSite: 'lax' or 'strict' for CSRF protection
  • Never expose JWT secrets to client-side code
  • Implement session refresh for long-lived sessions

Authentication Flow#

  1. User initiates login
  2. Validate credentials or OAuth callback
  3. Create session token
  4. Store in secure cookie
  5. Verify token on protected routes
  6. Refresh token before expiry

Error Handling#

1// app/auth/error/page.tsx 2const errorMessages: Record<string, string> = { 3 OAuthSignin: 'Error starting OAuth flow', 4 OAuthCallback: 'Error in OAuth callback', 5 OAuthAccountNotLinked: 'Email already registered with different provider', 6 CredentialsSignin: 'Invalid credentials', 7 SessionRequired: 'Please sign in to continue', 8 default: 'An error occurred' 9}