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
- User clicks login → redirected to Azure AD
- User authenticates → returns with authorization code
- Frontend exchanges code for Access Token + Refresh Token (PKCE flow)
- API calls use
Authorization: Bearer <token>header - 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
Using Azure Portal (Recommended)
- Navigate to Azure Portal → Microsoft Entra ID → App registrations
- Click “New registration”
-
Configure:
- Name: Genesis (or your preferred name)
- Supported account types: Single tenant (recommended)
- Redirect URI: Leave empty for now
- Click “Register”
-
Copy these values (you’ll need them later):
- Application (client) ID → This is your
AUTH0_CLIENT_ID - Directory (tenant) ID → Used in
AUTH_ISSUER
- Application (client) ID → This is your
Configure Authentication Platform
- Go to Authentication blade (left sidebar)
- Click “Add a platform”
- Select “Single-page application” (NOT “Web”!)
-
Enter Redirect URI:
- For Testing:
http://genesis-oidc.<INGRESS_IP>.nip.ioorhttps://genesis-oidc.<INGRESS_IP>.nip.io - For Production:
https://genesis-oidc.yourcompany.com(your permanent domain) - Important: No trailing slash, no
/callbackpath — just the origin
- For Testing:
- Click “Configure”
-
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.
-
Under “Implicit grant and hybrid flows”:
- Leave BOTH unchecked (ID tokens AND Access tokens)
- We use Authorization Code + PKCE, not Implicit flow
- Click “Save”
Configure Token Claims (Important)
Azure AD doesn’t include email in tokens by default. To fix:- Go to Token configuration blade
- Click “Add optional claim”
- Select ID token type
-
Check:
email,upn,preferred_username - 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
EditAzure/my-values-oidc.yaml with your values:
If you prefer to version Genesis app settings separately from AKS values,
move the OIDC, provider, and Pass the file with
embedding configuration into
genesis-config.yaml and keep
Kubernetes settings such as namespace, ingress, and resources in
Azure/my-values-oidc.yaml.--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.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.<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:Step 4: Deploy Genesis
Step 5: Get Ingress IP and Update Configuration
Update Helm Values
For Testing (nip.io): EditAzure/values-aks-oidc.yaml and update the ingress host:
- Create DNS A record:
genesis-oidc.yourcompany.com→<INGRESS_IP> - 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.
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):
Redeploy with Updated Values
Step 6: Verify Deployment
Step 7: Test Authentication
Testing (nip.io):- Navigate to
http://genesis-oidc.<INGRESS_IP>.nip.io/orhttps://genesis-oidc.<INGRESS_IP>.nip.io/ - Click Login — you’ll be redirected to Microsoft login
- Authenticate with your Azure AD credentials
- After successful authentication, you’ll be redirected back to Genesis
- Navigate to
https://genesis-oidc.yourcompany.com/ - Click Login — you’ll be redirected to Microsoft login
- Authenticate with your Azure AD credentials
- 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.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:
group-role-mapping.yaml:
- 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.
/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:
rbac-policies.csv:
/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:
Configuration Reference
Environment Variables
Azure AD URLs
For tenant IDxxxxxxxx-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 examplevalues-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.
Troubleshooting
”AADSTS500113: No reply address is registered”
Cause: No Redirect URI configured in Azure AD. Fix:- Azure Portal → App registrations → Your app → Authentication
- Add platform: Single-page application
- 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.ioorhttps://genesis-oidc.yourcompany.com(no trailing/) - For Claude Code hosted MCP login, register exactly:
http://localhost:31318/callbackas a Mobile and desktop applications redirect URI - Check browser DevTools → Network → look at
redirect_uriin the/authorizerequest
Login works but no email in Genesis
Cause: Azure AD doesn’t include email in tokens by default. Fix:- Azure Portal → App registrations → Your app → Token configuration
- Click “Add optional claim”
- Select ID token type
- Check:
email,upn,preferred_username - Save and re-login
Token Refresh Failure / Infinite Login Loop
Cause: Missingoffline_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:-
Get your ingress IP:
-
Create DNS A record:
- In your DNS provider (Azure DNS, Route 53, etc.)
- Create A record:
genesis-oidc.yourcompany.com→<INGRESS_IP>
-
Update Helm values:
- Edit
Azure/values-aks-oidc.yamlto use your permanent domain - Update ingress hosts to use your domain
- Add TLS configuration
- Edit
-
Update Azure AD redirect URIs:
- Remove
nip.ioredirect URIs - Add your permanent domain redirect URI:
https://genesis-oidc.yourcompany.com
- Remove
-
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.

