skills/awais68/auth-integration/SKILL.md
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".
npx skillsauth add aiskillstore/marketplace auth-integrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
This skill triggers when users request:
// 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:
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:
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:
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:
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:
Auth Configuration:
lib/auth.ts - Better Auth setupapp/auth/[...auth]/route.ts - Auth API routesForms:
app/auth/login/page.tsx - Login formapp/auth/signup/page.tsx - Signup formapp/auth/forgot-password/page.tsx - Password resetSession Management:
lib/auth-store.ts - Zustand auth storehooks/useAuth.ts - Auth hookProtected Routes:
middleware.ts - Route protectioncomponents/AuthGuard.tsx - Auth guard HOCAnalyze Auth Requirements
Setup Auth Provider
Create Auth Forms
Implement Session Management
Protect Routes
Validate Security
Before completing any auth implementation:
'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>
);
}
'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} />;
}
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]);
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>
);
};
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;
}
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(),
},
});
}
# .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
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.