Back to list
aiskillstore

auth-integration

by aiskillstore

Security-audited skills for Claude, Codex & Claude Code. One-click install, quality verified.

102🍴 3📅 Jan 23, 2026

SKILL.md


name: auth-integration description: | Use when implementing authentication - login/signup forms, session management, protected routes, or role-based access control. NOT when non-auth UI, plain data fetching, or unrelated backend logic. Triggers: "login page", "signup form", "auth setup", "protected route", "role-based access", "Better Auth", "NextAuth".

Better Auth Integration Skill

Overview

Expert guidance for authentication implementation using Better Auth/NextAuth v5, including login/signup forms, session management with Zustand, protected routes via middleware, and role-based access control for ERP systems.

When This Skill Applies

This skill triggers when users request:

  • Auth Setup: "Setup Better Auth", "Configure authentication", "Auth providers"
  • Auth Forms: "Login page", "Signup form", "Forgot password", "Email verification"
  • Session Management: "Auth store", "Session handling", "Zustand auth"
  • Protected Routes: "Protected dashboard", "Auth middleware", "Route guards"
  • Role-Based Access: "Role guard", "Permission check", "Admin only", "Teacher access"

Core Rules

1. Provider Setup

// app/auth/[...auth]/route.ts
import { auth } from '@/lib/auth';
import { toNextJsHandler } from 'better-auth/next-js';

export const { GET, POST } = toNextJsHandler(auth);

// lib/auth.ts
import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';

export const auth = betterAuth({
  database: prismaAdapter(prisma),
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days
    updateAge: 60 * 60 * 24, // 24 hours
  },
});

Requirements:

  • Use Better Auth v5 for Next.js App Router
  • Configure providers (Google, Email/Password)
  • Enable email verification for new users
  • Set appropriate session expiration
  • Use environment variables for secrets (never hardcode)
  • Prisma adapter for database integration

2. Auth Forms with Validation

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { signIn } from '@/lib/auth/client';

const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

type LoginFormData = z.infer<typeof loginSchema>;

export default function LoginForm() {
  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<LoginFormData>({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = async (data: LoginFormData) => {
    await signIn.email({
      email: data.email,
      password: data.password,
    });
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <div>
        <label className="block text-sm font-medium mb-2">Email</label>
        <input
          {...register('email')}
          type="email"
          className="w-full px-4 py-2 rounded-lg border"
        />
        {errors.email && <p className="text-red-500 text-sm mt-1">{errors.email.message}</p>}
      </div>
      <div>
        <label className="block text-sm font-medium mb-2">Password</label>
        <input
          {...register('password')}
          type="password"
          className="w-full px-4 py-2 rounded-lg border"
        />
        {errors.password && <p className="text-red-500 text-sm mt-1">{errors.password.message}</p>}
      </div>
      <button
        type="submit"
        disabled={isSubmitting}
        className="w-full py-2 bg-blue-500 text-white rounded-lg disabled:opacity-50"
      >
        {isSubmitting ? 'Signing in...' : 'Sign In'}
      </button>
    </form>
  );
}

Requirements:

  • Use React Hook Form + Zod for validation
  • shadcn/ui components for form inputs
  • Clear error messages with localization support
  • Loading states for better UX
  • Accessibility: proper labels, ARIA attributes
  • Password strength indicator for signup

3. Session Management

import { create } from 'zustand';
import { authClient } from '@/lib/auth/client';

interface AuthState {
  user: User | null;
  session: Session | null;
  isLoading: boolean;
  isAuthenticated: boolean;
  role: string | null;
  hydrateAuth: () => Promise<void>;
  signIn: (email: string, password: string) => Promise<void>;
  signOut: () => Promise<void>;
  refresh: () => Promise<void>;
}

export const useAuthStore = create<AuthState>((set, get) => ({
  user: null,
  session: null,
  isLoading: true,
  isAuthenticated: false,
  role: null,

  hydrateAuth: async () => {
    set({ isLoading: true });
    try {
      const session = await authClient.getSession();
      set({
        user: session.user,
        session: session,
        isAuthenticated: !!session,
        role: session.user?.role || null,
        isLoading: false,
      });
    } catch (error) {
      set({ isLoading: false, isAuthenticated: false });
    }
  },

  signIn: async (email: string, password: string) => {
    await authClient.signIn.email({ email, password });
    await get().hydrateAuth();
  },

  signOut: async () => {
    await authClient.signOut();
    set({
      user: null,
      session: null,
      isAuthenticated: false,
      role: null,
    });
  },

  refresh: async () => {
    await get().hydrateAuth();
  },
}));

export const useAuth = () => {
  const auth = useAuthStore();

  useEffect(() => {
    if (!auth.user && !auth.isLoading) {
      auth.hydrateAuth();
    }
  }, []);

  return auth;
};

Requirements:

  • Use Zustand for client-side auth state
  • Sync with Better Auth session
  • Type-safe user and session data
  • Automatic session refresh on hydration
  • Secure cookie storage (httpOnly, secure, sameSite)
  • Clear sessions on sign out

4. Protected Routes

import { authMiddleware } from 'better-auth/next-js';
import { NextResponse } from 'next/server';

export default authMiddleware({
  pathPrefix: '/dashboard',

  callback: async (request) => {
    const { user } = request;

    // Redirect to login if not authenticated
    if (!user) {
      return NextResponse.redirect(new URL('/auth/login', request.url));
    }

    // Role-based access control
    const path = request.nextUrl.pathname;

    if (path.startsWith('/dashboard/admin') && user.role !== 'admin') {
      return NextResponse.redirect(new URL('/dashboard', request.url));
    }

    if (path.startsWith('/dashboard/teacher') && user.role !== 'teacher' && user.role !== 'admin') {
      return NextResponse.redirect(new URL('/dashboard', request.url));
    }

    return NextResponse.next();
  },
});

export const config = {
  matcher: ['/dashboard/:path*'],
};
import { useRouter } from 'next/navigation';
import { useAuth } from '@/hooks/useAuth';

export function withAuth<P extends object>(
  WrappedComponent: React.ComponentType<P>,
  allowedRoles?: string[]
) {
  return function AuthGuard(props: P) {
    const { isAuthenticated, user, isLoading } = useAuth();
    const router = useRouter();

    useEffect(() => {
      if (!isLoading && !isAuthenticated) {
        router.push('/auth/login');
      }

      if (!isLoading && isAuthenticated && allowedRoles) {
        if (!allowedRoles.includes(user?.role || '')) {
          router.push('/unauthorized');
        }
      }
    }, [isAuthenticated, isLoading, user, router]);

    if (isLoading) {
      return <LoadingSkeleton />;
    }

    if (!isAuthenticated || (allowedRoles && !allowedRoles.includes(user?.role || ''))) {
      return null;
    }

    return <WrappedComponent {...props} />;
  };
}

Requirements:

  • Middleware for server-side route protection
  • Path matchers for protected routes
  • Role-based access control in middleware
  • Client-side auth guard HOC for extra protection
  • Redirect unauthorized users to appropriate pages
  • Loading states during auth checks

5. Role-Based Access Control

export const ROLES = {
  ADMIN: 'admin',
  TEACHER: 'teacher',
  STUDENT: 'student',
  PARENT: 'parent',
} as const;

export const PERMISSIONS = {
  // Admin permissions
  MANAGE_USERS: 'manage:users',
  MANAGE_COURSES: 'manage:courses',
  VIEW_ALL_DATA: 'view:all_data',

  // Teacher permissions
  VIEW_CLASS: 'view:class',
  MANAGE_STUDENTS: 'manage:students',
  CREATE_ASSIGNMENTS: 'create:assignments',

  // Student permissions
  VIEW_OWN_DATA: 'view:own_data',
  SUBMIT_ASSIGNMENTS: 'submit:assignments',
} as const;

export const hasRole = (user: User | null, role: string): boolean => {
  return user?.role === role || user?.role === ROLES.ADMIN;
};

export const hasPermission = (user: User | null, permission: string): boolean => {
  if (!user) return false;
  if (user.role === ROLES.ADMIN) return true;

  const rolePermissions = {
    [ROLES.ADMIN]: Object.values(PERMISSIONS),
    [ROLES.TEACHER]: [
      PERMISSIONS.VIEW_CLASS,
      PERMISSIONS.MANAGE_STUDENTS,
      PERMISSIONS.CREATE_ASSIGNMENTS,
    ],
    [ROLES.STUDENT]: [
      PERMISSIONS.VIEW_OWN_DATA,
      PERMISSIONS.SUBMIT_ASSIGNMENTS,
    ],
    [ROLES.PARENT]: [
      PERMISSIONS.VIEW_OWN_DATA,
    ],
  };

  return rolePermissions[user.role]?.includes(permission) ?? false;
};

export const PermissionGuard = ({
  permission,
  fallback = null,
  children,
}: {
  permission: string;
  fallback?: React.ReactNode;
  children: React.ReactNode;
}) => {
  const { user } = useAuth();

  if (!hasPermission(user, permission)) {
    return <>{fallback}</>;
  }

  return <>{children}</>;
};

Requirements:

  • Define roles and permissions clearly
  • Admin has all permissions by default
  • Type-safe permission checking
  • Permission-aware components
  • Server-side permission verification for sensitive actions
  • Audit logs for permission checks

Output Requirements

Code Files

  1. Auth Configuration:

    • lib/auth.ts - Better Auth setup
    • app/auth/[...auth]/route.ts - Auth API routes
  2. Forms:

    • app/auth/login/page.tsx - Login form
    • app/auth/signup/page.tsx - Signup form
    • app/auth/forgot-password/page.tsx - Password reset
  3. Session Management:

    • lib/auth-store.ts - Zustand auth store
    • hooks/useAuth.ts - Auth hook
  4. Protected Routes:

    • middleware.ts - Route protection
    • components/AuthGuard.tsx - Auth guard HOC

Integration Requirements

  • shadcn/ui: Use shadcn form components
  • @ui-ux-designer: Ensure responsive, accessible forms
  • @nextjs-app-router: Follow Next.js App Router patterns
  • @react-component: Use functional components with hooks

Documentation

  • PHR: Create Prompt History Record for auth decisions
  • ADR: Document auth strategy (JWT vs DB sessions, provider choice)
  • Comments: Document security considerations

Workflow

  1. Analyze Auth Requirements

    • Identify auth providers needed (Google, Email/Password)
    • Determine role-based access needs
    • Check email verification requirements
  2. Setup Auth Provider

    • Configure Better Auth with providers
    • Setup database adapter (Prisma)
    • Configure session settings
  3. Create Auth Forms

    • Build login form with validation
    • Create signup with email verification
    • Add password reset flow
  4. Implement Session Management

    • Create Zustand auth store
    • Sync with Better Auth session
    • Handle session refresh
  5. Protect Routes

    • Setup middleware for server-side protection
    • Create auth guard HOC for client-side
    • Implement role-based access control
  6. Validate Security

    • Check OWASP compliance
    • Verify CSRF protection
    • Test session handling

Quality Checklist

Before completing any auth implementation:

  • OWASP Secure: CSRF tokens, XSS protection, secure headers
  • Password Hash/Verify: bcrypt/argon2, no plain text storage
  • Role Guards: Middleware + client-side role checks
  • Logout Clear Sessions: Clear cookies, Zustand store, redirect
  • Responsive Mobile Login: Mobile-friendly forms, touch targets 44px+
  • Email Verification: Required for new accounts
  • Session Expiration: Reasonable timeout (7-30 days)
  • Secure Cookies: httpOnly, secure, sameSite='strict'
  • Rate Limiting: Prevent brute force attacks
  • Audit Logging: Log auth events for security monitoring

Common Patterns

Login Page with Google Auth

'use client';

import { useState } from 'react';
import { signIn } from '@/lib/auth/client';
import { useRouter } from 'next/navigation';

export default function LoginPage() {
  const router = useRouter();
  const [error, setError] = useState('');

  const handleGoogleSignIn = async () => {
    try {
      await signIn.social({
        provider: 'google',
        callbackURL: '/dashboard',
      });
    } catch (err) {
      setError('Failed to sign in with Google');
    }
  };

  const handleEmailSignIn = async (email: string, password: string) => {
    try {
      await signIn.email({
        email,
        password,
        callbackURL: '/dashboard',
      });
      router.push('/dashboard');
    } catch (err) {
      setError('Invalid email or password');
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
      <div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow-md">
        <h1 className="text-3xl font-bold text-center">Sign In</h1>

        {error && (
          <div className="p-3 bg-red-100 text-red-700 rounded-lg">
            {error}
          </div>
        )}

        <button
          onClick={handleGoogleSignIn}
          className="w-full flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50"
        >
          Sign in with Google
        </button>

        <div className="relative">
          <div className="absolute inset-0 flex items-center">
            <div className="w-full border-t border-gray-300" />
          </div>
          <div className="relative flex justify-center text-sm">
            <span className="px-2 bg-white text-gray-500">Or continue with email</span>
          </div>
        </div>

        <LoginForm onSubmit={handleEmailSignIn} />

        <p className="text-center text-sm text-gray-600">
          Don't have an account?{' '}
          <a href="/auth/signup" className="text-blue-600 hover:underline">
            Sign up
          </a>
        </p>
      </div>
    </div>
  );
}

Signup with Email Verification

'use client';

import { useState } from 'react';
import { signUp } from '@/lib/auth/client';

export default function SignupPage() {
  const [success, setSuccess] = useState(false);
  const [error, setError] = useState('');

  const handleSignup = async (data: SignupFormData) => {
    try {
      await signUp.email({
        email: data.email,
        password: data.password,
        name: data.name,
      });

      setSuccess(true);
    } catch (err) {
      setError('Failed to create account');
    }
  };

  if (success) {
    return (
      <div className="min-h-screen flex items-center justify-center">
        <div className="text-center">
          <h1 className="text-2xl font-bold mb-4">Check your email</h1>
          <p className="text-gray-600">
            We've sent a verification link to your email address
          </p>
        </div>
      </div>
    );
  }

  return <SignupForm onSubmit={handleSignup} />;
}

Protected Dashboard with Role Guard

import { withAuth } from '@/components/AuthGuard';
import { ROLES } from '@/lib/permissions';

function AdminDashboard() {
  const { user } = useAuth();

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold mb-6">Admin Dashboard</h1>
      <p>Welcome, {user?.name}</p>
    </div>
  );
}

export default withAuth(AdminDashboard, [ROLES.ADMIN]);

Password Strength Indicator

export const PasswordStrength = ({ password }: { password: string }) => {
  const getStrength = (pwd: string) => {
    let strength = 0;
    if (pwd.length >= 8) strength++;
    if (/[a-z]/.test(pwd)) strength++;
    if (/[A-Z]/.test(pwd)) strength++;
    if (/[0-9]/.test(pwd)) strength++;
    if (/[^a-zA-Z0-9]/.test(pwd)) strength++;
    return strength;
  };

  const strength = getStrength(password);
  const colors = ['bg-red-500', 'bg-orange-500', 'bg-yellow-500', 'bg-green-500', 'bg-green-600'];
  const labels = ['Weak', 'Fair', 'Good', 'Strong', 'Very Strong'];

  return (
    <div className="mt-2">
      <div className="flex gap-1">
        {[1, 2, 3, 4, 5].map((i) => (
          <div
            key={i}
            className={`h-2 flex-1 rounded ${i <= strength ? colors[strength - 1] : 'bg-gray-200'}`}
          />
        ))}
      </div>
      {password && (
        <p className={`text-sm mt-1 ${colors[strength - 1]?.replace('bg-', 'text-')}`}>
          Password strength: {labels[strength - 1]}
        </p>
      )}
    </div>
  );
};

Security Best Practices

OWASP Compliance

  1. CSRF Protection: Use built-in Better Auth CSRF tokens
  2. XSS Prevention: Sanitize user input, use React's auto-escaping
  3. SQL Injection: Use Prisma ORM (parameterized queries)
  4. Password Security: bcrypt/argon2 hashing, minimum 8 characters
  5. Session Security: httpOnly, secure, sameSite cookies

Rate Limiting

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, '10 s'), // 5 requests per 10 seconds
});

export async function checkRateLimit(identifier: string) {
  const { success } = await ratelimit.limit(identifier);
  return success;
}

Audit Logging

export async function logAuthEvent(event: {
  type: 'login' | 'logout' | 'signup' | 'password_reset';
  userId?: string;
  ip?: string;
  userAgent?: string;
}) {
  await prisma.authEvent.create({
    data: {
      ...event,
      timestamp: new Date(),
    },
  });
}

Environment Variables

# .env.local
AUTH_SECRET=your-super-secret-random-string
BETTER_AUTH_URL=http://localhost:3000

# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

# Database
DATABASE_URL=your-database-url

References

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

+5
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon