feat: add Hanzo IAM OIDC SSO provider

- Add HanzoIAMProvider to NextAuth config (OIDC with userinfo endpoint)
- Add "Continue with Hanzo" button to login page (first SSO option)
- Env vars: HANZO_IAM_URL, HANZO_IAM_CLIENT_ID, HANZO_IAM_CLIENT_SECRET
- IAM app registered as app-dataroom in init_data.json
This commit is contained in:
Hanzo Dev
2026-03-01 22:36:44 -08:00
parent 2e30946472
commit 53c72672ca
2 changed files with 58 additions and 1 deletions
+25 -1
View File
@@ -32,7 +32,7 @@ export default function Login() {
const isSSORequired = authError === "require-saml-sso";
const [lastUsed, setLastUsed] = useLastUsed();
const authMethods = ["google", "email", "linkedin", "passkey"] as const;
const authMethods = ["hanzo", "google", "email", "linkedin", "passkey"] as const;
type AuthMethod = (typeof authMethods)[number];
const [clickedMethod, setClickedMethod] = useState<AuthMethod | undefined>(
undefined,
@@ -166,6 +166,30 @@ export default function Login() {
</form>
<p className="py-4 text-center">or</p>
<div className="flex flex-col space-y-2 px-4 sm:px-12">
<div className="relative">
<Button
onClick={() => {
setClickedMethod("hanzo");
setLastUsed("hanzo");
signIn("hanzo-iam", {
...(next && next.length > 0 ? { callbackUrl: next } : {}),
}).then(() => {
setClickedMethod(undefined);
});
}}
loading={clickedMethod === "hanzo"}
disabled={clickedMethod && clickedMethod !== "hanzo"}
className="flex w-full items-center justify-center space-x-2 border border-red-500 bg-red-500 font-normal text-white hover:bg-red-600"
>
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span>Continue with Hanzo</span>
{clickedMethod !== "hanzo" && lastUsed === "hanzo" && (
<LastUsed />
)}
</Button>
</div>
<div className="relative">
<Button
onClick={() => {
+33
View File
@@ -9,6 +9,7 @@ import CredentialsProvider from "next-auth/providers/credentials";
import EmailProvider from "next-auth/providers/email";
import GoogleProvider from "next-auth/providers/google";
import LinkedInProvider from "next-auth/providers/linkedin";
import type { OAuthConfig } from "next-auth/providers/oauth";
import { identifyUser, trackAnalytics } from "@/lib/analytics";
import { qstash } from "@/lib/cron";
@@ -24,6 +25,37 @@ import { getIpAddress } from "@/lib/utils/ip";
const VERCEL_DEPLOYMENT = !!process.env.VERCEL_URL;
const HANZO_IAM_URL = process.env.HANZO_IAM_URL;
const HANZO_IAM_CLIENT_ID = process.env.HANZO_IAM_CLIENT_ID;
const HANZO_IAM_CLIENT_SECRET = process.env.HANZO_IAM_CLIENT_SECRET;
function HanzoIAMProvider(): OAuthConfig<any> {
const issuer = HANZO_IAM_URL || "https://hanzo.id";
return {
id: "hanzo-iam",
name: process.env.HANZO_IAM_PROVIDER_NAME || "Hanzo",
type: "oauth",
wellKnown: `${issuer}/.well-known/openid-configuration`,
clientId: HANZO_IAM_CLIENT_ID || "",
clientSecret: HANZO_IAM_CLIENT_SECRET || "",
authorization: { params: { scope: "openid profile email" } },
idToken: false,
userinfo: { url: `${issuer}/oauth/userinfo` },
profile(profile) {
return {
id: profile.sub,
name:
profile.displayName ||
profile.name ||
profile.preferred_username,
email: profile.email,
image: profile.avatar || profile.picture,
};
},
allowDangerousEmailAccountLinking: true,
};
}
function getMainDomainUrl(): string {
if (process.env.NODE_ENV === "development") {
return process.env.NEXTAUTH_URL || "http://localhost:3000";
@@ -41,6 +73,7 @@ export const authOptions: NextAuthOptions = {
error: "/login",
},
providers: [
...(HANZO_IAM_CLIENT_ID ? [HanzoIAMProvider()] : []),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,