authservice-nextjs
v1.0.0
Published
Next.js SDK for Auth Service - Server and client-side authentication with App Router support
Downloads
32
Maintainers
Readme
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
npm install @auth-service/nextjs-sdkQuick Start
1. Create Auth Instance
// 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)
// 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
// 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
// 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
// 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
// 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
// pages/api/profile.ts
export default auth.withAuth(async (req, res) => {
// req.user is available
res.json({ user: req.user });
});Permission-Based Protection
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
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
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
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
// 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
// Set custom auth cookie
auth.setAuthCookie(res, token, maxAge);
// Clear auth cookie (logout)
auth.clearAuthCookie(res);Error Handling
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
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
- Environment Variables: Keep
appSecretserver-side only - Error Boundaries: Wrap auth components in error boundaries
- Loading States: Always handle loading states in client components
- Permission Naming: Use consistent
resource:actionformat - Cache Wisely: Balance performance with data freshness
- Type Safety: Leverage TypeScript for better DX
Migration Guide
From Pages to App Router
// Pages Router
export const getServerSideProps = auth.getServerSideProps.withAuth();
// App Router
const user = await auth.requireAuth();From Client-Side to Server-Side
// 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:
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
