diff --git a/console/.env.example b/console/.env.example index 23acbfb3..82cdcf0f 100644 --- a/console/.env.example +++ b/console/.env.example @@ -19,5 +19,10 @@ VITE_POLARIS_PRINCIPAL_SCOPE=PRINCIPAL_ROLE:ALL # VITE_OIDC_REDIRECT_URI=http://localhost:3000/auth/callback # VITE_OIDC_SCOPE=openid profile email +# Comma-separated list of JWT claims used to resolve the Polaris principal name, +# in order of priority. Defaults to "sub,principal,principal_name,name". +# For Entra ID / Azure AD, use: preferred_username,email,sub,name +# VITE_OIDC_PRINCIPAL_CLAIMS=sub,principal,principal_name,name + # Docker Configuration PORT=3000 diff --git a/console/docker/generate-config.sh b/console/docker/generate-config.sh index 3f311dd4..f8bed871 100644 --- a/console/docker/generate-config.sh +++ b/console/docker/generate-config.sh @@ -33,7 +33,8 @@ window.APP_CONFIG = { VITE_OIDC_ISSUER_URL: '${VITE_OIDC_ISSUER_URL}', VITE_OIDC_CLIENT_ID: '${VITE_OIDC_CLIENT_ID}', VITE_OIDC_REDIRECT_URI: '${VITE_OIDC_REDIRECT_URI}', - VITE_OIDC_SCOPE: '${VITE_OIDC_SCOPE}' + VITE_OIDC_SCOPE: '${VITE_OIDC_SCOPE}', + VITE_OIDC_PRINCIPAL_CLAIMS: '${VITE_OIDC_PRINCIPAL_CLAIMS}' }; EOF diff --git a/console/src/lib/config.ts b/console/src/lib/config.ts index df866378..ab570c4f 100644 --- a/console/src/lib/config.ts +++ b/console/src/lib/config.ts @@ -27,6 +27,7 @@ interface AppConfig { VITE_OIDC_CLIENT_ID?: string VITE_OIDC_REDIRECT_URI?: string VITE_OIDC_SCOPE?: string + VITE_OIDC_PRINCIPAL_CLAIMS?: string } declare global { @@ -59,4 +60,8 @@ export const config = { OIDC_CLIENT_ID: getConfig("VITE_OIDC_CLIENT_ID", ""), OIDC_REDIRECT_URI: getConfig("VITE_OIDC_REDIRECT_URI", ""), OIDC_SCOPE: getConfig("VITE_OIDC_SCOPE", "openid profile email"), + OIDC_PRINCIPAL_CLAIMS: getConfig( + "VITE_OIDC_PRINCIPAL_CLAIMS", + "sub,principal,principal_name,name", + ), } diff --git a/console/src/lib/utils.ts b/console/src/lib/utils.ts index 45481254..443260a1 100644 --- a/console/src/lib/utils.ts +++ b/console/src/lib/utils.ts @@ -19,6 +19,7 @@ import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" +import { config } from "@/lib/config" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) @@ -45,8 +46,14 @@ export function decodeJWT(token: string): Record | null { } /** - * Extracts the principal name from a JWT token - * The principal name is typically in the 'sub' (subject) claim + * Extracts the principal name from a JWT token. + * The claims to inspect and their priority order are controlled by the + * VITE_OIDC_PRINCIPAL_CLAIMS environment variable (comma-separated list). + * Defaults to "sub,principal,principal_name,name". + * + * Example for Entra ID / Azure AD: + * VITE_OIDC_PRINCIPAL_CLAIMS=preferred_username,email,sub,name + * * @param token - The JWT token string * @returns The principal name or null if not found */ @@ -55,12 +62,14 @@ export function getPrincipalNameFromToken(token: string): string | null { if (!decoded) { return null } - // Try common JWT claim names for the principal/subject - return ( - (decoded.sub as string) || - (decoded.principal as string) || - (decoded.principal_name as string) || - (decoded.name as string) || - null - ) + const claims = config.OIDC_PRINCIPAL_CLAIMS.split(",") + .map((c) => c.trim()) + .filter(Boolean) + for (const claim of claims) { + const value = decoded[claim] + if (typeof value === "string" && value) { + return value + } + } + return null }