feat: add oidc support with authentik

This commit is contained in:
2026-01-14 02:33:20 -05:00
parent 180f14dfb0
commit 302f3cb3f5
25 changed files with 252 additions and 712 deletions

View File

@@ -1,5 +1,59 @@
import { createAuthClient } from "better-auth/react";
"use client";
export const authClient = createAuthClient({
import { createAuthClient } from "better-auth/react";
import { ssoClient } from "@better-auth/sso/client";
/**
* Auth client for better-auth with SSO support.
*
* Uses a Proxy pattern to ensure the client is only created in the browser.
* This prevents SSR/build-time errors while maintaining full type safety.
*/
// Create a typed client reference for type inference
const _createClient = () => createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL,
plugins: [ssoClient()],
});
// Type for the full client including plugins
type AuthClientType = ReturnType<typeof _createClient>;
// Lazy initialization - only create the client when first accessed
let _client: AuthClientType | undefined;
function getClient(): AuthClientType {
if (typeof window === "undefined") {
// During SSR, return a safe mock that won't crash
// The actual client will only be used in the browser
// @ts-ignore - SSR mock doesn't need full client implementation
return {
// @ts-ignore
useSession: () => ({ data: null, isPending: false, error: null }),
// @ts-ignore
signIn: { email: async () => { }, social: async () => { }, sso: async () => { } },
// @ts-ignore
signOut: async () => { },
// @ts-ignore
signUp: { email: async () => { } },
};
}
if (!_client) {
_client = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL,
plugins: [ssoClient()],
});
}
return _client;
}
// Export a Proxy that lazy-loads the client
export const authClient = new Proxy({} as AuthClientType, {
get(_target, prop) {
const client = getClient();
const value = client[prop as keyof AuthClientType];
return typeof value === "function" ? value.bind(client) : value;
},
});

View File

@@ -1,6 +1,7 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { nextCookies } from "better-auth/next-js";
import { sso } from "@better-auth/sso";
import { db } from "~/server/db";
import * as schema from "~/server/db/schema";
@@ -28,5 +29,28 @@ export const auth = betterAuth({
},
},
},
plugins: [nextCookies()],
plugins: [
nextCookies(),
sso({
// Only configure default SSO if Authentik credentials are provided
defaultSSO:
process.env.AUTHENTIK_ISSUER &&
process.env.AUTHENTIK_CLIENT_ID &&
process.env.AUTHENTIK_CLIENT_SECRET
? [
{
providerId: "authentik",
domain: "beenvoice.soconnor.dev",
oidcConfig: {
issuer: process.env.AUTHENTIK_ISSUER,
clientId: process.env.AUTHENTIK_CLIENT_ID,
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET,
discoveryEndpoint: `${process.env.AUTHENTIK_ISSUER}/.well-known/openid-configuration`,
pkce: true,
},
},
]
: [],
}),
],
});