Authentication & signing
Four sign-in paths — email/password, wallet signature, provider exchange, API key. Same JWT works against every YF endpoint.
Identity and signing. Every party in the system — your users, the other side of a deal, agents acting on behalf of a group — has an account backed by signing keys. Logging in returns a JWT; the same JWT works against every YF endpoint and is what the platform uses to authorise on-chain operations on behalf of the caller.
There are four sign-in paths. Browser clients use one of the first three; backend services (operators, app backends, batch jobs) use the fourth.
/auth/login.All four return the same response shape — { token, refresh_token, expires_in, user } — and the resulting JWT is interchangeable across
auth, payments, agents, and the federation gateway.
1. Email + password
LOGIN=$(curl -s -X POST $YF_AUTH/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "your-password"
}')
export TOKEN=$(echo "$LOGIN" | jq -r .token)
export REFRESH=$(echo "$LOGIN" | jq -r .refresh_token)
export USER_ID=$(echo "$LOGIN" | jq -r .user.id)
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 900,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"role": "User"
}
}
2. Wallet signature
For wallet-led users who hold their own keys. Hit the nonce endpoint,
sign the nonce off-chain (EIP-191 personal_sign), exchange.
# 1. Fetch a fresh nonce
NONCE=$(curl -s $YF_AUTH/auth/signature/nonce | jq -r .nonce)
# 2. Wallet signs $NONCE off-chain (returns signature)
# 3. Exchange
curl -s -X POST $YF_AUTH/auth/signature/signin \
-H "Content-Type: application/json" \
-d "{
\"public_key\": \"0x04…\",
\"signature\": \"0x…\",
\"message\": \"$NONCE\",
\"nonce\": \"$NONCE\"
}"
3. Provider exchange
For federated identity providers configured in this deployment. Discover what's available first, then exchange.
# Discover providers (Averer, MetaMask, Email, WebAuthn, Dynamic, …)
curl $YF_AUTH/auth/providers/config
# Per-provider exchange (shape varies — see /docs/api/auth)
curl -X POST $YF_AUTH/auth/metamask/exchange \
-H "Content-Type: application/json" \
-d '{
"address": "0x…",
"signature": "0x…",
"message": "<contains nonce>",
"chain_id": 1
}'
4. API key (the backend pattern)
For non-browser callers — your server-side code, the
yieldfabric-operator pattern, batch jobs, anything that doesn't
log a human in at boot. Don't put an email + password in env vars;
mint an API key instead. Two steps, then you're done:
Step A — Issue the key once, by hand:
# As that service account user (one-time login with any flow above)
curl -X POST $YF_AUTH/auth/api-key/generate \
-H "Authorization: Bearer $ONE_TIME_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"service_name": "my-app-backend",
"description": "Backend that submits obligations on behalf of admin"
}'
# → { "api_key": "yf_api_…", "service_name": "…", … }
The raw yf_api_… value is returned only at creation time — the
server only keeps a hash. Save it; store it as API_KEY in your
service's env / secret manager.
Step B — At boot (and on token refresh), exchange the key for a JWT:
curl -X POST $YF_AUTH/auth/api-key \
-H "Content-Type: application/json" \
-d "{\"api_key\":\"$API_KEY\"}"
# → same shape as /auth/login
Why this matters: API keys are individually revocable
(POST /auth/api-keys/{key_id}/revoke) and auditable
(GET /auth/api-keys) without rotating the underlying user's
password. The tncshell reference impl uses this pattern.
Token lifetimes
token field of the login response. Use as Authorization: Bearer.refresh_token — store the new one atomically. Using the same refresh twice returns 401.Refreshing
NEW=$(curl -s -X POST $YF_AUTH/auth/refresh \
-H "Content-Type: application/json" \
-d "{\"refresh_token\":\"$REFRESH\"}")
export TOKEN=$(echo "$NEW" | jq -r .token)
export REFRESH=$(echo "$NEW" | jq -r .refresh_token)
Resolve the caller from a JWT
curl $YF_AUTH/protected/jwt \
-H "Authorization: Bearer $TOKEN"
# → { "user_id": "...", "default_wallet_id": "...", "account_address": "0x...", "role": "...", ... }
curl $YF_AUTH/auth/users/me \
-H "Authorization: Bearer $TOKEN"
# → full user profile incl. `is_external_signer` flag
Use /protected/jwt when you just need the claims (faster, no DB
join); /auth/users/me when you want role + email + the
is_external_signer flag that drives whether on-chain operations
require a manual signature from the user's wallet.
Permissions: the auto-fallback gotcha
Every JWT carries a permissions claim listing what the holder can
do. For vault-touching operations (mint obligation, send payment,
deploy account) the executor checks for the CryptoOperations
permission. Without it you get 403 Insufficient permissions: CryptoOperations even though everything else looks right.
The JWT-mint path falls back to role-default permissions when the
user_permissions table is empty — so users with the right role get
the permission automatically:
CryptoOperations from the fallback?CryptoOperations from the fallback?CryptoOperations from the fallback?CryptoOperations from the fallback?CryptoOperations from the fallback?CryptoOperations from the fallback?For users with narrower roles, grant explicitly:
curl -X POST $YF_AUTH/auth/users/$USER_ID/permissions \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "permissions": ["CryptoOperations"] }'
Delegation — acting on behalf of a group
When an operation belongs to a group rather than a single user
(group-owned wallet, multi-party deal, working-group settlement), mint
a delegation JWT that lets a member of the group act on the
group's behalf. The user's identity stays in the audit trail; the
acting_as claim records which group the operation belongs to.
curl -X POST $YF_AUTH/auth/delegation/jwt \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"group_id": "550e8400-e29b-41d4-a716-446655440000",
"delegation_scope": ["read", "write", "manage"],
"expiry_seconds": 3600
}'
Send the resulting delegation JWT as the Bearer on operations that should run as the group. Delegation is a JWT claim, not an HTTP header — never try to "act as" via a header.
Manage delegation tokens
# List active delegations
curl $YF_AUTH/auth/delegation/tokens -H "Authorization: Bearer $TOKEN"
# Revoke a delegation
curl -X DELETE $YF_AUTH/auth/delegation/tokens/{token_id} \
-H "Authorization: Bearer $TOKEN"
JWT structure
User JWT
{
"sub": "550e8400-e29b-41d4-a716-446655440000",
"aud": ["yieldfabric"],
"exp": 1697712000,
"iat": 1697625600,
"role": "Operator",
"permissions": ["CryptoOperations", "ViewSignatureKeys"],
"session_id": "a1b2c3d4-...",
"auth_method": "jwt",
"entity_type": "user",
"email": "user@example.com",
"account_address": "0x1234567890abcdef...",
"default_wallet_id": "...",
"acting_as": null,
"delegation_scope": null
}
subrolepermissionsaccount_addressdefault_wallet_idauth_methodjwt (login) / api_key (key exchange) / signature (wallet sign-in) / delegation (delegated).Delegation JWT (additions)
{
"auth_method": "delegation",
"group_account_address": "0xabcdef1234567890...",
"acting_as": "group-id-550e8400-...",
"delegation_scope": ["CryptoOperations", "ReadGroup"],
"delegation_token_id": "c3d4e5f6-..."
}
User roles
Common permissions
CryptoOperationsViewSignatureKeysManageSignatureKeysCreateGroupCreateDelegationTokenAuth endpoints quick reference
/auth/login/auth/refresh/auth/signature/nonce/auth/signature/signin/auth/{provider}/exchange/auth/api-key/generate/auth/api-key/auth/api-keys/auth/api-keys/{id}/revoke/protected/jwt/auth/users/me/auth/delegation/jwt/auth/delegation/tokens/auth/delegation/tokens/{id}/auth/users/{id}/permissionsFull surface (~130 endpoints with OpenAPI types): auth API reference.
Error shape
Auth REST errors are flat: { "error": "<string>" } with the
information in the HTTP status. There is no nested code envelope on
this side; message strings are human-readable and may change between
releases. Route on status code:
GraphQL surfaces use a different shape — see API Reference.
See also
- Building with YieldFabric — wire-level reference covering all four sign-in paths plus the most-common operations.
- Balances — using a delegation token to query a group's wallet activity.
- Auth API reference — endpoint-level details for issuing, exchanging, rotating, and revoking API keys.