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
Markdown
# 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