Entra authentication
Every Yieldforce B2B endpoint requires a valid Entra token in the Authorization header. This token identifies your tenant — not your end-users — and is obtained via the OAuth 2.0 Client Credentials grant against your Azure AD application.
When you need it
Every B2B endpoint requires an Entra token. Without a valid token, Yieldforce responds with HTTP 401 and one of the codes invalid_entra_token (token present but expired or malformed) or token_missing (no Authorization header at all).
Getting a token
Make a POST request to the Microsoft identity platform token endpoint for your tenant:
POST https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
Body parameters
Must be client_credentials.
The application (client) ID of your Entra App Registration.
A client secret generated for your App Registration. Treat this like a password — never commit it to source control.
The Yieldforce resource scope. Format: api://<resource-app-id>/yieldforce-api/.default. See Scope below.
const tenantId = process.env.ENTRA_TENANT_ID;
const clientId = process.env.ENTRA_CLIENT_ID;
const clientSecret = process.env.ENTRA_CLIENT_SECRET;
const scope = process.env.ENTRA_SCOPE ?? 'api://yieldforce/.default';
const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
const res = await fetch(tokenUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
scope,
}).toString(),
});
if (!res.ok) throw new Error(`Entra token request failed: ${res.status}`);
const data = (await res.json()) as { access_token: string; expires_in: number };
// data.access_token — the bearer token to include in Authorization headers
// data.expires_in — lifetime in seconds (typically 3600)Pass the token as Authorization: Bearer <access_token> on every request to Yieldforce.
Caching
Cache the token in-memory for its full TTL. Re-fetch only when fewer than 60 seconds remain before expiry — this avoids both redundant Entra round-trips and race conditions where a token expires between your cache check and the request reaching the Yieldforce backend.
The demo implements a module-level singleton cache in src/lib/entra.ts:
interface EntraCache {
token: string;
expiresAt: number; // epoch ms
}
let cache: EntraCache | undefined;The getEntraToken() function reads from this cache if expiresAt - now > 60_000 ms. On a cache miss it calls the token endpoint, then sets cache.expiresAt = now + expires_in * 1000 before returning.
Refreshing reactively
Even with proactive caching, a token may expire between your 60-second guard and the moment the request reaches Yieldforce (clock skew, long-lived idle connections, etc.). If Yieldforce returns 401 invalid_entra_token, force-refresh once and retry the original request.
The demo's src/lib/yieldforce.ts implements this pattern:
let res = await fetch(`${baseUrl}${opts.path}`, init);
if (res.status === 401) {
entra = await getEntraToken(true); // force refresh
(init.headers as Record<string, string>).Authorization = `Bearer ${entra}`;
res = await fetch(`${baseUrl}${opts.path}`, init);
}Do not retry indefinitely — a second 401 after a fresh token indicates a different problem (wrong scope, unknown tenant, etc.).
Required fields in the request
Yieldforce decodes the Entra JWT to extract the following claims. These must be present; the backend rejects tokens that are missing or mismatch.
Required claims (decoded JWT)
Your application (client) ID. Yieldforce uses this claim to look up which tenant you are. If no
registered tenant matches this value, the request fails with unknown_partner.
The Yieldforce resource App ID. Must be one of the configured audiences for your deployment. See the callout below.
Token expiry as epoch seconds. Yieldforce applies a 30-second clock tolerance, so tokens up to
30 seconds past their exp are still accepted.
Yieldforce accepts both forms of the resource App ID as valid aud values:
- URI form:
api://<resource-app-id>/yieldforce-api - GUID form:
<resource-app-id>
Use the URI form in your scope parameter (with /.default appended) — Entra will issue a token whose aud is the URI form, which Yieldforce's backend recognizes as valid.
Common errors
| HTTP | Code | Meaning | Fix |
|---|---|---|---|
401 | invalid_entra_token | Token is expired, malformed, or has an unexpected audience/issuer. | Force-refresh your cached token and retry once. If the error persists, verify your scope and ENTRA_TENANT_ID. |
401 | token_missing | No Authorization: Bearer ... header on the request. | Ensure every request includes the Authorization header. |
401 | unknown_partner | The appid claim in your Entra token does not match any registered Yieldforce tenant. | Contact Yieldforce operations to confirm your App Registration is onboarded. |
401 | cross_tenant_jwt | Your Entra token's appid resolves to a different tenant than the iss in your partner JWT. | Ensure your Entra credentials and your JWKS issuer URL are registered under the same Yieldforce tenant. |
Scope
The scope parameter must be in the format:
api://<resource-app-id>/yieldforce-api/.default
The .default suffix requests all API permissions that have been statically configured on the target resource application. Do not specify individual permission names — the Yieldforce integration uses .default exclusively.
Yieldforce operations will provide the correct <resource-app-id> when onboarding your tenant.