All docs/Authentication

docs/architecture/authentication.md

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 from auth() 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.tsx use the orgId from the URL and validate it against the user's current orgId from 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 use clerkClient.organizations.getOrganization({ organizationId: orgId }) to fetch Clerk org data, extract clientRef from publicMetadata, 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_KEY
  • CLERK_SECRET_KEY

This updated authentication architecture provides a more scalable and secure solution for managing users and their access to multiple organizations.