UNPKG

authservice-nextjs

Version:

Next.js SDK for Auth Service - Server and client-side authentication with App Router support

512 lines (406 loc) 11.6 kB
# Auth Service Next.js SDK Complete authentication and authorization SDK for Next.js applications with support for both Pages Router and App Router. ## Features - 🚀 **Full Next.js Support**: Works with Pages Router and App Router - 🔐 **Server-Side Auth**: Secure authentication with getServerSideProps - 🛡️ **API Route Protection**: Middleware for protecting API endpoints - ⚡ **Edge Runtime Compatible**: Works with Next.js middleware - 🎨 **React Components**: Pre-built auth components - 📱 **Client-Side Hooks**: React hooks for auth state - 🔄 **Automatic Token Management**: Handle refresh tokens seamlessly - 🎯 **TypeScript First**: Full type safety ## Installation ```bash npm install @auth-service/nextjs-sdk ``` ## Quick Start ### 1. Create Auth Instance ```typescript // lib/auth.ts import { createAuth } from '@auth-service/nextjs-sdk'; export const auth = createAuth({ authServiceUrl: process.env.NEXT_PUBLIC_AUTH_SERVICE_URL!, appId: process.env.NEXT_PUBLIC_APP_ID!, appSecret: process.env.APP_SECRET!, // Server-side only // Optional configuration cookieName: 'auth-token', loginUrl: '/login', unauthorizedUrl: '/unauthorized' }); ``` ### 2. Add Provider (Pages Router) ```tsx // pages/_app.tsx import { NextAuthProvider } from '@auth-service/nextjs-sdk'; import { auth } from '../lib/auth'; function MyApp({ Component, pageProps }: AppProps) { return ( <NextAuthProvider config={auth.clientConfig} initialUser={pageProps.user} > <Component {...pageProps} /> </NextAuthProvider> ); } ``` ### 3. Protect Pages ```tsx // pages/dashboard.tsx import { auth } from '../lib/auth'; export const getServerSideProps = auth.getServerSideProps.withAuth(); export default function Dashboard({ user }) { return ( <div> <h1>Welcome {user.email}</h1> </div> ); } ``` ## Pages Router ### Protecting Pages #### Basic Authentication ```tsx // Require authentication export const getServerSideProps = auth.getServerSideProps.withAuth(); // With custom logic export const getServerSideProps = auth.getServerSideProps.withAuth( async (context) => { // context.user is available here const data = await fetchUserData(context.user.id); return { props: { data } }; } ); ``` #### Permission-Based Protection ```tsx // Single permission export const getServerSideProps = auth.getServerSideProps.withPermission('posts:create'); // Multiple permissions (ANY) export const getServerSideProps = auth.getServerSideProps.withAnyPermission([ 'posts:edit', 'posts:admin' ]); // Multiple permissions (ALL) export const getServerSideProps = auth.getServerSideProps.withAllPermissions([ 'admin:access', 'posts:delete' ]); ``` #### Check Auth Without Redirect ```tsx // Check authentication status without redirecting export const getServerSideProps = auth.getServerSideProps.checkAuth( async (context) => { // context.user might be null return { props: { isAuthenticated: !!context.user } }; } ); ``` ### Protecting API Routes #### Basic Authentication ```typescript // pages/api/profile.ts export default auth.withAuth(async (req, res) => { // req.user is available res.json({ user: req.user }); }); ``` #### Permission-Based Protection ```typescript // Single permission export default auth.withPermission('posts:create')( async (req, res) => { // User has permission const post = await createPost(req.body); res.json(post); } ); // Multiple permissions export default auth.withAnyPermission(['posts:edit', 'posts:admin'])( async (req, res) => { // User has at least one permission } ); ``` #### Composed Middleware ```typescript // Combine multiple middlewares const handler = async (req, res) => { res.json({ success: true }); }; export default auth.middleware.compose( auth.withAuth, auth.withPermission('admin:access') )(handler); ``` ## App Router ### Setup Middleware ```typescript // middleware.ts import { createAuthMiddleware, authMiddlewareConfig } from '@auth-service/nextjs-sdk'; export const middleware = createAuthMiddleware({ authServiceUrl: process.env.NEXT_PUBLIC_AUTH_SERVICE_URL!, appId: process.env.NEXT_PUBLIC_APP_ID!, cookieName: 'auth-token', loginUrl: '/login' }); export const config = authMiddlewareConfig({ protected: ['/dashboard', '/admin'], public: ['/login', '/signup'] }); ``` ### Server Components ```typescript // lib/auth-app.ts import { createAppRouterAuth } from '@auth-service/nextjs-sdk'; export const auth = createAppRouterAuth({ authServiceUrl: process.env.AUTH_SERVICE_URL!, appId: process.env.APP_ID!, appSecret: process.env.APP_SECRET! }); ``` #### Protected Pages ```tsx // app/dashboard/page.tsx import { auth } from '@/lib/auth-app'; export default async function Dashboard() { const user = await auth.requireAuth(); return ( <div> <h1>Welcome {user.id}</h1> </div> ); } ``` #### Permission-Based Pages ```tsx // app/admin/page.tsx import { auth } from '@/lib/auth-app'; export default async function AdminPage() { const user = await auth.requirePermission('admin:access'); return ( <div> <h1>Admin Dashboard</h1> </div> ); } ``` #### Conditional Rendering ```tsx // app/posts/page.tsx import { auth } from '@/lib/auth-app'; export default async function Posts() { const user = await auth.getCurrentUser(); const canCreate = user ? await auth.hasPermission('posts:create') : false; return ( <div> {canCreate && ( <button>Create Post</button> )} </div> ); } ``` ### Server Actions ```typescript // app/actions/posts.ts 'use server'; import { auth } from '@/lib/auth-app'; // Protected server action export const createPost = auth.withPermission('posts:create', async (user, data: FormData) => { const title = data.get('title'); // User is authenticated and has permission const post = await db.post.create({ data: { title, authorId: user.id } }); return post; } ); ``` ## Client-Side Components ### Permission Guard ```tsx import { PermissionGuard } from '@auth-service/nextjs-sdk'; export function PostActions({ postId }) { return ( <> <PermissionGuard permission="posts:edit"> <button>Edit Post</button> </PermissionGuard> <PermissionGuard permission="posts:delete" fallback={<span>No delete permission</span>} > <button>Delete Post</button> </PermissionGuard> </> ); } ``` ### Authentication Components ```tsx import { RequireAuth, AuthOnly, GuestOnly, UserInfo } from '@auth-service/nextjs-sdk'; export function Header() { return ( <header> <GuestOnly> <Link href="/login">Login</Link> </GuestOnly> <AuthOnly> <UserInfo showEmail showRoles /> <Link href="/logout">Logout</Link> </AuthOnly> </header> ); } // Protect entire page export function ProtectedPage() { return ( <RequireAuth redirectTo="/login"> <div>Protected content</div> </RequireAuth> ); } ``` ### Hooks ```tsx import { useNextAuth } from '@auth-service/nextjs-sdk'; export function UserProfile() { const { user, loading, error, hasPermission, hasRole, checkPermission, refreshPermissions } = useNextAuth(); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; if (!user) return <div>Not authenticated</div>; return ( <div> <h1>{user.email}</h1> {hasPermission('posts:create') && ( <button>Create Post</button> )} {hasRole('admin') && ( <Link href="/admin">Admin Panel</Link> )} </div> ); } ``` ## Advanced Usage ### Custom Token Extraction ```typescript // For API routes with custom token location const handler = async (req, res) => { const token = req.headers['x-custom-token']; // Manual verification const user = await auth.server.getUserFromToken(token); res.json({ user }); }; ``` ### Session Management ```typescript // Set custom auth cookie auth.setAuthCookie(res, token, maxAge); // Clear auth cookie (logout) auth.clearAuthCookie(res); ``` ### Error Handling ```typescript import { AuthServiceError, TokenError, PermissionError } from '@auth-service/nextjs-sdk'; // API route error handling export default auth.withAuth(async (req, res) => { try { // Your logic } catch (error) { if (error instanceof TokenError) { res.status(401).json({ error: 'Invalid token' }); } else if (error instanceof PermissionError) { res.status(403).json({ error: 'Permission denied' }); } else { res.status(500).json({ error: 'Internal error' }); } } }); ``` ## Configuration Options ```typescript const auth = createAuth({ // Required authServiceUrl: 'https://auth.example.com', appId: 'your-app-id', appSecret: 'your-app-secret', // Cookie options cookieName: 'auth-token', // default: 'auth-token' cookieDomain: '.example.com', // optional cookieSecure: true, // default: true in production cookieHttpOnly: true, // default: true cookieSameSite: 'lax', // default: 'lax' cookiePath: '/', // default: '/' // Redirect options loginUrl: '/login', // default: '/login' unauthorizedUrl: '/unauthorized', // default: '/unauthorized' redirectOnError: true, // default: true // Backend SDK options (inherited) cacheEnabled: true, // default: true cacheTTL: 60, // default: 60 seconds timeout: 10000, // default: 10000ms retryAttempts: 3, // default: 3 }); ``` ## Best Practices 1. **Environment Variables**: Keep `appSecret` server-side only 2. **Error Boundaries**: Wrap auth components in error boundaries 3. **Loading States**: Always handle loading states in client components 4. **Permission Naming**: Use consistent `resource:action` format 5. **Cache Wisely**: Balance performance with data freshness 6. **Type Safety**: Leverage TypeScript for better DX ## Migration Guide ### From Pages to App Router ```typescript // Pages Router export const getServerSideProps = auth.getServerSideProps.withAuth(); // App Router const user = await auth.requireAuth(); ``` ### From Client-Side to Server-Side ```typescript // Client-side check const { hasPermission } = useNextAuth(); if (hasPermission('posts:create')) { } // Server-side check const canCreate = await auth.hasPermission('posts:create'); if (canCreate) { } ``` ## TypeScript Support Full TypeScript support with type inference: ```typescript import { NextApiRequestWithAuth } from '@auth-service/nextjs-sdk'; export default auth.withAuth(async (req: NextApiRequestWithAuth, res) => { // req.user is fully typed const userId = req.user.id; const permissions = req.user.permissions; }); ``` ## License MIT