Architecture
The B2B API uses two layered authentication tokens: a server-level Entra token that identifies your tenant, and a per-user user token that identifies the individual end-user. Read-only tenant-level endpoints need only the Entra token; user-scoped endpoints need both.
Entra authentication
Your backend holds Entra App Registration credentials (tenant ID, client ID, client secret). Before making any API call, it requests an access token from Entra using the OAuth 2.0 Client Credentials grant against your Entra tenant's token endpoint. The returned token is a signed JWT issued by Microsoft with an appid claim identifying your app registration.
Tokens are valid for approximately one hour. Your partner backend should cache the Entra token in memory and reuse it across requests — re-fetching on every API call wastes a round-trip to Microsoft and will rate-limit you under load. The recommended pattern: cache the token until fewer than 60 seconds remain before expiry, then fetch a fresh one. If a request fails with 401 invalid_entra_token, force-refresh once and retry before surfacing the error. The demo implements this pattern in src/lib/entra.ts as a reference.
User authentication
The Entra token tells Yieldforce which partner tenant is calling. It does not identify which end-user the operation targets. The user token fills that gap.
Rather than routing every user through a centralized identity provider, the B2B API allows partners to issue their own user tokens. Each partner registers a JWKS endpoint URL with Yieldforce. When a user-scoped request arrives, Yieldforce reads the iss claim from the X-User-Token header, maps it to your tenant, fetches your JWKS URL (cached), and verifies the RS256 signature. If verification passes, the sub claim is trusted as the end-user's external ID.
This design keeps end-user identity fully partner-controlled: Yieldforce never holds end-user credentials, never issues end-user tokens, and never needs to know how the partner authenticates its own users.
Dual-header pattern
Every user-scoped endpoint requires two headers simultaneously:
Authorization: Bearer <entra-token>— identifies the partner tenant. Issued by Microsoft Entra, verified by Yieldforce using the Entra JWKS.X-User-Token: <user-token>— identifies the end-user within the tenant. Issued by the partner, verified by Yieldforce using the partner's JWKS.
The two checks are independent. Both must pass for a user-scoped request to proceed. Read-only endpoints that operate at the tenant level (such as GET /v1/partner/pools) only require the Entra token.
The demo's src/lib/yieldforce.ts assembles both headers for every call: it always fetches an Entra token, and when requireUser: true is set it additionally mints or reuses a user token from the current session.
Tenant identity
When Yieldforce receives a request it resolves the tenant through two parallel paths:
- From the Entra token: the
appidclaim (the client ID of your App Registration). Yieldforce maps this to your tenant. - From the user token: the
iss(issuer) claim. Yieldforce maps this to your tenant via your registered JWKS issuer URL.
On user-scoped endpoints, both paths must resolve to the same tenant. If they disagree — for example, if someone attempts to use a user token minted for Tenant A alongside an Entra token for Tenant B — Yieldforce returns a cross_tenant_jwt error. This prevents a compromised user token from being replayed against a different tenant's data.
Wallet isolation
Each partner tenant has its own Privy application for wallet provisioning. End-user wallets created under your tenant are fully isolated from yieldforce.io consumer users and from any other partner tenant. Contact Yieldforce operations to configure your Privy app during onboarding.