Skip to content

Local identity

Before you have an IdP, you still want auth. Local identity authenticates requests against a static set of API keys defined in a file (or env var), each mapped to a principal and tenant. It's a shipped preset — zero infrastructure — and explicitly not for production (no rotation, revocation, or audit).

The key file

A JSON document mapping API keys to principals, with optional per-key tenants:

{
  "api_keys": {
    "dev-service-key": {
      "principal_id": "550e8400-e29b-41d4-a716-446655440000",
      "tenant_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
    }
  },
  "principal_tenants": {},
  "default_tenant_id": null
}

Load it from a path or the environment:

from forze_identity.builtin.local import LocalIdentityConfig, from_env, from_json_path

config = from_json_path("identity.local.json")
# or: config = from_env()   # FORZE_IDENTITY_LOCAL_FILE, else FORZE_IDENTITY_LOCAL_CONFIG (inline JSON)

Wire it

local_identity_deps registers the API-key verifier and the tenant resolver in one call:

from forze_identity.builtin.local import local_identity_deps

local = local_identity_deps(config, authn_route="main", tenancy_route="main")

deps = DepsRegistry.from_modules(local)
Wiring it by hand

local_identity_deps is a thin wrapper over the two planes — drop to this if you want to mix local keys with other routes:

from forze_identity.authn import AuthnDepsModule, AuthnKernelConfig
from forze_identity.builtin.local import (
    ConfigurableLocalApiKeyVerifier, ConfigurableLocalTenantResolver,
)
from forze_identity.tenancy import TenancyDepsModule

authn = AuthnDepsModule(
    kernel=AuthnKernelConfig(),
    authn={"main": frozenset({"api_key"})},
    api_key_verifiers={"main": ConfigurableLocalApiKeyVerifier(config=config)},
)
tenancy = TenancyDepsModule(
    tenant_resolver={"main"},
    tenant_resolvers={"main": ConfigurableLocalTenantResolver(config=config)},
)

At the boundary

Local identity uses API keys, so the middleware ingress is HeaderApiKeyAuthn (see Authn, authz & tenancy for the full boundary setup):

from forze.application.contracts.authn import AuthnSpec
from forze_fastapi.security import AuthnRequirement, HeaderApiKeyAuthn

MAIN = AuthnSpec(name="main", enabled_methods=frozenset({"api_key"}))
requirement = AuthnRequirement(
    ingress=(HeaderApiKeyAuthn(authn_spec=MAIN, header_name="X-API-Key"),),
)

A request carrying X-API-Key: dev-service-key is resolved to that principal and tenant — the same ctx.inv_ctx binding every authz/tenancy hook reads.

Demo trust model

Keys are compared in a constant-time linear scan against a static table — fine for a demo or MVP, but there's no rotation, revocation, or audit trail. Move to first-party tokens or an external IdP before production.