Skip to content

Make the /siwe surface audience-aware for cert enrollment#15

Merged
elffjs merged 1 commit into
mainfrom
siwe-audience-aware-challenge
Jun 29, 2026
Merged

Make the /siwe surface audience-aware for cert enrollment#15
elffjs merged 1 commit into
mainfrom
siwe-audience-aware-challenge

Conversation

@elffjs

@elffjs elffjs commented Jun 29, 2026

Copy link
Copy Markdown
Member

Why

step-ca enrolls certificates against dauth's /siwe OIDC provisioner and validates the sign-in token as an id_token, requiring its own clientID (step-ca) in the token's aud. Today /siwe mints a fixed audience from JWT_AUDIENCE (dimo), so enrollment tokens are rejected on audience before the on-chain connection webhook is ever consulted.

This makes the /siwe surface audience-aware: a caller can request a specific, allow-listed aud at challenge time, while the default behavior is unchanged. It's a minimal, additive spike to unblock enrollment — not a redesign of the audience model.

What

  • SIWE_ALLOWED_AUDIENCES (comma list) — allow-list of aud values a caller may request. Empty (default) means no override is permitted. JWT_AUDIENCE stays the required default audience.
  • POST /siwe/challenge accepts an optional audience (string array). Every value must be on the allow-list or the challenge is rejected with 400 invalid_request and no usable nonce is created. The audience is bound to the nonce at challenge time, so it can't be swapped at /token. Omitted → the configured default.
  • Issuer.Issue(account, audience) takes a per-issuance override; empty falls back to the configured default.
  • Nonce Challenge carries the bound audience. In-memory store gets it for free; Postgres gains a TEXT[] audience column with an idempotent ALTER TABLE … ADD COLUMN IF NOT EXISTS so existing deployments self-migrate.
  • The EIP-4361 challenge message format and what the wallet signs are unchanged.

Safety / invariants (verified in code)

  • /exchange data-plane unaffected — its inbound JWT middleware validates the /siwe signature only, not iss/aud (internal/exchange/middleware/auth.go). Widening the /siwe audience does not touch the authZ path.
  • Two-keyset boundary preserved/siwe still signs with the /siwe keyset alone.
  • Fail-closed — with SIWE_ALLOWED_AUDIENCES unset, any requested audience is rejected; step-ca's environment must set it for enrollment to work.

Tests

  • token: override wins / empty falls back to default.
  • server: default path, allow-listed override → aud:["step-ca"], rejected non-allow-listed → 400 with no nonce, rejected when no allow-list configured, and audience-bound-at-challenge (a /token request can't change it).
  • nonce/postgres: audience round-trips; the ALTER TABLE upgrade path was manually verified to add the column to a pre-existing old-schema table.

Docs: README env table + aud row + /siwe/challenge note updated; Swagger regenerated. OIDC discovery doc needed no change (it doesn't enumerate audiences).

🤖 Generated with Claude Code

step-ca enrolls certificates against dauth's /siwe OIDC provisioner and
validates the sign-in token as an id_token, requiring its own clientID
("step-ca") in the token's `aud`. Today /siwe mints a fixed audience from
JWT_AUDIENCE, so enrollment tokens are rejected on audience before the
on-chain webhook is ever consulted.

Let a caller request a specific, allow-listed `aud` at challenge time while
leaving the default path unchanged:

- SIWE_ALLOWED_AUDIENCES (comma list) is an allow-list of `aud` values a
  caller may request; empty means no override is permitted. JWT_AUDIENCE
  stays the required default.
- POST /siwe/challenge accepts an optional `audience` array, validated
  against the allow-list and bound to the nonce at challenge time so it
  cannot be swapped at /token. A disallowed request returns 400 and creates
  no usable nonce.
- Issuer.Issue takes a per-issuance audience override; empty falls back to
  the configured default.
- The nonce Challenge carries the bound audience; Postgres gains a TEXT[]
  audience column with an idempotent ALTER TABLE so existing deployments
  self-migrate.

The /exchange data-plane is unaffected: its inbound JWT middleware validates
the /siwe signature only (not iss/aud), and /siwe still signs with the /siwe
keyset alone, preserving the two-keyset boundary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@elffjs elffjs merged commit 28b7518 into main Jun 29, 2026
2 checks passed
@elffjs elffjs deleted the siwe-audience-aware-challenge branch June 29, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant