Protokit

Authentication

Configure NextAuth.js v5 with OAuth providers and JWT sessions

Authentication

Datyze uses NextAuth.js v5 for authentication with the DrizzleAdapter for database persistence. Sessions are JWT-based for optimal performance with self-hosted deployments.

Configuration

Authentication is configured in the @lucashochart/auth package:

packages/auth/
├── src/
│   ├── config.ts      # NextAuth.js configuration
│   ├── session.ts     # Session helpers
│   └── index.ts       # Package exports

OAuth Providers

Google

  1. Create a project in Google Cloud Console
  2. Enable the Google+ API
  3. Create OAuth 2.0 credentials
  4. Add authorized redirect URI: https://yourdomain.com/api/auth/callback/google
GOOGLE_CLIENT_ID="your-client-id"
GOOGLE_CLIENT_SECRET="your-client-secret"

GitHub

  1. Go to GitHub Developer Settings
  2. Create a new OAuth App
  3. Set callback URL: https://yourdomain.com/api/auth/callback/github
GITHUB_CLIENT_ID="your-client-id"
GITHUB_CLIENT_SECRET="your-client-secret"

Session Management

Getting the Current User

Use the getCurrentUser() helper in Server Components:

import { getCurrentUser } from "@lucashochart/auth/session";
 
export default async function DashboardPage() {
  const user = await getCurrentUser();
 
  if (!user) {
    redirect("/login");
  }
 
  return <div>Welcome, {user.name}!</div>;
}

Session Object

The session contains:

interface Session {
  user: {
    id: string;
    name: string | null;
    email: string;
    image: string | null;
    isAdmin?: boolean;
    emailVerified?: Date | null;
  };
}

Protected Routes

Server Components

import { getCurrentUser } from "@lucashochart/auth/session";
import { redirect } from "next/navigation";
 
export default async function ProtectedPage() {
  const user = await getCurrentUser();
 
  if (!user) {
    redirect("/login");
  }
 
  // User is authenticated
  return <div>Protected content</div>;
}

API Routes

import { getCurrentUser } from "@lucashochart/auth/session";
 
export async function GET() {
  const user = await getCurrentUser();
 
  if (!user) {
    return new Response("Unauthorized", { status: 401 });
  }
 
  return Response.json({ user });
}

Admin Impersonation

Datyze includes an admin impersonation feature for debugging:

Starting Impersonation

// POST /api/admin/impersonate
const response = await fetch("/api/admin/impersonate", {
  method: "POST",
  body: JSON.stringify({ userId: "user-to-impersonate" }),
});

Detecting Impersonation

const user = await getCurrentUser();
 
if (user?._impersonation?.isImpersonating) {
  console.log("Currently impersonating");
  console.log("Admin ID:", user._impersonation.adminId);
}

Impersonation Banner

The ImpersonationBanner component displays when impersonating:

import { ImpersonationBanner } from "@/components/admin/impersonation-banner";
 
// In your layout
<ImpersonationBanner />

Database Tables

Authentication uses these tables:

TableDescription
userUser accounts
accountOAuth provider connections
sessionActive sessions
verificationTokenEmail verification tokens

Environment Variables

Required authentication variables:

# Required
AUTH_SECRET="min-32-character-secret"
AUTH_URL="https://yourdomain.com"
 
# OAuth (optional)
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET=""
Generate AUTH_SECRET: openssl rand -base64 32

Adding New Providers

Edit packages/auth/src/config.ts:

import DiscordProvider from "next-auth/providers/discord";
 
export const authConfig = {
  providers: [
    // Existing providers...
    DiscordProvider({
      clientId: env.DISCORD_CLIENT_ID,
      clientSecret: env.DISCORD_CLIENT_SECRET,
    }),
  ],
};

Don't forget to add the environment variables to apps/web/src/env.ts.

Next Steps