Microsoft Entra ID (Azure AD)
This page covers using Microsoft Entra ID as Stroom’s Identity Provider (IDP) Identity Provider (IDP) An Identity Provider is a system or service that can authenticate a user and assert their identity. Identity providers can support single sign on (SSO), which allows the user to sign in once to the Identity Provider so they are then authenticated to all systems using that IDP.Click to see more details.... Entra ID is the current name for what was Azure Active Directory, and much of the tooling and documentation still says Azure AD.
See Also
Read External IDP first for what Stroom needs from any provider, and Stroom Configuration for what each setting does.
Note
This page covers Stroom itself being the OIDC client of Entra ID. If an authenticating proxy in front of Stroom does the sign in instead, the proxy is the client and much of this page does not apply - see AWS Application Load Balancer or NGINX and oauth2-proxy.
Entra ID has two generations of endpoint, v1.0 and v2.0, which issue tokens with different issuers and different formats. Two of the three things most likely to go wrong here come from mixing them up, so it is worth being deliberate: use the v2.0 endpoints throughout.
Creating the App Registration
In the Microsoft Entra admin centre , or the Azure portal under Microsoft Entra ID:
- Go to App registrations => New registration.
- Give it a name, e.g.
Stroom. - For Supported account types choose Accounts in this organizational directory only, i.e. single tenant, unless you have a specific reason not to. This restricts sign in to your own tenant.
- Under Redirect URI select a platform of Web and enter
https://STROOM_FQDN/api/auth/flow/v1/signin-oidc. - Click Register, then note the Application (client) ID and the Directory (tenant) ID from the overview page.
Where STROOM_FQDN is the public address of Stroom, i.e. what you have set as appConfig.publicUri.
Warning
Entra ID matches redirect URIs exactly and does not accept wildcards.
It also requires https, other than for http://localhost.
Register the single exact URI above. Earlier versions of Stroom sent the user’s current page as the redirect URI; if you are upgrading, remove whatever was registered for that.
Then, still in the app registration:
- Under Authentication, add a Front-channel logout URL of
https://STROOM_FQDN/, and add the same value under Redirect URIs if your tenant requires post logout redirect URIs to be registered. - Under Certificates & secrets => Client secrets, create a new secret and copy its Value immediately, as it is only shown once.
Warning
Entra ID client secrets expire, with a maximum lifetime of 24 months. When the secret expires Stroom will stop being able to exchange authorization codes for tokens and nobody will be able to sign in.
Record the expiry date and plan the rotation, or use certificate credentials instead.
Entra ID supports PKCE, and Stroom always sends an S256 challenge, so there is nothing to configure for it.
Exposing an API for Access Tokens
This step is what makes API authentication work, and is the Entra ID equivalent of KeyCloak’s audience mapper.
If Stroom only ever asks for the openid, email and profile scopes, Entra ID issues an access token for Microsoft Graph rather than for Stroom.
Those tokens are intended only for Graph, are not in a format a third party can validate, and will fail validation at Stroom.
Interactive sign in still works throughout, because it uses the id_token.
To get an access token that Stroom can validate, the app registration has to expose an API of its own:
- Go to Expose an API => Add next to Application ID URI.
Accept the default of
api://CLIENT_ID, or set your own. - Click Add a scope, name it something like
user_impersonation, and choose who can consent. - Under Manifest, set
accessTokenAcceptedVersionto2.
Note
accessTokenAcceptedVersion defaults to null, which means v1.0.
A v1.0 access token has an issuer of https://sts.windows.net/TENANT_ID/, which does not match the v2.0 issuer that Stroom obtains from the v2.0 discovery document, so such tokens are refused.
Setting it to 2 is the clean fix.
See Issuers if you have a reason to stay on v1.0.
Callers then request that scope, e.g. api://CLIENT_ID/user_impersonation, and the resulting access token carries an aud claim that Stroom can be configured to accept.
Configuring Stroom
receive:
# Set to true to require authentication for /datafeed requests
authenticationRequired: true
# Set to true to allow authentication using an Open ID token
tokenAuthenticationEnabled: true
security:
authentication:
authenticationRequired: true
openId:
identityProviderType: EXTERNAL_IDP
# Note the '/v2.0' path part. Without it you get the v1.0 endpoints and a different issuer.
openIdConfigurationEndpoint: "https://login.microsoftonline.com/TENANT_ID/v2.0/.well-known/openid-configuration"
# The Application (client) ID from the app registration overview
clientId: "CLIENT_ID"
clientSecret: "CLIENT_SECRET"
logoutEndpoint: "https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/logout"
# Accept both the id_token audience (the client id) and the access token audience
# (the Application ID URI). Adjust to match what your tokens actually carry.
allowedAudiences:
- "CLIENT_ID"
- "api://CLIENT_ID"
# Accept tokens from both the v2.0 and the v1.0 endpoints. Setting this replaces
# Stroom's default issuer check, so the v2.0 issuer must be listed too.
validIssuers:
- "https://login.microsoftonline.com/TENANT_ID/v2.0"
- "https://sts.windows.net/TENANT_ID/"
# Entra ID only issues the 'name' and 'preferred_username' claims when 'profile'
# is requested, and Stroom's default scopes do not include it.
requestScopes:
- "openid"
- "email"
- "profile"
# 'oid' is stable across app registrations; the default of 'sub' is not.
uniqueIdentityClaim: "oid"
Replace TENANT_ID with the Directory (tenant) ID, CLIENT_ID with the Application (client) ID, and CLIENT_SECRET with the value of the client secret, all from the app registration’s Overview and Certificates & secrets pages.
Each of validIssuers, requestScopes and uniqueIdentityClaim is explained in the sections that follow.
Issuers
The v2.0 discovery endpoint advertises an issuer of https://login.microsoftonline.com/TENANT_ID/v2.0, which is a parent path of the discovery endpoint itself, so Stroom’s issuer check is satisfied with no extra configuration.
The v1.0 endpoints are not so tidy.
Their issuer is https://sts.windows.net/TENANT_ID/, which shares no base URI with the discovery endpoint, and Stroom will refuse to start with:
Issuer ‘X’ obtained from configuration endpoint Y does not share the same base URI.
If you must use v1.0, or you have v1.0 access tokens in circulation from an app registration you cannot change, list both issuers explicitly, as in the example above:
validIssuers:
- "https://login.microsoftonline.com/TENANT_ID/v2.0"
- "https://sts.windows.net/TENANT_ID/"
Stroom then accepts tokens carrying either issuer, so v1.0 and v2.0 tokens can be in use at the same time.
Warning
When validIssuers is set, the issuer advertised by the discovery document must be one of the values in it; the parent path check described above is no longer applied.
Listing only the v1.0 issuer against the v2.0 discovery endpoint will stop Stroom starting with:
Issuer ‘X’ obtained from configuration endpoint Y does not match those in the ‘issuer’ or ‘validIssuers’ properties.
Always include the v2.0 issuer alongside the v1.0 one.
The values must match exactly, including the /v2.0 suffix on one and the trailing / on the other.
Using the v2.0 endpoints and accessTokenAcceptedVersion: 2 is much the better answer, in which case validIssuers can be omitted altogether.
Note
Do not use thecommon or organizations endpoints in place of a tenant id.
Their discovery documents report an issuer containing a literal {tenantid} placeholder rather than a real value, and they allow sign in from any tenant, which is unlikely to be what you want.
Audience Validation
An Entra ID id_token carries aud set to the Application (client) ID, so interactive sign in validates against clientId with no further configuration.
An access token for your exposed API carries aud set to either the Application ID URI or the client id, depending on accessTokenAcceptedVersion and how the scope was requested.
Listing both in allowedAudiences, as above, covers either.
Warning
Do not simply setaudienceClaimRequired: false to make a rejection go away.
Entra ID does populate the audience claim, so an absent one means the token is not the one you think it is, most likely a Microsoft Graph token, and loosening the check hides that rather than fixing it.
Leave validateAudience at its default of true.
Claims
Entra ID v2.0 issues preferred_username, normally the user principal name, which Stroom uses as the display name by default, and name, which satisfies the default fullNameClaimTemplate of ${name}.
Both of those claims are only issued when the profile scope is requested.
Stroom’s default requestScopes is openid and email, which does not include it, so without the requestScopes shown in the example above users will sign in with no display name or full name.
Setting requestScopes replaces the defaults rather than adding to them, so keep openid and email in the list.
For uniqueIdentityClaim you have a choice:
| Claim | Notes |
|---|---|
sub |
The Stroom default. In Entra ID this is pairwise, i.e. a different value per application, and stable for the life of that app registration. Delete and recreate the app registration and every user’s sub changes, orphaning their Stroom user. |
oid |
The user’s object id in the directory. Stable across applications and across app registrations, so it survives a re-registration. Unique within a tenant. |
oid is the more robust choice for a single tenant deployment, and is what Microsoft’s own guidance points to as the durable identifier.
sub is fine if you are confident the app registration will not be recreated.
The example above uses oid; remove that line to keep the default of sub.
Warning
Whichever you choose, decide before the first user signs in. Changing it later means every existing Stroom user is orphaned, and their permissions and group memberships have to be reapplied to the new identities.
Do not use preferred_username, email or upn; all can be reassigned to a different person, who would then inherit the Stroom user.
Group and Role Claims
Entra ID can be configured to emit groups and roles claims.
Stroom does not consume them.
All authorisation is done with Stroom’s own users, groups and permissions, so directory group membership has no effect on what a user can do in Stroom.
Access Token Type
Leave requiredAccessTokenType unset until you have decoded the header of a real access token from your tenant and confirmed what it contains.
Setting it to a value your tokens do not use will refuse every API call.
Setting up the Admin User in Stroom
Find the identifier of the account that is to be the administrator, matching whatever you set uniqueIdentityClaim to.
If you are using oid, it is shown as the Object ID on the user’s page under Users in the Entra admin centre.
If you are using sub, it is pairwise and not shown anywhere in the portal, so you will need to decode an id_token issued for that user, or have them sign in once and read it from the Stroom logs.
Then run the following, ideally before Stroom has been started for the first time:
The command is repeatable and will skip anything that already exists, so running it against a user who has already signed in is fine. Restart Stroom afterwards if it was running, as permissions are cached.
See Also
See KeyCloak for a fuller description of what this command does, and Command Line Tools for its options.
Stroom-Proxy with Entra ID
A Stroom-Proxy obtains a token for its own service user using the client credentials grant.
Create a second app registration for the proxy, then grant it access to the API exposed by the Stroom app registration:
- In the proxy’s app registration, go to API permissions => Add a permission => My APIs and select the Stroom app registration.
- Choose Application permissions, which is the client credentials case, rather than delegated permissions.
- Have a directory administrator grant admin consent, without which the grant will fail.
Entra ID’s client credentials flow uses the .default scope of the target API:
security:
authentication:
openId:
identityProviderType: EXTERNAL_IDP
openIdConfigurationEndpoint: "https://login.microsoftonline.com/TENANT_ID/v2.0/.well-known/openid-configuration"
# The proxy's own app registration
clientId: "PROXY_CLIENT_ID"
clientSecret: "PROXY_CLIENT_SECRET"
# The Application ID URI of the Stroom app registration, not the proxy's
clientCredentialsScopes:
- "api://CLIENT_ID/.default"
Note
Stroom’s default forclientCredentialsScopes is openid, and its configuration description suggests setting openid alongside the .default scope.
Entra ID’s v2.0 client credentials flow generally accepts a .default scope on its own and rejects it being combined with others, so start with just the .default scope as above and add openid only if your tenant requires it.
The destination the proxy forwards to must accept the audience these tokens carry, which will be the Application ID URI or client id of the Stroom app registration, so make sure it appears in that destination’s allowedAudiences.
Obtaining a Token to Send Data
When a Stroom-Proxy, or Stroom itself, is configured with Entra ID as above and has receive.authenticationRequired and receive.tokenAuthenticationEnabled both set to true, anything sending data to /datafeed must present an Entra ID access token, or a certificate, with each request.
See Token Authentication for how the token is attached.
This section covers how a sending system gets that token. It is the mirror image of Stroom-Proxy with Entra ID: the sender is a client of the API exposed by the Stroom app registration in exactly the same way the proxy is, and uses the same client credentials grant.
Registering the Sender
Each sending system needs an app registration of its own, so that it has a client id and secret to authenticate with and can be granted, or revoked, individually.
- Create an app registration for the sender, e.g.
Stroom Sender - Widget Service. No redirect URI is needed as it will never sign in interactively. - Under Certificates & secrets, create a client secret and record its value.
- Under API permissions => Add a permission => My APIs, select the Stroom app registration.
- Choose Application permissions, not delegated permissions.
- Have a directory administrator grant admin consent, without which the token request will fail.
The Stroom app registration must already expose an API with accessTokenAcceptedVersion set to 2.
If it does not, the token comes back as a v1.0 token with an issuer of https://sts.windows.net/TENANT_ID/ and is refused unless that issuer is listed in validIssuers, see Issuers.
Requesting the Token
The sender asks the v2.0 token endpoint for a token using the client credentials grant.
The scope is the .default scope of the Stroom app registration’s Application ID URI, not openid or email; asking for those returns a token for Microsoft Graph that Stroom cannot validate.
Where:
TENANT_IDis the Directory (tenant) ID.SENDER_CLIENT_IDandSENDER_CLIENT_SECRETare from the sender’s app registration.STROOM_APP_ID_URIis the Application ID URI of the Stroom app registration, as shown under Expose an API. The default form isapi://<CLIENT_ID>, whereCLIENT_IDis the Stroom app registration’s client id, not the sender’s, and it is the same value used in the proxy’sclientCredentialsScopesabove.
The response is a JSON document containing access_token, token_type and expires_in; the command above extracts just the token.
A 400 response instead carries error and error_description fields, the latter with an AADSTS code and a message that says what is wrong.
The usual causes are a wrong or expired client secret, a scope that is not the .default scope of an API the Stroom app registration actually exposes, or admin consent not having been granted.
Sending Data with the Token
The token goes in the Authorization header as a bearer token, alongside the usual header arguments:
Tokens are short lived, typically around an hour, so a sender must request a fresh token when expires_in has elapsed or a request is refused with a 401, rather than caching one indefinitely.
See Also
See curl (Linux) for more on sending data with curl.
Checking the Token
If the proxy rejects the token, decode its payload before assuming the proxy is at fault:
Check that:
issishttps://login.microsoftonline.com/TENANT_ID/v2.0. If it ishttps://sts.windows.net/TENANT_ID/the Stroom app registration is still issuing v1.0 tokens, see Exposing an API for Access Tokens.audis a value that appears in the proxy’sallowedAudiences. It will be the Application ID URI or the client id of the Stroom app registration.expis in the future.
Once accepted, the sender’s identity is recorded against the received data in the UploadUserId meta attribute, taken from the claim named in uniqueIdentityClaim.
For a client credentials token that is the oid of the sender’s service principal, so it is that value, not the app registration’s display name, that you match on in Data Receipt Rules.