Authentication and Authorization with Clerk
This document outlines the authentication and authorization flow using Clerk, supporting multi-tenancy with organizations.
Overview
The system leverages Clerk for robust user authentication and organization management. This approach provides:
- Multi-factor authentication
- Social logins
- Organization-level access control
- Role-based permissions within organizations
- Seamless organization switching
Authentication Flow
The following diagram illustrates the authentication process:
Key Components and Concepts
1. ClerkProvider
Wraps the application to provide authentication context to all components.
2. Middleware (middleware.ts)
Clerk's authMiddleware protects routes and manages session validation. It can be configured with publicRoutes and ignoredRoutes.
// middleware.ts
import { authMiddleware } from "@clerk/nextjs";
export default authMiddleware({
publicRoutes: ["/", "/sign-in(.*)", "/sign-up(.*)", "/api/webhook(.*)"],
// ignoredRoutes: ["/api/some-public-api"],
});
export const config = {
matcher: ['/((?!.+\.[\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
};
3. Authentication Hooks and Functions
auth(): Server-side function to get authentication state (userId,orgId, etc.).useAuth(): Client-side hook for accessing authentication state.currentUser(): Server-side function to get the full user object.clerkClient: Server-side client for interacting with the Clerk API (e.g., fetching organization memberships, user details).
4. Organization Management
- Organization ID (
orgId): Crucial for multi-tenancy. It's available fromauth()and used to scope data access and UI. <OrganizationSwitcher />: UI component allowing users to switch between organizations they belong to.- Organization-Specific Routes: Routes like
app/dashboard/[orgId]/page.tsxuse theorgIdfrom the URL and validate it against the user's currentorgIdfrom Clerk.
5. Database Interaction
- Our application database stores company-specific data.
- Clerk manages user and organization core data (name, members, roles).
- A
clientRef(or similar identifier) in Clerk's organization metadata links to our internal company records in our database. - Services like
getCompanyByOrgId(orgId)will first useclerkClient.organizations.getOrganization({ organizationId: orgId })to fetch Clerk org data, extractclientReffrompublicMetadata, and then query our database.
Example: Protected Page with Organization Context
// app/dashboard/[orgId]/page.tsx
import { auth } from "@clerk/nextjs";
import { redirect } from "next/navigation";
// Import your service to get organization-specific data
// import { getOrganizationData } from "@/lib/services/organizationService";
export default async function DashboardPage({
params: { orgId },
}: {
params: { orgId: string };
}) {
const { userId, orgId: currentOrgId } = auth();
if (!userId) {
redirect("/sign-in"); // Or your sign-in page
}
// Ensure the user is accessing an organization they are part of
// and that the route's orgId matches their active organization
if (!currentOrgId || orgId !== currentOrgId) {
// Redirect to an organization selection page or a default page
redirect("/");
}
// Fetch organization-specific data using the validated orgId
// const data = await getOrganizationData(orgId);
return (
<div>
<h1>Dashboard for Organization: {orgId}</h1>
{/* Render organization-specific content */}
</div>
);
}
Environment Setup
Clerk requires environment variables for its publishable key and secret key:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYCLERK_SECRET_KEY
This updated authentication architecture provides a more scalable and secure solution for managing users and their access to multiple organizations.