Authentication
All calls to the Management API should be authenticated. Any call with missing or incorrect authentication will result in a 401 error.
Authentication headers
The authentication method for this API is the HTTP Bearer method. The HTTP call should contain an Authorization header, with a Bearer JWT token. This token should be issued by the JWT issuer specified in the environment variables. See the API .envrc.example for this.
Environment variables: identity provider sync and JWT user lookup
Two environment variables control how the API relates to the identity provider (IdP) and how a validated JWT is mapped to a user in the Management API database. Both are documented in the Management API .envrc.example and can be set in deployment (for example Azure Web App application settings).
API_DISABLE_IDP_SYNC
When set to true, the Management API does not call the IdP to create, update, or delete user accounts. User rows are maintained in the database only.
Implications:
- User creation (and similar flows) must supply
identityProviderIdon the request body when creating a user, because the API will not create the user in the IdP nor fetch an IdP subject from there. IfidentityProviderIdis omitted while sync is disabled, the API responds with400 Bad Request. - When IdP sync is enabled (
API_DISABLE_IDP_SYNCunset orfalse), clients must not sendidentityProviderIdon create; the API creates the user in the IdP (or retrieves it if it already exists) and stores the returned identifier. - Initialization follows the same rule: when sync is disabled, the administrator’s external key must be provided (for example
adminIdpIdin the init payload, where applicable). - JWT signature validation (issuer, audience, JWKS) is unchanged: the API still validates Bearer tokens against the configured issuer. Disabling sync only removes provisioning calls to the IdP, not JWT verification.
- Features that require the API to create or manage IdP accounts while the caller is unauthenticated may be unavailable when sync is disabled (for example self-check-in flows that expect IdP user creation).
JWT_USER_LOOKUP_CLAIM
After the JWT signature and standard claims (issuer, audience, expiry) are validated, the API must resolve the caller to a single user row in the database. That resolution uses the value of one JWT payload claim, compared to the user’s stored identityProviderId column.
JWT_USER_LOOKUP_CLAIMnames which JWT claim provides that value. The default issub, the OIDC subject identifier. This matches the common case whereidentityProviderIdstores the IdP’s stable subject.- If you set it to another claim name (for example
email), the string value of that claim (after decoding the JWT) must exactly match theidentityProviderIdstored for the user when IdP sync is disabled (or whenever you rely on a non-subkey). There is no separate “login email” column used for this lookup. - If the chosen claim is missing, empty, or not usable as a non-empty string (for example an array of non-strings), the user is treated as unknown to the platform for that request: endpoints that require a resolved user will not succeed as that user.
- If the claim is present as a JSON array of strings (typical for Azure AD B2C
emails), the API uses the first non-empty string element when the configured claim name points at that array.
Changing JWT_USER_LOOKUP_CLAIM requires a process restart in typical deployments so the new value is picked up. Treat it as a security-sensitive setting: it defines how every authenticated request binds tokens to internal users.
Implementation
The authentication is verified using passportjs through the passport-jwt strategy. The call to the authentication provider's well-known URL is done through the helper library jwks-rsa.
Verifying with a static signing certificate
By default the signing key is resolved dynamically from the IdP's JWKS endpoint (JWT_ISSUER_WELL_KNOWN_URL), matching the token's kid header to a published key.
Some identity providers instead set kid to the name of a signing certificate rather than a JWKS key id. In that case the JWKS lookup cannot find a matching key and validation fails with 401.
When the JWT_SIGNING_CERT_PATH environment variable is set, the API verifies JWT signatures against the public key of the PEM-encoded X.509 certificate at that path, ignoring kid entirely. JWT_ISSUER_WELL_KNOWN_URL is then not used for signature verification (issuer and audience are still validated).
- The file may be PEM (
-----BEGIN CERTIFICATE-----) or DER; a raw public key is also accepted. Validate withopenssl x509 -in cert.pem -noout -text. - Accepted signature algorithms are pinned to RS256 by default and can be overridden with
JWT_ALGORITHMS(comma-separated). Pinning prevents algorithm-confusion attacks where a forgedHS256token is verified against the public key, so leave at least one asymmetric algorithm set. This applies only whenJWT_SIGNING_CERT_PATHis set; the JWKS path is unaffected. - Key rotation becomes manual: if the IdP rotates the signing certificate, the new certificate must be deployed and the process restarted. When the IdP exposes a standard JWKS endpoint with matching
kids, prefer leavingJWT_SIGNING_CERT_PATHunset so rotation is automatic.
Once the token validation has succeded, the final step is to make sure that we know who the user represents in the platform. Therefore, we retrieve the identity provider ID token from the verified JWT token and find the user in our Management API database which corresponds to this identity provider token.
If no matching user is found, the principal is not a known platform user for that request, and downstream handling will fail for routes that require an identified user (in line with missing authentication above).
If the user is found, the found User object is attached to the request for further processing. The next step is authorization.