Hanzo

Configuration

Canonical IAM_* environment variable contract for services that consume Hanzo IAM.

Configuration

Every Hanzo service that delegates authentication to Hanzo IAM reads the same canonical IAM_* environment variables. There is exactly one way to wire a consumer to IAM — no fallback aliases, no HANZO_IAM_* legacy names, no HANZO_CLIENT_* variants.

If you find an older HANZO_IAM_URL, HANZO_IAM_CLIENT_ID, or HANZO_IAM_CLIENT_SECRET reference in any consumer (manifest, docker-compose, code), it is stale and must be renamed.

Consumer environment variables

These are read by services that consume IAM (web apps, APIs, gateways). They are populated from KMS at deploy time.

VariableRequiredDescriptionExample
IAM_ENDPOINTyesIAM server URL. In-cluster: http://iam:8000. Public: https://hanzo.id.https://hanzo.id
IAM_ISSUERoptionalOIDC issuer URL used for token validation. Defaults to IAM_ENDPOINT.https://hanzo.id
IAM_CLIENT_IDyesOAuth client ID issued for this application.hanzo-myapp
IAM_CLIENT_SECRETyesOAuth client secret. Must come from KMS, never plaintext.${IAM_APP_MYAPP_CLIENT_SECRET}
IAM_ORGoptionalOrganization slug this app is scoped to.hanzo
IAM_APPoptionalApplication name as registered in IAM.hanzo-myapp
IAM_CERToptionalPEM-encoded signing certificate (for offline JWT validation).(PEM blob)
IAM_USERINFO_URLoptionalOverride for the OIDC userinfo URL. Defaults to ${IAM_ENDPOINT}/v1/iam/oauth/userinfo.https://hanzo.id/v1/iam/oauth/userinfo

The <org>-<app> naming scheme is canonical for IAM_APP values: hanzo-team, hanzo-tasks, hanzo-cloud, hanzo-brain, etc.

RFC-compliant OAuth/OIDC endpoints

All consumer integrations MUST use the RFC 6749 / OpenID Connect Core 1.0 standard paths. Legacy Casdoor paths (/login/oauth/authorize, /api/login/oauth/access_token, /api/userinfo, …) are still served for backward compatibility but are deprecated and MUST NOT appear in new code or docs. The Hanzo canonical alias prefix is /v1/iam/* (NOT /api/* — that is the Casdoor compat surface). Use /v1/iam/userinfo, /v1/iam/login/oauth/access_token, etc., when an RFC path is not yet available for a non-OIDC endpoint.

PurposePath
Authorization/v1/iam/oauth/authorize
Token/v1/iam/oauth/token
Introspection/v1/iam/oauth/introspect
Revocation/v1/iam/oauth/revoke
UserInfo/v1/iam/oauth/userinfo
Device authorization/v1/iam/oauth/device
End-session / logout/v1/iam/oauth/logout
Discovery/.well-known/openid-configuration (alias of /v1/iam/.well-known/openid-configuration)
JSON Web Key Set/v1/iam/.well-known/jwks

A consumer that reads IAM_ENDPOINT constructs every endpoint by concatenation — there are no other env vars for individual OAuth URLs.

Secrets sourcing

Client secrets land in Hanzo KMS first. The KMS storage key for an application's secret follows the convention IAM_APP_<APP>_CLIENT_SECRET (uppercased, hyphens removed). Example:

KMS keyConsumes
IAM_APP_HANZO_CLIENT_SECREThanzo-app
IAM_APP_CLOUD_CLIENT_SECREThanzo-cloud
IAM_APP_COMMERCE_CLIENT_SECREThanzo-commerce

The KMS operator syncs these into a service-scoped K8s Secret, and the consumer pod reads them as IAM_CLIENT_SECRET from that Secret. See KMS Integration for the full sync pattern.

Example: docker-compose

services:
  myapp:
    image: ghcr.io/hanzoai/myapp:v1.0.0
    environment:
      IAM_ENDPOINT: https://hanzo.id
      IAM_CLIENT_ID: hanzo-myapp
      IAM_CLIENT_SECRET: ${IAM_APP_MYAPP_CLIENT_SECRET}
      IAM_ORG: hanzo
      IAM_APP: hanzo-myapp

Example: Kubernetes

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: myapp
          image: ghcr.io/hanzoai/myapp:v1.0.0
          env:
            - name: IAM_ENDPOINT
              value: http://iam.hanzo.svc.cluster.local:8000
            - name: IAM_CLIENT_ID
              value: hanzo-myapp
            - name: IAM_CLIENT_SECRET
              valueFrom:
                secretKeyRef:
                  name: myapp-secrets
                  key: IAM_CLIENT_SECRET
            - name: IAM_ORG
              value: hanzo

Example: code

const iam = {
  endpoint: process.env.IAM_ENDPOINT!,
  clientId: process.env.IAM_CLIENT_ID!,
  clientSecret: process.env.IAM_CLIENT_SECRET!,
  issuer: process.env.IAM_ISSUER ?? process.env.IAM_ENDPOINT!,
};

const tokenUrl = `${iam.endpoint}/v1/iam/oauth/token`;
const userInfoUrl =
  process.env.IAM_USERINFO_URL ?? `${iam.endpoint}/v1/iam/oauth/userinfo`;
import os

IAM_ENDPOINT = os.environ["IAM_ENDPOINT"]
IAM_CLIENT_ID = os.environ["IAM_CLIENT_ID"]
IAM_CLIENT_SECRET = os.environ["IAM_CLIENT_SECRET"]
IAM_ISSUER = os.environ.get("IAM_ISSUER", IAM_ENDPOINT)

TOKEN_URL = f"{IAM_ENDPOINT}/v1/iam/oauth/token"
USERINFO_URL = os.environ.get(
    "IAM_USERINFO_URL", f"{IAM_ENDPOINT}/v1/iam/oauth/userinfo"
)
package main

import "os"

var (
    IAMEndpoint     = os.Getenv("IAM_ENDPOINT")
    IAMClientID     = os.Getenv("IAM_CLIENT_ID")
    IAMClientSecret = os.Getenv("IAM_CLIENT_SECRET")
    IAMIssuer       = orDefault(os.Getenv("IAM_ISSUER"), IAMEndpoint)
)

func orDefault(v, d string) string {
    if v == "" {
        return d
    }
    return v
}

IAM server environment variables

These are read by the IAM server itself (not by consumers). They live in ~/work/hanzo/iam/CLAUDE.md and are reproduced here for completeness.

VariablePurposeDefault
IAM_DATA_DIRBase/SQLite parent directory/data/iam
IAM_LISTENHTTP listen address:8000
IAM_ZAP_LISTENZAP RPC listen:9653
IAM_KMS_MASTER_KEYMaster encryption key (from KMS)required
IAM_REPLICATE_BUCKETObject-storage bucket for WAL replicationoptional
IAM_REPLICATE_AGE_RECIPIENTage public key for at-rest encryptionrequired if bucket set
IAM_ADMIN_ORGBootstrap admin organization nameadmin
IAM_ADMIN_APPBootstrap admin application nameiam
IAM_ADMIN_USERBootstrap admin user nameroot

Features, organizations, and authentication methods

Authorization code, PKCE, client credentials, device code

How IAM client secrets are sourced from KMS

How is this guide?

Last updated on

On this page