HIPsHanzo Proposals
Back to HIPs
HIP-22DraftStandards TrackInfrastructure

Identity & Access Management Standard

Hanzo AI Team
Created: 2025-01-15
Requires: HIP-0027, HIP-0111, HIP-0144

HIP-0026: Identity & Access Management Standard

Abstract

Hanzo IAM is the identity and access management provider for the Hanzo ecosystem and the sole authority for identity and tokens. Nothing else in the estate issues a credential, validates one, or keeps a session: there is no service token, no shared secret and no per-app auth stack. This HIP specifies the server; HIP-0111 specifies the wire contract every client speaks to it, and where the two touch, HIP-0111 is authoritative.

IAM is a clean-room native rewrite on the Hanzo stack — zip over hanzoai/orm, with no Beego and no xorm. Storage is one orm.DB abstraction with the backend chosen at boot (--store): embedded SQLite by default (hanzoai/sqlite, pure-Go, WAL), or the shared sql or datastore over ZAP (HIP-0144). Every handler is written once against orm.DB and never against a driver.

It implements OAuth 2.0, OpenID Connect, and SCIM 2.0, and provides multi-tenant authentication with per-organization white-label identity domains. Any organization registered in IAM can be assigned a branded login page and identity domain; the default deployment ships hanzo.id, lux.id, zoo.id, pars.id and id.ad.nexus.

Repository: github.com/hanzoai/iam Image: ghcr.io/hanzoai/iam Retired: the Beego/xorm fork this replaced is hanzoai/iam-v1, out of every graph. A description of Beego, xorm, or a Postgres schema is a description of that repository.

Motivation

The Problem

Every service in the Hanzo ecosystem needs authentication. Without a centralized IAM, each team independently builds login flows, token validation, user storage, and session management. This leads to:

  1. Duplicated effort: Cloud, Commerce, Console, Platform, and Chat all need OAuth. Five teams building five login pages is waste.
  2. Inconsistent security posture: Some teams do PKCE, some do not. Some rotate tokens, some use static API keys. The attack surface is the union of all weaknesses.
  3. No cross-service SSO: A user logged into cloud.hanzo.ai should not need to log in again at console.hanzo.ai. Without centralized identity, SSO requires ad-hoc token sharing.
  4. Multi-org complexity: Hanzo (AI infrastructure), Lux (blockchain), Zoo (research foundation), Pars (regional platform), and AdNexus (advertising) are separate organizations with separate branding, but share users and infrastructure. Each organization needs its own login page, theme, and policies, while a single user (e.g., [email protected]) must hold memberships across all of them.
  5. Billing integration: AI usage is metered per-user. The billing system needs a single source of truth for "who is this user and what is their balance?" If user identity lives in IAM and balance lives in a separate billing service, every LLM API call requires two round-trips.

Why Centralized IAM Solves This

A single IAM instance at hanzo.id eliminates all five problems. Services delegate authentication entirely. The OAuth application model provides per-service isolation (each app has its own client ID, redirect URIs, and scopes). Multi-org support is built into the data model. And the user entity in IAM carries a balance field, so balance checks are a single query against the same database that validates the token.

Design Philosophy

This section explains the why behind each major design decision. Good infrastructure decisions compound; bad ones metastasize. Understanding the rationale prevents future engineers from "fixing" things that are not broken.

Why one identity service, owned outright

A managed identity service prices per monthly active user and puts the most sensitive part of the stack behind somebody else's export path: migrating password hashes out is not a routine operation, and an air-gapped or sovereign deployment cannot use a hosted provider at all. Those are the constraints, and they are why identity is ours.

Owning it is not the same as forking it. The predecessor was a fork, and the fork is what a clean-room rewrite replaced — hanzoai/iam owns its source outright and collapses to one way of doing each thing. What that buys is the ability to delete: the vendor error envelope, the verb aliases, the second spelling of the token endpoint, and the second storage engine all go away because nothing upstream requires them (HIP-0111 §4 lists what is gone and §Conformance status records what is still live).

The cost is stated plainly: a rewrite carries no upstream community and no inherited security review, so the surface is RFC-standard precisely so that review can be done against the RFCs rather than against us.

Why Multi-Tenant via Domain

Each organization gets its own white-label identity domain. The system supports an arbitrary number of tenants — any organization registered in IAM can be assigned a custom domain. The default deployment includes:

OrganizationDomainPrimary ColorDescription
Hanzohanzo.id#fd4444 (red)AI infrastructure
Luxlux.id#e4e4e7 (zinc)Blockchain network
Zoozoo.id#10b981 (emerald)Research foundation
Parspars.id#3b82f6 (blue)Regional platform
AdNexusid.ad.nexus#3b82f6 (blue)Advertising platform

Adding a new tenant requires:

  1. Create the organization in IAM (via API or init_data.json)
  2. Create an OAuth application for the organization
  3. Add the domain to the edge (an IngressRoute served by Hanzo Ingress, HIP-0068, or a DNS record)
  4. Either add the domain to the hanzo/id middleware tenant map, or deploy a forked instance with IAM_ORIGIN, NEXT_PUBLIC_ORG, and NEXT_PUBLIC_CLIENT_ID environment variables

Hanzo Ingress (HIP-0068) routes every tenant domain to the same IAM process. IAM resolves the organization from the request's Host header via the origin configuration and the application's organization field. Organizations are fully isolated — different themes, different OAuth applications, different password policies, different MFA requirements — while sharing one IAM process and one database.

The hanzo/id login UI is designed to be forked for deep customization. Organizations can:

  • Fork hanzoai/id to luxfi/id, zoofdn/id, etc. for fully custom branding
  • Or use the same hanzoai/id image with per-tenant env vars for lightweight white-labeling
  • Or add entries to the middleware tenant map for multi-domain deployment from a single image

The alternative (path-based multi-tenancy like hanzo.id/lux/login) is fragile. It leaks the organizational structure into URLs, makes CORS configuration harder, and prevents each org from having a clean, branded identity domain that users can trust.

Why balances are NOT IAM's

An earlier revision of this HIP argued that the user's balance belongs on the user record, so that "is this caller authenticated?" and "does this caller have credit?" answer in one token validation. That is not what shipped, and the argument does not survive contact with the money.

A balance is a position in a ledger. It is derived from transactions that Commerce records, that a payment processor confirms, and that a metered usage record debits — three systems with their own ordering, retries and reconciliation (HIP-0018, HIP-1220, HIP-1313, HIP-1001). Putting the authoritative copy on the identity record makes identity a participant in settlement: a failed debit becomes an identity write, and a disagreement between the two copies is resolved by whichever one a reader happened to ask.

The balance fields IAM carries are read-only mirrors, and the code says so. The authoritative balance lives in Commerce. A service that gates on credit reads the entitlement surface, not the identity record; a token is proof of who is calling, never of what they can afford.

Specification

Architecture

                           Internet
                              │
                    ┌─────────┴─────────┐
                    │   Hanzo Ingress    │
                    │  (TLS termination) │
                    │   HIP-0068         │
                    └─────────┬─────────┘
                              │
              ┌───────────────┼───────────────┐
              │               │               │
         hanzo.id         lux.id          zoo.id ...
              │               │               │
              └───────────────┼───────────────┘
                              │
                    ┌─────────┴─────────┐
                    │    Hanzo IAM       │
                    │  zip over orm.DB   │
                    │  HTTP edge + ZAP   │
                    └─────────┬─────────┘
                              │  --store
                    ┌─────────┴─────────┐
                    │ sqlite (default)  │
                    │ sql | datastore   │
                    │     HIP-0144      │
                    └───────────────────┘

OAuth 2.0 Flow: Authorization Code Grant with PKCE

Every Hanzo application uses Authorization Code Grant with PKCE (RFC 7636). Implicit grant is not supported. This is the flow:

1. Client generates code_verifier (random 43-128 chars)
2. Client computes code_challenge = BASE64URL(SHA256(code_verifier))

3. Client redirects user to:
   GET https://iam.hanzo.ai/v1/iam/oauth/authorize
     ?client_id=hanzo-app-client-id
     &redirect_uri=https://hanzo.ai/callback
     &response_type=code
     &scope=openid profile email
     &state=<random>
     &code_challenge=<code_challenge>
     &code_challenge_method=S256

4. User authenticates at the brand login UI (password, WebAuthn, or social login)

5. IAM redirects back:
   GET https://hanzo.ai/callback
     ?code=<authorization_code>
     &state=<random>

6. Client exchanges code for tokens:
   POST https://iam.hanzo.ai/v1/iam/oauth/token
     grant_type=authorization_code
     &code=<authorization_code>
     &redirect_uri=https://hanzo.ai/callback
     &client_id=hanzo-app-client-id
     &code_verifier=<code_verifier>

7. IAM returns:
   {
     "access_token": "eyJhbGciOi...",
     "token_type": "Bearer",
     "expires_in": 604800,
     "refresh_token": "eyJhbGciOi...",
     "id_token": "eyJhbGciOi...",
     "scope": "openid profile email"
   }

Access tokens are JWTs signed with the application's certificate (e.g., cert-hanzo). Token lifetime defaults to 168 hours (7 days). Refresh token lifetime defaults to 720 hours (30 days).

Multi-Tenant Domain Resolution

When a request arrives, IAM resolves the organization context through the following chain:

  1. Application lookup via /v1/iam/get-app-login: The login UI (hosted at hanzo.id, served by the hanzo.id-worker Cloudflare Worker) calls this endpoint with the clientId from the OAuth authorize URL. IAM returns the application name and organization name. This is the source of truth.

  2. Direct login via /v1/iam/login: The payload includes application and organization fields. These must match the application's configured organization. Hardcoding organization: "hanzo" for all requests breaks scoped SSO clients (e.g., KMS has its own client ID and expects the correct org context).

  3. Domain-based fallback: If no application context is provided, IAM falls back to matching the request Host header against known origins. Each application configures origin and originFrontend to enable this.

Application Configuration

Each service in the ecosystem registers as an OAuth application with its own client credentials, redirect URIs, and scopes:

ApplicationClient IDOrganizationRedirect URIs (production)
app-hanzohanzo-app-client-idhanzohanzo.ai/callback, hanzo.app/callback, cloud.hanzo.ai/callback
app-cloudhanzo-cloud-client-idhanzocloud.hanzo.ai/callback
app-commercehanzo-commerce-client-idhanzocommerce.hanzo.ai/callback
app-consolehanzo-console-client-idhanzoconsole.hanzo.ai/api/auth/callback/hanzo-iam
app-platformhanzo-platform-client-idhanzoplatform.hanzo.ai/callback
app-zoozoo-app-client-idzoozoo.ngo/callback, zips.zoo.ngo/callback
app-luxlux-app-client-idluxlux.network/callback, wallet.lux.network/callback
app-parspars-app-client-idparspars.ai/callback
app-adnexusadnexus-app-client-idadnexusad.nexus/callback

All applications use:

  • Grant types: authorization_code, refresh_token, client_credentials, implicit, password
  • Response types: code, token, id_token
  • Token format: JWT
  • Password hashing: argon2id
  • WebAuthn: Enabled

Client secrets use KMS-managed placeholders (${IAM_APP_HANZO_CLIENT_SECRET}) resolved at startup via the resolveSecrets() function. Plaintext secrets never appear in configuration files or init_data.json.

The balance mirror

Every user and organization carries balance fields. They are a cache with a publisher, not a ledger:

  • Commerce owns the authoritative position and every balance-affecting event.
  • IAM's copy exists so a surface that has already validated a token can render a number without a second round trip.
  • A decision that costs money — admitting a request, starting a run, releasing a payout — MUST read the authoritative surface. Gating spend on a mirror gates it on a value that can be stale in the direction that costs us.

There is no add-balance and no add-transaction verb on IAM (HIP-0111 §4.8). The ledger's shape is specified where the ledger is: HIP-1001 for double-entry, HIP-1313 for the metered record, HIP-1220 for the merchant half.

Bootstrap: init_data.json

IAM bootstraps from init_data.json on first startup. This file defines the initial state of the system:

{
  "organizations": [
    {
      "name": "hanzo",
      "displayName": "Hanzo",
      "websiteUrl": "https://hanzo.ai",
      "passwordType": "argon2id",
      "defaultApplication": "app-hanzo",
      "themeData": {
        "themeType": "dark",
        "colorPrimary": "#fd4444"
      }
    },
    { "name": "zoo", "displayName": "Zoo Labs", "colorPrimary": "#10b981" },
    { "name": "lux", "displayName": "Lux Network", "colorPrimary": "#e4e4e7" },
    { "name": "pars", "displayName": "Pars", "colorPrimary": "#3b82f6" },
    { "name": "adnexus", "displayName": "AdNexus", "colorPrimary": "#3b82f6" }
  ],
  "applications": [
    {
      "name": "app-hanzo",
      "organization": "hanzo",
      "clientId": "hanzo-app-client-id",
      "clientSecret": "${IAM_APP_HANZO_CLIENT_SECRET}",
      "grantTypes": ["authorization_code", "refresh_token", "client_credentials", "implicit", "password"],
      "tokenFormat": "JWT",
      "expireInHours": 168,
      "refreshExpireInHours": 720
    }
  ],
  "users": [
    {
      "name": "admin",
      "email": "[email protected]",
      "type": "normal-user",
      "isAdmin": true,
      "balance": 10000
    }
  ],
  "certs": [
    {
      "name": "cert-hanzo",
      "cryptoAlgorithm": "RS256",
      "bitSize": 4096
    }
  ]
}

The initDataNewOnly configuration flag controls whether init_data.json overwrites existing records (false) or only creates missing ones (true). Production uses initDataNewOnly = false to ensure configuration drift is corrected on restart.

API Endpoints

HIP-0111 §1 is the one table of endpoints. It is not repeated here, because two tables of one surface is how the second one goes stale — which is exactly what happened: this section previously listed get-account, get-user, add-user, update-user, delete-user, add-balance, add-transaction and get-transactions as the user-management and billing surface, and every one of those is a verb alias HIP-0111 §4.8 forbids. Identity provisioning is SCIM 2.0 (§8); account claims are OIDC UserInfo (§1); delegation is RFC 8693 token exchange (§7); balances are Commerce's, not IAM's.

What this HIP states about the surface, as the server's own concern:

  • The paths are /v1/iam/*. There is no /oauth/*, no /api/login/*, no /api/ prefix anywhere, and no v2 (HIP-0119).
  • IAM serves a 200 text/html SPA catch-all for any unregistered path, so a wrong path is silent breakage rather than a 404. That is why clients reach the surface only through @hanzo/iam, which holds the paths in one place.
  • The login entry point (get-app-login, login, signup, send-verification-code) is the authorization server's own concern — OAuth deliberately does not specify how an AS authenticates the end user — and is called by the hosted login UI alone. It is not a client integration surface. HIP-0111 §6 is normative for it.
  • Health is at the root: /healthz, /readyz. Not /api/health.

The discovery document is host-relative and self-consistent — issuer, authorize, token, userinfo and jwks share one origin, which requires originFrontend to be empty:

{
  "issuer": "https://iam.hanzo.ai",
  "authorization_endpoint": "https://iam.hanzo.ai/v1/iam/oauth/authorize",
  "token_endpoint": "https://iam.hanzo.ai/v1/iam/oauth/token",
  "userinfo_endpoint": "https://iam.hanzo.ai/v1/iam/oauth/userinfo",
  "jwks_uri": "https://iam.hanzo.ai/v1/iam/.well-known/jwks",
  "end_session_endpoint": "https://iam.hanzo.ai/v1/iam/oauth/logout",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic"]
}

A split-origin discovery document breaks strict OIDC clients that pin the issuer.

SDK Integration

The client contract is HIP-0111. JS/TS applications integrate only through @hanzo/iam; Go services use iamsdk. No application writes an OIDC path string.

Go SDK

import "github.com/hanzoai/iam/iamsdk"

func init() {
    iamsdk.InitConfig(
        "https://iam.hanzo.ai",       // IAM endpoint
        "hanzo-app-client-id",        // Client ID
        "client-secret-here",         // Client secret
        "cert-hanzo",                 // Certificate name
        "hanzo",                      // Organization
        "app-hanzo",                  // Application
    )
}

// Validate a JWT access token
func validateToken(token string) (*iamsdk.Claims, error) {
    claims, err := iamsdk.ParseJwtToken(token)
    if err != nil {
        return nil, fmt.Errorf("invalid token: %w", err)
    }
    return claims, nil
}

// Get user info from token
func getUserInfo(token string) (*iamsdk.User, error) {
    return iamsdk.GetUserByAccessToken(token)
}

JavaScript / TypeScript SDK (@hanzo/iam)

Server-side token validation:

import { validateToken } from "@hanzo/iam/server";

const result = await validateToken(accessToken, {
  serverUrl: "https://iam.hanzo.ai",
  clientId: "hanzo-app-client-id",
});
if (result.ok) {
  const { userId, email, owner } = result; // owner = org slug; scope queries to it
}

Framework providers (@hanzo/iam/betterauth, @hanzo/iam/nextauth), the React SPA client (@hanzo/iam/react, @hanzo/iam/browser), and Passport (@hanzo/iam/passport) are specified in HIP-0111.

Implementation

Deployment

IAM runs on the hanzo-k8s cluster, one process per brand origin, behind Hanzo Ingress (HIP-0068), which terminates TLS and routes every tenant domain to it. The image is ghcr.io/hanzoai/iam, built by Hanzo Git Actions from .hanzo/workflows/ (HIP-0036); there is no second registry.

Health is at the root — /healthz, /readyz — never under a version prefix and never under /api/ (HIP-0119 §Health).

Configuration

Configuration is flags with environment fallbacks; there is no app.conf and no Beego runmode.

flagwhat it decides
--storesqlite (default), sql, or datastore — see Storage below
--dbSQLite path, when the store is sqlite
--zapthe ZAP listener for service-to-service calls
--httpthe external HTTP edge
--init-dataseed file, new entities only

Deployment environment: IAM_ISSUER pins the issuer per brand (e.g. https://hanzo.id) so every token and the discovery document advertise one stable issuer regardless of request host, never steerable by X-Forwarded-Host. The three capability allow-lists — IAM_TOKEN_EXCHANGE_APPS, IAM_ADMIN_TOKEN_EXCHANGE_APPS and IAM_KEY_MINT_ALLOWED_APPS — are specified in HIP-0111 §7, which is the one description of them.

The seed file expands ${VAR} from the environment and creates only what is missing. It never deletes or overwrites an existing user, application or org, so a restart cannot reset a password, an MFA enrolment or any other user data.

Storage

One orm.DB abstraction, backend chosen at boot, per HIP-0144:

  • sqlite (default) — embedded, pure-Go, WAL. No server, no credential.
  • sql — the one shared hanzoai/sql, reached over ZAP.
  • datastore — hanzoai/datastore over ZAP, with snapshots, at no code change.

There is no hanzo_iam database, no per-app Postgres instance, no Redis, and no MySQL path. Sessions are IAM's own state in that store, not a second engine: an external cache for sessions was a property of the retired fork.

The entities are organization, user, application, token, session, cert, provider, permission and role. Balance fields on an organization are read-only mirrors; the authoritative balance lives in Commerce (HIP-0018, HIP-1220) and the metered record in HIP-1313. IAM is not a billing engine and is not the source of truth for spend.

Secrets

Client secrets and signing material are KMS references, never values, and never plaintext in Git, a manifest, the seed file or an image. The path is derivable from the app that reads the secret and the variable it becomes — HIP-0136 is the one statement of that convention, and it also records why base keeps its own KMS project rather than being folded into the shared one.

Passwords are hashed, never stored or transmitted in the clear. Verification is algorithm-resolved from the stored row (argon2id and bcrypt), verify-only and fail-closed: an unrecognised algorithm is a refusal, not a fallback.

Standards Compliance

Standards Implemented

StandardStatusNotes
RFC 6749 (OAuth 2.0)FullAuthorization Code + PKCE; client_secret_basic
RFC 7636 (PKCE)FullS256 only
OIDC Core 1.0FullDiscovery, UserInfo, ID Tokens
OIDC Discovery 1.0Full/.well-known/openid-configuration (host-relative)
OIDC RP-Initiated LogoutFull/v1/iam/oauth/logout
RFC 7517 (JWK)Full/v1/iam/.well-known/jwks
RFC 7519 (JWT)FullRS256 today; ML-DSA-65 hybrid JWT and JWKS from the Cert entity is the direction (HIP-0005)
RFC 7662 / RFC 7009FullIntrospection and revocation
RFC 8414FullAuthorization server metadata
RFC 8693 (Token Exchange)FullDelegation, gated per HIP-0111 §7
RFC 8707 (Resource Indicators)Fullresource/audience pins aud; validators fail closed
RFC 7644 / RFC 7643 (SCIM 2.0)FullIdentity provisioning

Custom Login UI

The hanzo/id repository provides a forkable, white-label Next.js login UI that serves as the frontend for all identity domains. It includes:

  • OIDC discovery rewriting: serves .well-known host-relative to the tenant domain
  • Multi-tenant detection: hostname-based tenant resolution (per-brand origin)
  • PKCE support: built-in S256 code challenge generation and verification
  • White-label forkable: fork to luxfi/id, zoofdn/id, etc. for org-specific branding

SDK Compliance

The client contract is HIP-0111. JS/TS uses @hanzo/iam; Go uses iamsdk. All hit the canonical /v1/iam/oauth/* endpoints.

SDKPackageAuthorizeToken
JS/TS@hanzo/iam/v1/iam/oauth/authorize/v1/iam/oauth/token
Gogithub.com/hanzoai/iam/iamsdk/v1/iam/oauth/authorize/v1/iam/oauth/token

No Backward Compatibility

There are no legacy paths. /oauth/*, /api/login/oauth/*, and /api/-prefixed auth paths are not served and not supported. The OIDC discovery document returns only the canonical /v1/iam/oauth/* endpoints.

Security Considerations

Authentication Security

  • PKCE required: All public clients (SPAs, mobile apps) MUST use PKCE (RFC 7636) with S256 challenge method. Authorization code interception is the most common OAuth attack vector; PKCE eliminates it.
  • Token rotation: Refresh tokens are rotated on use. The previous refresh token is invalidated when a new one is issued. This limits the window of a leaked refresh token.
  • Password hashing: argon2id with per-org salt configuration. argon2id is the winner of the Password Hashing Competition and is resistant to both GPU and side-channel attacks.
  • WebAuthn: Enabled on all applications for phishing-resistant second-factor authentication.

Session Security

  • Session timeout: inactiveTimeoutMinutes = 30 in production. Idle sessions expire after 30 minutes.
  • Secure cookies: Sessions use HttpOnly, Secure, SameSite=Lax cookies. The authState configuration pins sessions to the IAM origin.
  • Sessions are IAM's own state, held in its orm.DB store with a TTL and registered for revocation. There is no external session cache: a second engine for sessions was a property of the retired fork, and it made "log everyone out" an operation on infrastructure rather than on the identity service.

Network Security

  • TLS everywhere: Hanzo Ingress (HIP-0068) terminates TLS. HTTP is redirected to HTTPS. IAM rejects plaintext.
  • CORS whitelist: The origin and originFrontend settings restrict which origins can interact with IAM APIs. Cross-origin requests from unknown origins are rejected.
  • Rate limiting: Per-IP rate limiting on login endpoints prevents brute-force attacks. Failed login attempts increment a counter; after 5 failures, the IP is throttled for 15 minutes.
  • Health endpoint isolation: /healthz is unauthenticated (load-balancer probes require it) and returns only a boolean status, leaking no internal state. It is at the root, not under /api/ and not under a version prefix (HIP-0119).

Operational Security

  • Error masking: enableErrorMask = true in production ensures internal errors (database errors, stack traces) are never exposed to clients. Clients receive generic error messages; details are logged server-side.
  • Admin password rotation: The default admin password in init_data.json is admin. Production deployments MUST rotate this immediately. The HANZO_INIT_USER_EMAIL bootstrap flow creates admin users with KMS-managed passwords.
  • Audit logging: All authentication events (login, logout, token refresh, password change) are logged with timestamp, IP, user agent, and result. Logs are shipped to the centralized logging stack.

Authentication vs Authorization (AuthN vs AuthZ)

IAM handles both authentication (identity verification) and authorization (access control), but they are distinct concerns:

Authentication (AuthN) — "Who are you?"

  • OAuth 2.0 flows (authorization code + PKCE, client credentials, device code)
  • Password login with argon2id hashing
  • Social login (GitHub, Google, etc.) via identity providers
  • WebAuthn / FIDO2 for phishing-resistant MFA
  • SAML 2.0 and CAS for enterprise SSO
  • Session management (30-minute idle timeout, in IAM's own store)

Authorization (AuthZ) — "What can you do?"

  • OAuth scopes: Applications request scopes (openid, profile, email, custom). IAM validates requested scopes against the application's allowed scope set and returns invalid_scope per RFC 6749 §4.1.2.1 if the client requests scopes not configured for its application.
  • RBAC roles and permissions: IAM supports role-based access control. Roles are collections of permissions; users are assigned roles per-organization. The permission and role tables enforce this.
  • Organization isolation: Users can be members of multiple organizations (hanzo, lux, zoo, pars, adnexus) but each session is scoped to one organization context. Cross-org access requires switching context.
  • Application-level isolation: Each OAuth application has its own client credentials, redirect URIs, grant types, and scopes. A token issued for app-console cannot be used at app-commerce (different aud claim).
  • Admin vs normal user: The isAdmin flag on the user entity grants full API access within the organization. Non-admin users are restricted to self-service operations.
  • Balance-gated access: Services can check balance > 0 from the JWT claims or userinfo endpoint to gate access to paid features (AI inference, compute).

The key design principle: IAM authenticates users and issues scoped tokens. Services authorize requests by validating token claims. IAM does not make fine-grained authorization decisions for downstream services — it provides the identity and claims that services use to make their own authorization decisions.

References

  1. Hanzo IAM - Open-source identity and access management platform
  2. RFC 6749 - The OAuth 2.0 Authorization Framework
  3. RFC 7636 - Proof Key for Code Exchange (PKCE)
  4. RFC 7519 - JSON Web Token (JWT)
  5. OpenID Connect Core 1.0 - OIDC specification
  6. RFC 7662 - OAuth 2.0 Token Introspection
  7. RFC 7009 - OAuth 2.0 Token Revocation
  8. RFC 8414 - OAuth 2.0 Authorization Server Metadata
  9. RFC 8628 - OAuth 2.0 Device Authorization Grant
  10. RFC 7517 - JSON Web Key (JWK)
  11. RFC 7033 - WebFinger
  12. HIP-0111: Hanzo IAM Authentication Standard - the wire contract, authoritative where it touches this HIP
  13. HIP-0118: SuperAdmin & Tenant Isolation Model - the reserved admin org and the one SuperAdmin predicate
  14. HIP-0519: One Identity Boundary - where the token is validated and X-Org-Id is minted
  15. HIP-0068: Ingress Standard - the edge that terminates TLS and routes every brand domain
  16. HIP-0144: Where State Lives - the store this service is a tenant of
  17. HIP-0136: One Secret, One Path - where a client secret is addressed
  18. HIP-0027: Secrets Management Standard - the KMS this reads from
  19. HIP-0004: LLM Gateway - consumes IAM tokens
  20. HIP-0018: Payment Processing Standard - Commerce, which owns the authoritative balance
  21. HIP-0025: Bot Agent Wallet & RPC Billing Protocol - agent identity, built on IAM

Copyright

Copyright and related rights waived via CC0.