Usage Guide

A comprehensive guide to using NextRoleManager for role-based access control in next.js applications.

Introduction

The NextRoleManager is a powerful tool for implementing role-based access control (RBAC) in Next.js applications. It integrates seamlessly with the @iamjs/core package to provide robust authorization capabilities. This guide will walk you through setting up, configuring, and using the NextRoleManager in various scenarios.

Basic Setup

Here's a basic setup to get you started with @iamjs/next:

import { Role, Schema } from '@iamjs/core';
import { NextRoleManager } from '@iamjs/next';
import { NextApiRequest, NextApiResponse } from 'next';
 
// Define roles
const userRole = new Role({
  name: 'user',
  config: {
    posts: {
      base: 'cr--l',
      custom: {
        like: true,
        comment: true
      }
    },
    profile: {
      base: 'crud-',
      custom: {
        changePassword: true
      }
    }
  }
});
 
// Create schema
const schema = new Schema({
  roles: { user: userRole }
});
 
// Initialize NextRoleManager
const roleManager = new NextRoleManager({
  schema,
  onSuccess: (req: NextApiRequest, res: NextApiResponse) => {
    res.status(200).json({ message: 'Access granted' });
  },
  onError: (err: Error, req: NextApiRequest, res: NextApiResponse) => {
    console.error('Authorization error:', err);
    res.status(403).json({ error: 'Access denied' });
  }
});
 
// Example API route
export default roleManager.check(
  (req: NextApiRequest, res: NextApiResponse) => {
    res.status(200).json({ message: 'Protected resource accessed successfully' });
  },
  {
    resources: 'posts',
    actions: ['read', 'list'],
    role: 'user'
  }
);

This setup demonstrates how to define roles, create a schema, initialize the NextRoleManager, and use it in a Next.js API route.

Advanced Usage

For more complex scenarios, you might need to dynamically determine the role or perform additional checks:

import { Role, Schema } from '@iamjs/core';
import { NextRoleManager } from '@iamjs/next';
import { NextApiRequest, NextApiResponse } from 'next';
 
// ... (previous role and schema setup)
 
const roleManager = new NextRoleManager({ schema });
 
// Middleware to fetch user permissions
const withAuth = (handler) => async (req: NextApiRequest, res: NextApiResponse) => {
  // In a real app, you'd fetch this from a database or authentication service
  req.user = { id: 1, role: 'user' };
  req.permissions = userRole.toObject();
  return handler(req, res);
};
 
const handler = roleManager.check(
  (req: NextApiRequest, res: NextApiResponse) => {
    res.status(200).json({ message: 'Post created successfully' });
  },
  {
    resources: 'posts',
    actions: ['create'],
    strict: true,
    construct: true,
    data: async (req) => req.permissions
  }
);
 
export default withAuth(handler);

This advanced example shows how to use middleware for authentication and fetching user permissions, and how to use the construct and data options for dynamic role checking.

Success and Error Handling

Customize success and error handling to tailor the behavior of your application:

const roleManager = new NextRoleManager({
  schema,
  onSuccess: (req: NextApiRequest, res: NextApiResponse) => {
    console.log('Authorization successful');
    res.setHeader('X-Auth-Status', 'success');
    res.status(200).json({ message: 'Access granted' });
  },
  onError: (err: Error, req: NextApiRequest, res: NextApiResponse) => {
    console.error('Authorization error:', err);
    res.status(403).json({
      error: 'Access denied',
      details: err.message
    });
  }
});

Logging User Activity

Use the onActivity handler to log or process user activities:

const roleManager = new NextRoleManager({
  schema,
  onSuccess: (req, res) => { /* ... */ },
  onError: (err, req, res) => { /* ... */ },
  async onActivity(data) {
    console.log('User activity:', {
      timestamp: new Date(),
      userId: data.req?.user?.id,
      role: data.role,
      resources: data.resources,
      actions: data.actions,
      success: data.success
    });
    // In a real app, you might save this to a database or send to a logging service
  }
});

TypeScript Support

@iamjs/next provides full TypeScript support. You can create custom interfaces to extend the Next.js request and response types:

import { NextApiRequest, NextApiResponse } from 'next';
import { Role, Schema } from '@iamjs/core';
import { NextRoleManager } from '@iamjs/next';
 
interface CustomRequest extends NextApiRequest {
  user?: { id: number; role: string };
  permissions?: any;
}
 
interface CustomResponse extends NextApiResponse {}
 
const roleManager = new NextRoleManager({
  schema,
  onSuccess: (req: CustomRequest, res: CustomResponse) => {
    console.log('Authorized user:', req.user?.id);
    res.status(200).json({ message: 'Access granted' });
  },
  onError: (err: Error, req: CustomRequest, res: CustomResponse) => {
    console.error('Authorization failed for user:', req.user?.id);
    res.status(403).json({ error: 'Access denied' });
  },
});
 
const handler = roleManager.check<CustomRequest, CustomResponse>(
  (req, res) => {
    res.status(200).json({ message: 'Protected resource accessed', userId: req.user?.id });
  },
  {
    resources: 'protectedResource',
    actions: ['read'],
    construct: true,
    data: async (req) => req.permissions
  }
);
 
export default handler;

Usage with Next.js 13 App Router

For Next.js 13's App Router, which uses the Web Request and Response APIs, you can use the checkFn method:

import { NextResponse } from 'next/server';
import { roleManager } from '@/lib/roleManager';
import { getUserPermissions } from '@/lib/auth';
 
export async function GET(request: Request) {
  const authorized = await roleManager.checkFn({
    resources: 'posts',
    actions: ['read'],
    strict: true,
    construct: true,
    data: await getUserPermissions(request);
  });
 
  if (!authorized) {
    return new NextResponse('Unauthorized', { status: 401 });
  }
 
  // Fetch and return posts
  const posts = await fetchPosts();
  return NextResponse.json(posts);
}

Best Practices and Tips

  1. Granular Permissions: Define roles with specific, granular permissions for flexible access control.

  2. Dynamic Role Assignment: Utilize the construct and data options for dynamic role assignment based on user data.

  3. Error Handling: Provide clear error messages in your onError handler for better debugging and user experience.

  4. Logging: Use the onActivity handler for comprehensive audit trails of authorization attempts.

  5. Performance: Consider caching role data or authorization results for improved performance with complex permission structures.

  6. Security: Always validate and sanitize user input, especially when constructing roles dynamically.

  7. Testing: Implement thorough unit and integration tests for your authorization logic.

  8. Separation of Concerns: Keep your authorization logic separate from your business logic for better maintainability.