Authorization
Role-based access control and permission system
Overview
Authorization determines what authenticated users can do in your application. While authentication verifies identity (who you are), authorization controls access (what you can do).
This template provides a comprehensive authorization system with:
- Role-Based Access Control (RBAC) - User roles with different privilege levels
- Permission System - Fine-grained access control for organizations
- Resource Ownership - Users can only access their own resources
- Row-Level Security - Database-level access policies
Authentication vs Authorization
| Aspect | Authentication | Authorization |
|---|---|---|
| Purpose | Verify identity | Control access |
| Question | Who are you? | What can you do? |
| Implementation | Login, sessions | Roles, permissions |
| Example | Email/password login | Admin can delete users |
Quick Start
Server-Side Authorization
import { requireRole } from "@/lib/auth/layout-guards";
import { UserRole } from "@/db/enums";
// In a layout or page
export default async function AdminLayout() {
// Requires ADMIN role, redirects if unauthorized
const auth = await requireRole(UserRole.ADMIN);
return <div>Admin content for {auth.user.email}</div>;
}tRPC Authorization
import { roleProcedure } from "@/trpc/procedures/trpc";
import { UserRole } from "@/db/enums";
// Only admins can call this procedure
deleteUser: roleProcedure(UserRole.ADMIN)
.input(z.object({ userId: z.string() }))
.mutation(async ({ ctx, input }) => {
// ctx.user is guaranteed to be an admin
await ctx.db.users.deleteUser({ id: input.userId });
return { success: true };
})Client-Side Role Checks
"use client";
import { useAuth } from "@/lib/auth/client";
import { UserRole } from "@/db/enums";
export function AdminButton() {
const { user, hasRole } = useAuth();
if (!hasRole(UserRole.ADMIN)) return null;
return <button>Admin Action</button>;
}Authorization Levels
This template implements authorization at multiple layers:
1. Route Level
Entire pages/layouts protected by role requirements:
// app/[locale]/admin/layout.tsx
await requireRole(UserRole.ADMIN);2. API Level
tRPC procedures protected by role or authentication:
// authProcedure - requires any authenticated user
// roleProcedure - requires specific role(s)3. Component Level
UI elements conditionally rendered based on roles:
{hasRole(UserRole.ADMIN) && <AdminPanel />}4. Database Level
Row-Level Security (RLS) policies enforce access at the database:
// Users can only read their own data
.enableRLS()
.withPolicy("select", { to: authenticatedRole, using: isOwner(users.id) })Core Concepts
User Roles
System-wide roles defined in UserRole enum:
ADMIN- Full system accessUSER- Standard user access
See Roles documentation for details.
Organization Permissions
Fine-grained permissions within organizations:
owner- Full organization accessadmin- Can manage members and settingsviewer- Read-only access
See Permissions documentation for details.
Resource Ownership
Users have implicit access to resources they own:
import { isOwner } from "@/db/rls";
// Check if current user owns this resource
.withPolicy("update", {
to: authenticatedRole,
using: isOwner(tasks.ownerId)
})Architecture
src/lib/auth/
├── layout-guards.ts # requireRole(), requireUnauthenticated()
└── permissions.ts # Access control definitions
src/trpc/procedures/
└── trpc.ts # authProcedure, roleProcedure
src/db/
└── rls.ts # Row-Level Security helpers
src/db/
└── enums.ts # UserRole enumBest Practices
Defense in Depth - Implement authorization checks at multiple layers (route, API, database) for maximum security.
Never Trust Client-Side - Always validate permissions server-side. Client-side checks are for UX only.
Fail Securely - Default to denying access. Explicitly grant permissions rather than explicitly denying them.