Skip to main content
This guide walks through setting up Azure AD authentication for Genesis deployed on Azure Kubernetes Service (AKS) using native OIDC authentication — no OAuth2 Proxy required.

Overview

Genesis supports native OIDC authentication using Authorization Code Flow + PKCE. This approach:
  • No OAuth2 Proxy needed — Genesis handles authentication directly
  • Simpler architecture — Fewer moving parts to configure and maintain
  • Browser-native — Works with all modern browsers including Safari and Chrome Incognito
  • Automatic token refresh — Uses refresh tokens, not iframe-based silent renew
  • WebSocket support — Authenticated real-time connections

Architecture

  1. User clicks login → redirected to Azure AD
  2. User authenticates → returns with authorization code
  3. Frontend exchanges code for Access Token + Refresh Token (PKCE flow)
  4. API calls use Authorization: Bearer <token> header
  5. Token refresh handled automatically (no user interaction)

Prerequisites

  • AKS cluster with NGINX ingress controller installed
  • Azure CLI installed and authenticated
  • kubectl configured to access your AKS cluster
  • Helm 3.x installed
  • Sufficient node resources (Standard_D4s_v3 or larger recommended)

Hostname Configuration

For Testing/Development: This guide uses nip.io for quick testing (e.g., genesis-oidc.172.214.4.208.nip.io). nip.io is a free DNS service that maps any IP to a hostname. Only use nip.io for testing and development.For Production: You must create a permanent DNS name (e.g., genesis-oidc.yourcompany.com). Create a DNS A record pointing your domain to the ingress IP address, update all configuration files to use your permanent domain name, update Azure AD redirect URIs to use your permanent domain, and configure TLS certificates for HTTPS (required for production).

Step 1: Scale AKS Node Pool (if needed)

If you have resource constraints, ensure adequate capacity:

Step 2: Create Azure AD App Registration

  1. Navigate to Azure Portal → Microsoft Entra ID → App registrations
  2. Click “New registration”
  3. Configure:
    • Name: Genesis (or your preferred name)
    • Supported account types: Single tenant (recommended)
    • Redirect URI: Leave empty for now
  4. Click “Register”
  5. Copy these values (you’ll need them later):
    • Application (client) ID → This is your AUTH0_CLIENT_ID
    • Directory (tenant) ID → Used in AUTH_ISSUER

Configure Authentication Platform

  1. Go to Authentication blade (left sidebar)
  2. Click “Add a platform”
  3. Select “Single-page application” (NOT “Web”!)
  4. Enter Redirect URI:
    • For Testing: http://genesis-oidc.<INGRESS_IP>.nip.io or https://genesis-oidc.<INGRESS_IP>.nip.io
    • For Production: https://genesis-oidc.yourcompany.com (your permanent domain)
    • Important: No trailing slash, no /callback path — just the origin
  5. Click “Configure”
  6. If hosted MCP for Claude Code will be enabled, click “Add a platform” again, select “Mobile and desktop applications”, and add this redirect URI:
    This is Claude Code’s local loopback callback. It does not require inbound network access to the user’s laptop; after Microsoft sign-in, the user’s browser redirects to localhost on the same machine.
  7. Under “Implicit grant and hybrid flows”:
    • Leave BOTH unchecked (ID tokens AND Access tokens)
    • We use Authorization Code + PKCE, not Implicit flow
  8. Click “Save”

Configure Token Claims (Important)

Azure AD doesn’t include email in tokens by default. To fix:
  1. Go to Token configuration blade
  2. Click “Add optional claim”
  3. Select ID token type
  4. Check: email, upn, preferred_username
  5. Click “Add”

Checklist

  • Platform type is “Single-page application” (not “Web”)
  • Redirect URI is exact origin without trailing slash
  • Hosted MCP public/native redirect URI is added: http://localhost:31318/callback
  • Implicit grant checkboxes are BOTH unchecked
  • Optional claims added: email, upn, preferred_username

Step 3: Configure values-aks-oidc.yaml

Copy and edit the values file from your Genesis deployment bundle:

Required Configuration

Edit Azure/my-values-oidc.yaml with your values:
If you prefer to version Genesis app settings separately from AKS values, move the OIDC, provider, and embedding configuration into genesis-config.yaml and keep Kubernetes settings such as namespace, ingress, and resources in Azure/my-values-oidc.yaml.
Pass the file with --set-file genesisConfig.content=genesis-config.yaml, or use genesisConfig.existingConfigMap when the file already exists as a ConfigMap. Genesis mounts the file at /app/.genesis/config/genesis-config.yaml, and explicit environment variables still take precedence over file-backed values.
extraManifests defaults to []. Add complete Kubernetes resources here when you want Helm to deploy objects such as PodDisruptionBudget, NetworkPolicy, or PodMonitor alongside Genesis without forking the chart. Each list item is rendered as its own YAML document.
Current Genesis container images also bundle the in-repo genesis_sdk package under /app/genesis_sdk (for execute_python_code local execution — not installable from PyPI) and the llm_wiki package under /app/llm_wiki, so the SDK, wiki APIs, and tools shipped with Genesis are available without extra image customization.
If you use azure-aks/deploy-aks-ga-oidc.sh, set ECR_REGISTRY in azure-aks/aks-config.env and pin image.tag in your values file. The script passes ecr.registry and a fresh ecr.token, forces image.pullPolicy=Always, waits for the StatefulSet rollout to complete, and only passes --set image.tag when GENESIS_IMAGE_TAG is set for a one-off override.
Replace:
  • <tenant-id> with your Directory (tenant) ID
  • <application-client-id> with your Application (client) ID

Ingress Configuration

For Testing (nip.io): Initially deploy with a placeholder host, then update with ingress IP:
For Production (permanent DNS): After creating DNS A record, use your permanent domain:

Step 4: Deploy Genesis

Step 5: Get Ingress IP and Update Configuration

Update Helm Values

For Testing (nip.io): Edit Azure/values-aks-oidc.yaml and update the ingress host:
For Production (permanent DNS):
  1. Create DNS A record: genesis-oidc.yourcompany.com → <INGRESS_IP>
  2. Edit Azure/values-aks-oidc.yaml:

Update Azure AD Redirect URIs

Keep the Genesis web UI redirect and Claude Code hosted-MCP redirect together on the same app registration:
  • The Genesis web UI redirect is the public Genesis origin.
  • The Claude Code MCP redirect is http://localhost:31318/callback.
When enabling Hosted MCP for Claude Code, also configure a customer-facing public base URL (GENESIS_MCP_PUBLIC_URL or correct ingress forwarding headers). For Anthropic Console Connect, set GENESIS_OAUTH_PROXY_ENABLED=true on the Genesis workload so Genesis can advertise and proxy OAuth on the same public hostname. For Testing (nip.io):
For Production (permanent domain):

Redeploy with Updated Values

Step 6: Verify Deployment

Step 7: Test Authentication

Testing (nip.io):
  1. Navigate to http://genesis-oidc.<INGRESS_IP>.nip.io/ or https://genesis-oidc.<INGRESS_IP>.nip.io/
  2. Click Login — you’ll be redirected to Microsoft login
  3. Authenticate with your Azure AD credentials
  4. After successful authentication, you’ll be redirected back to Genesis
Production (permanent domain):
  1. Navigate to https://genesis-oidc.yourcompany.com/
  2. Click Login — you’ll be redirected to Microsoft login
  3. Authenticate with your Azure AD credentials
  4. After successful authentication, you’ll be redirected back to Genesis

Step 8: Configure the LLM Provider

Genesis starts without a provider and reports No suitable LLM found until one is configured. Configure it against the running pod, either from Config → AI Providers in the UI or through the config API. Keep credentials in a request-body file — process arguments are visible to every process on the host and land in shell history.
The key is stored by Genesis through this call, not supplied as a Helm value. If check stops the chain on “Configuration for … already exists”, the activation and model-assignment calls never ran — the provider is stored but not usable. Change the first call from POST to PUT and rerun the whole block, so activation and assignment execute after the update. For the full endpoint reference and the other providers, see Configuring a provider through the config API.
The chart still accepts secrets.openaiApiKey, config.modelName and config.azureOpenaiEndpoint; this guide’s values example no longer shows them. Each reaches the pod as an environment variable carrying a default_env_var binding — secrets.openaiApiKey as OPENAI_API_KEY, on the OpenAI provider’s key field. Such a binding seeds its field while nothing is stored there, and stored APP_CONFIG values take precedence from then on, so a value saved through the UI or the config API wins on every later read. They are deprecated as an LLM setup path: none of them writes llm.active_provider, and on an Azure OpenAI deployment secrets.openaiApiKey seeds the wrong provider — the azureopenai key field binds to AZURE_OPENAI_API_KEY. Configure the provider with the calls above, or declare it in a genesis-config.yaml mounted with --set-file genesisConfig.content=....
A mounted genesis-config.yaml is a third configuration surface. An unchanged file fills only the paths that are missing, so later UI edits survive a restart; when the profile path or the file’s hash changes, Genesis clears and rewrites every top-level section the file contains. Treat the file as the canonical source wherever it is used.

Helm-driven RBAC

For production OIDC deployments (for example Azure Entra ID on AKS), you can declare RBAC outside the Genesis UI using Helm values. Genesis applies both features on startup and on every login.

Group-to-role mapping (groupRoleMapping)

Maps external identity-provider group identifiers (for example Entra security group GUIDs from the JWT groups claim) to Genesis role names. On each login, Genesis syncs the user’s roles from group membership. Provide a YAML file via --set-file:
Example group-role-mapping.yaml:
Sync behavior:
  • Roles are added when the user gains membership in a mapped group.
  • Roles are removed when the user loses membership — but only for roles that appear as values in the mapping. Manually assigned roles outside the mapping are never removed.
  • The sync runs on every login, not only first login.
Entra prerequisite: Under Token configuration, add a groups claim with Security groups and emit group identifiers as GUIDs. Genesis mounts the file at /app/.genesis/config/group-role-mapping.yaml and sets GENESIS_GROUP_ROLE_MAPPING_FILE. Without Helm, you can instead set GENESIS_GROUP_ROLE_MAPPING to a JSON object with the same key/value shape.

RBAC seed policies (rbacSeedPolicy)

Pre-populate custom roles, permissions, and role hierarchy at startup using standard Casbin CSV format. Provide via --set-file:
Example rbac-policies.csv:
Genesis mounts the file at /app/.genesis/config/rbac-policies.csv and sets GENESIS_RBAC_SEED_FILE. Policies are idempotent — existing rules are not duplicated. Lines starting with # and blank lines are skipped. You can also set default roles for brand-new users (before the admin UI override) in genesis-config.yaml:
See RBAC in Genesis: A Practical Guide and Advanced Settings → RBAC defaults.

Configuration Reference

Environment Variables

Azure AD URLs

For tenant ID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:
  • Issuer URL: https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/v2.0
  • JWKS URL: https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/discovery/v2.0/keys

Reference: Example values file

The following is a production-ready example values-aks-oidc.yaml for Genesis on Azure AKS with native OIDC. Replace placeholders (e.g. <tenant-id>, <application-client-id>, <your-resource-name>, genesis-oidc.yourcompany.com) with your own values.
When config.databricksDb.authType is user_oauth, Genesis bootstraps a per-user OAuth Databricks connection at startup using DATABRICKS_DB_SERVER_HOSTNAME and DATABRICKS_DB_HTTP_PATH only — no DATABRICKS_DB_PAT secret is required. Each user authorizes their own grant at runtime. When authType is pat (default), provide secrets.databricksDbPat or existingSecretDatabricksDbPat.

Troubleshooting

”AADSTS500113: No reply address is registered”

Cause: No Redirect URI configured in Azure AD. Fix:
  1. Azure Portal → App registrations → Your app → Authentication
  2. Add platform: Single-page application
  3. Add redirect URI: http://genesis-oidc.<IP>.nip.io (no trailing slash, no path)

“AADSTS50011: Reply URL does not match”

Cause: Redirect URI mismatch. Fix:
  • For Genesis web login, register exactly: http://genesis-oidc.<IP>.nip.io or https://genesis-oidc.yourcompany.com (no trailing /)
  • For Claude Code hosted MCP login, register exactly: http://localhost:31318/callback as a Mobile and desktop applications redirect URI
  • Check browser DevTools → Network → look at redirect_uri in the /authorize request

Login works but no email in Genesis

Cause: Azure AD doesn’t include email in tokens by default. Fix:
  1. Azure Portal → App registrations → Your app → Token configuration
  2. Click “Add optional claim”
  3. Select ID token type
  4. Check: email, upn, preferred_username
  5. Save and re-login

Token Refresh Failure / Infinite Login Loop

Cause: Missing offline_access scope. Fix:
  • The frontend automatically requests openid profile email offline_access
  • Verify Azure AD allows refresh tokens for SPAs (enabled by default)

CORS Errors

Cause: Usually a misconfiguration in Azure AD. Fix:
  • Azure AD enables CORS by default for SPA apps
  • Verify the platform type is “Single-page application”

WebSocket Connection Fails

Cause: Token not sent or invalid. Fix:
  • Set AUTH_ALLOW_QUERY_TOKEN: "true" in config
  • Check that token hasn’t expired
  • Check browser DevTools → Network → WS → Headers

Pod Not Starting

Comparison: OIDC vs OAuth2 Proxy

If you choose OAuth2 Proxy instead of native OIDC, use the Helm bundle’s azure-aks/oauth2-proxy-values.yaml example (see also azure-aks/AZURE_AD_AKS_SETUP.md). Required oauth2-proxy settings for dashboard and Kubernetes health probes:
cookie_secure = true applies when HTTPS terminates at the ingress or external load balancer. In this guide’s reverse-proxy mode (upstreams = [...]), skip_auth_routes exempts only the GET probe endpoints above; other /api/health/* routes stay behind authentication. Under the nginx auth_request / nginx.ingress.kubernetes.io/auth-url pattern, oauth2-proxy sees only /oauth2/auth, so skip_auth_routes does not match application paths. Use a second Ingress on the same host without the auth annotations to exempt the health endpoints there. Keep offline_access in scope when using cookie_refresh: Entra ID returns a refresh token only for requests that include this scope. Without it, oauth2-proxy cannot renew the session when the Entra access token expires (about 1 hour by default), and the user is sent through sign-in again. cookie_refresh is not enabled in this example; if you enable it, set it below the access-token lifetime, such as 55m. Adding offline_access to an existing app registration may prompt users for consent on their next sign-in, depending on tenant consent policy and existing grants. Grant admin consent once from Azure AD → App registrations → your app → API permissions → Grant admin consent to avoid that prompt. Keep cookie_csrf_per_request = true to give concurrent sign-in flows separate CSRF cookies and prevent invalid-CSRF 403 errors. The trade-off is that abandoned flows leave cookies behind, so keep cookie_csrf_expire = "5m"; on oauth2-proxy 7.11 and later, set cookie_csrf_per_request_limit if needed to cap accumulation and avoid request headers growing until nginx returns 431 or 502.

Cleanup

To remove the deployment:
PVCs are retained by default. To delete: kubectl delete pvc -n genesis-oidc --allFor backup and snapshot strategies (CSI snapshots, Azure Backup for AKS, Velero, logical exports), see Backup and Snapshot Options.

Production DNS Setup

Important: For production deployments, you must configure permanent DNS names:
  1. Get your ingress IP:
  2. Create DNS A record:
    • In your DNS provider (Azure DNS, Route 53, etc.)
    • Create A record: genesis-oidc.yourcompany.com → <INGRESS_IP>
  3. Update Helm values:
    • Edit Azure/values-aks-oidc.yaml to use your permanent domain
    • Update ingress hosts to use your domain
    • Add TLS configuration
  4. Update Azure AD redirect URIs:
    • Remove nip.io redirect URIs
    • Add your permanent domain redirect URI: https://genesis-oidc.yourcompany.com
  5. Redeploy:

Next Steps

  • See your Genesis deployment bundle for Azure Key Vault setup (storing secrets securely), TLS configuration (HTTPS), and production checklist (hardening).
  • For general AKS deployment without OIDC, see Deploying Genesis - Kubernetes on Azure.