---
id: w1/m9/t001
title: Tenant mint on first login — Kratos identity → tenant row + tenant_members
worker: worker1
status: done
estimate: 30m
depends_on: [w1/m2/t007]
---

## Objective

The first authenticated request from a Kratos identity that has no tenant mints one: a tenant row plus a `tenant_members(identity_id, tenant_id, role=admin)` entry. Subsequent logins mint nothing. This is the `tenant_members` write path w1/m2/t004 deliberately deferred.

## Context

- Store + schema exist: `lego/backend/internal/store/` (migrations already carry `tenants` and `tenant_members`).
- Caller identity is resolved into the request context by `internal/api/auth.go` (`api.IdentityFrom` — Kratos identity id or OAuth2 client_id).
- Only runs when the store is wired (`BEX_CP_DB_URI` set); with it unset, behavior is unchanged.

## Steps

1. Add a store lookup `TenantForIdentity(identityID)` and a mint `CreateTenantWithMember(...)` (idempotent, unique-constrained).
2. Hook after session/whoami resolution in `internal/api/auth.go`: no tenant → mint, attach tenant id to the request context.
3. Unit tests: first login mints exactly one; concurrent first logins race-safe (unique constraint, not check-then-insert); bearer-only (machine) callers do not mint.

## Files

- `lego/backend/internal/store/store.go` (+ migration if a constraint is missing)
- `lego/backend/internal/api/auth.go`
- `lego/backend/internal/store/store_pg_test.go`

## Acceptance criteria

- [ ] A fresh Kratos identity's first authenticated call creates one tenant + one membership row; a second call creates nothing.
- [ ] With `BEX_CP_DB_URI` unset, no code path touches the store.

## Out of scope

- Key→tenant binding (t002); invitation/multi-member flows; billing (Metronome) linkage.
