agentsclimarketplace

Auth

Skill PIXARTSeu/Synapse/packages/codegraph/data/skill/auth

Authentication and authorization knowledge base - Auth.js v5, OAuth, credentials, JWT, RBAC. Use when implementing authentication, login, registration, session management, or role-based access control.From its SKILL.md

Install
npx -y skills add PIXARTSeu/Synapse --skill auth

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

14.0 KB, ~3.4k tokens by cl100k_base, as published. Nobody here has run it

Authentication Skill

Knowledge base per autenticazione e autorizzazione in applicazioni frontend moderne.

Auth.js (NextAuth.js v5)

Installazione

pnpm add next-auth@beta @auth/prisma-adapter

Configurazione Base

// auth.ts (root del progetto)
import NextAuth from 'next-auth'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { db } from '@/lib/db'
import Google from 'next-auth/providers/google'
import GitHub from 'next-auth/providers/github'
import Credentials from 'next-auth/providers/credentials'
import { compare } from 'bcryptjs'

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(db),
  session: { strategy: 'jwt' },
  
  pages: {
    signIn: '/login',
    signOut: '/logout',
    error: '/auth/error',
    verifyRequest: '/auth/verify',
  },
  
  providers: [
    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!,
    }),
    Credentials({
      name: 'credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) {
          return null
        }

        const user = await db.user.findUnique({
          where: { email: credentials.email as string },
        })

        if (!user || !user.password) {
          return null
        }

        const isValid = await compare(
          credentials.password as string,
          user.password
        )

        if (!isValid) {
          return null
        }

        return {
          id: user.id,
          email: user.email,
          name: user.name,
          image: user.avatar,
          role: user.role,
        }
      },
    }),
  ],

  callbacks: {
    async jwt({ token, user, trigger, session }) {
      if (user) {
        token.id = user.id
        token.role = user.role
      }
      
      // Update session
      if (trigger === 'update' && session) {
        token.name = session.name
      }
      
      return token
    },
    
    async session({ session, token }) {
      if (token) {
        session.user.id = token.id as string
        session.user.role = token.role as string
      }
      return session
    },
    
    async signIn({ user, account, profile }) {
      // Custom sign in logic
      // Return false to deny access
      return true
    },
    
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user
      const isOnDashboard = nextUrl.pathname.startsWith('/dashboard')
      const isOnAdmin = nextUrl.pathname.startsWith('/admin')
      
      if (isOnAdmin) {
        if (isLoggedIn && auth.user.role === 'ADMIN') return true
        return Response.redirect(new URL('/login', nextUrl))
      }
      
      if (isOnDashboard) {
        if (isLoggedIn) return true
        return false // Redirect to login
      }
      
      return true
    },
  },
})

Route Handlers

// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlers

Middleware

// middleware.ts
import { auth } from '@/auth'

export default auth((req) => {
  const { nextUrl } = req
  const isLoggedIn = !!req.auth

  // Protezione routes
  const protectedRoutes = ['/dashboard', '/settings', '/profile']
  const isProtected = protectedRoutes.some(route => 
    nextUrl.pathname.startsWith(route)
  )

  if (isProtected && !isLoggedIn) {
    return Response.redirect(new URL('/login', nextUrl))
  }

  // Redirect logged users from auth pages
  const authRoutes = ['/login', '/register']
  const isAuthRoute = authRoutes.includes(nextUrl.pathname)
  
  if (isAuthRoute && isLoggedIn) {
    return Response.redirect(new URL('/dashboard', nextUrl))
  }
})

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

Types Estesi

// types/next-auth.d.ts
import { DefaultSession, DefaultUser } from 'next-auth'
import { JWT, DefaultJWT } from 'next-auth/jwt'

declare module 'next-auth' {
  interface Session {
    user: {
      id: string
      role: string
    } & DefaultSession['user']
  }

  interface User extends DefaultUser {
    role: string
  }
}

declare module 'next-auth/jwt' {
  interface JWT extends DefaultJWT {
    id: string
    role: string
  }
}

Prisma Schema per Auth

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String    @unique
  emailVerified DateTime?
  image         String?
  password      String?   // Per credentials
  role          Role      @default(USER)
  accounts      Account[]
  sessions      Session[]
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.Text
  session_state     String?
  user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

enum Role {
  USER
  ADMIN
  EDITOR
}

Client Components

useSession Hook

'use client'

import { useSession, signIn, signOut } from 'next-auth/react'

export function UserMenu() {
  const { data: session, status } = useSession()

  if (status === 'loading') {
    return <Skeleton className="h-10 w-10 rounded-full" />
  }

  if (!session) {
    return (
      <Button onClick={() => signIn()}>
        Sign In
      </Button>
    )
  }

  return (
    <DropdownMenu>
      <DropdownMenuTrigger>
        <Avatar>
          <AvatarImage src={session.user.image} />
          <AvatarFallback>{session.user.name?.[0]}</AvatarFallback>
        </Avatar>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem>
          <Link href="/profile">Profile</Link>
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => signOut()}>
          Sign Out
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

Session Provider

// app/providers.tsx
'use client'

import { SessionProvider } from 'next-auth/react'

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <SessionProvider>
      {children}
    </SessionProvider>
  )
}

// app/layout.tsx
import { Providers } from './providers'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

Server Components & Actions

Get Session Server-Side

// app/dashboard/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'

export default async function DashboardPage() {
  const session = await auth()
  
  if (!session) {
    redirect('/login')
  }

  return (
    <div>
      <h1>Welcome, {session.user.name}</h1>
      <p>Role: {session.user.role}</p>
    </div>
  )
}

Server Actions

// app/actions/auth.ts
'use server'

import { signIn, signOut } from '@/auth'
import { db } from '@/lib/db'
import { hash } from 'bcryptjs'
import { z } from 'zod'

const registerSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  password: z.string().min(8),
})

export async function register(formData: FormData) {
  const validatedFields = registerSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    password: formData.get('password'),
  })

  if (!validatedFields.success) {
    return { error: 'Invalid fields' }
  }

  const { name, email, password } = validatedFields.data

  // Check if user exists
  const existingUser = await db.user.findUnique({
    where: { email },
  })

  if (existingUser) {
    return { error: 'Email already in use' }
  }

  // Create user
  const hashedPassword = await hash(password, 12)
  
  await db.user.create({
    data: {
      name,
      email,
      password: hashedPassword,
    },
  })

  // Auto sign in after registration
  await signIn('credentials', {
    email,
    password,
    redirectTo: '/dashboard',
  })
}

export async function login(formData: FormData) {
  await signIn('credentials', {
    email: formData.get('email'),
    password: formData.get('password'),
    redirectTo: '/dashboard',
  })
}

export async function logout() {
  await signOut({ redirectTo: '/' })
}

export async function loginWithGoogle() {
  await signIn('google', { redirectTo: '/dashboard' })
}

export async function loginWithGitHub() {
  await signIn('github', { redirectTo: '/dashboard' })
}

Authorization Patterns

Role-Based Access Control (RBAC)

// lib/auth.ts
import { auth } from '@/auth'

export async function getCurrentUser() {
  const session = await auth()
  return session?.user
}

export async function requireAuth() {
  const user = await getCurrentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }
  return user
}

export async function requireRole(roles: string[]) {
  const user = await requireAuth()
  if (!roles.includes(user.role)) {
    throw new Error('Forbidden')
  }
  return user
}

// Uso in Server Actions
export async function adminAction() {
  await requireRole(['ADMIN'])
  // ... admin logic
}

Permission-Based

// lib/permissions.ts
type Permission = 
  | 'posts:read'
  | 'posts:write'
  | 'posts:delete'
  | 'users:read'
  | 'users:write'
  | 'users:delete'

const rolePermissions: Record<string, Permission[]> = {
  USER: ['posts:read'],
  EDITOR: ['posts:read', 'posts:write'],
  ADMIN: ['posts:read', 'posts:write', 'posts:delete', 'users:read', 'users:write', 'users:delete'],
}

export function hasPermission(role: string, permission: Permission): boolean {
  return rolePermissions[role]?.includes(permission) ?? false
}

export async function requirePermission(permission: Permission) {
  const user = await requireAuth()
  if (!hasPermission(user.role, permission)) {
    throw new Error('Forbidden')
  }
  return user
}

JWT Tokens (API Custom)

// lib/jwt.ts
import { SignJWT, jwtVerify } from 'jose'

const secret = new TextEncoder().encode(process.env.JWT_SECRET!)

export async function signToken(payload: any, expiresIn = '7d') {
  return new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime(expiresIn)
    .sign(secret)
}

export async function verifyToken(token: string) {
  try {
    const { payload } = await jwtVerify(token, secret)
    return payload
  } catch {
    return null
  }
}

// Refresh token rotation
export async function refreshTokens(refreshToken: string) {
  const payload = await verifyToken(refreshToken)
  if (!payload) return null

  const newAccessToken = await signToken({ userId: payload.userId }, '15m')
  const newRefreshToken = await signToken({ userId: payload.userId }, '7d')

  return { accessToken: newAccessToken, refreshToken: newRefreshToken }
}

Security Best Practices

1. Password Hashing

import { hash, compare } from 'bcryptjs'

// Hash password (registration)
const hashedPassword = await hash(password, 12)

// Verify password (login)
const isValid = await compare(inputPassword, hashedPassword)

2. Rate Limiting

// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, '1 m'), // 5 requests per minute
})

export async function checkRateLimit(identifier: string) {
  const { success, limit, reset, remaining } = await ratelimit.limit(identifier)
  
  if (!success) {
    throw new Error('Too many requests')
  }
  
  return { limit, reset, remaining }
}

3. CSRF Protection

// Auth.js ha CSRF built-in
// Per API custom:
import { csrf } from '@/lib/csrf'

export async function POST(req: Request) {
  const token = req.headers.get('x-csrf-token')
  if (!csrf.verify(token)) {
    return Response.json({ error: 'Invalid CSRF token' }, { status: 403 })
  }
  // ... handle request
}

4. Secure Cookies

// next.config.js
const securityHeaders = [
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubDomains; preload',
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff',
  },
  {
    key: 'X-Frame-Options',
    value: 'DENY',
  },
]

Environment Variables

# .env.local
AUTH_SECRET="your-secret-key-min-32-chars"

# OAuth Providers
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."

GITHUB_CLIENT_ID="..."
GITHUB_CLIENT_SECRET="..."

# Database
DATABASE_URL="postgresql://..."

# JWT (se usi custom)
JWT_SECRET="your-jwt-secret-min-32-chars"

Checklist Autenticazione

  • Auth.js configurato con providers
  • Prisma schema con modelli User, Account, Session
  • Middleware per protezione routes
  • Types estesi per session
  • Server actions per login/register/logout
  • RBAC o permission system
  • Rate limiting su endpoints auth
  • Password hashing con bcrypt
  • CSRF protection attivo
  • Environment variables sicure
  • Redirect dopo login/logout configurati

What ships with it: 3 files

2.6 KB alongside SKILL.md

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.