Authentication Configure NextAuth.js v5 with OAuth providers and JWT sessions
Datyze uses NextAuth.js v5 for authentication with the DrizzleAdapter for database persistence. Sessions are JWT-based for optimal performance with self-hosted deployments.
Authentication is configured in the @lucashochart/auth package:
packages/auth/
├── src/
│ ├── config.ts # NextAuth.js configuration
│ ├── session.ts # Session helpers
│ └── index.ts # Package exports
Create a project in Google Cloud Console
Enable the Google+ API
Create OAuth 2.0 credentials
Add authorized redirect URI: https://yourdomain.com/api/auth/callback/google
GOOGLE_CLIENT_ID = "your-client-id"
GOOGLE_CLIENT_SECRET = "your-client-secret"
Go to GitHub Developer Settings
Create a new OAuth App
Set callback URL: https://yourdomain.com/api/auth/callback/github
GITHUB_CLIENT_ID = "your-client-id"
GITHUB_CLIENT_SECRET = "your-client-secret"
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 > ;
}
The session contains:
interface Session {
user : {
id : string ;
name : string | null ;
email : string ;
image : string | null ;
isAdmin ?: boolean ;
emailVerified ?: Date | null ;
};
}
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 > ;
}
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 });
}
Datyze includes an admin impersonation feature for debugging:
// POST /api/admin/impersonate
const response = await fetch ( "/api/admin/impersonate" , {
method: "POST" ,
body: JSON . stringify ({ userId: "user-to-impersonate" }),
});
const user = await getCurrentUser ();
if (user?._impersonation?.isImpersonating) {
console. log ( "Currently impersonating" );
console. log ( "Admin ID:" , user._impersonation.adminId);
}
The ImpersonationBanner component displays when impersonating:
import { ImpersonationBanner } from "@/components/admin/impersonation-banner" ;
// In your layout
< ImpersonationBanner />
Authentication uses these tables:
Table Description userUser accounts accountOAuth provider connections sessionActive sessions verificationTokenEmail verification tokens
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
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.