<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Stroom – open-id</title>
    <link>/tags/open-id/</link>
    <description>Recent content in open-id on Stroom</description>
    <generator>Hugo -- gohugo.io</generator>
    <lastBuildDate>Wed, 23 Sep 2026 00:00:00 +0000</lastBuildDate>
    
	  <atom:link href="/tags/open-id/index.xml" rel="self" type="application/rss+xml" />
    
    
      
        
      
    
    
    <item>
      <title>Docs: Stroom Configuration</title>
      <link>/docs/install-guide/setup/open-id/external-idp/stroom-configuration/</link>
      <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/external-idp/stroom-configuration/</guid>
      <description>
        
        
        &lt;p&gt;This page is the provider agnostic reference for the Stroom side of the configuration.
The provider specific pages give the values to put in it for a given &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;All of these settings live under &lt;code&gt;security.authentication.openId&lt;/code&gt; in the &lt;code&gt;config.yml&lt;/code&gt; file, beneath &lt;code&gt;appConfig&lt;/code&gt; for Stroom and &lt;code&gt;proxyConfig&lt;/code&gt; for Stroom-Proxy.
The structure is identical for both.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    &lt;code&gt;identityProviderType&lt;/code&gt; cannot be changed at runtime; the application must be restarted for a change to take effect.

&lt;/div&gt;


&lt;h2 id=&#34;a-minimal-configuration&#34;&gt;A Minimal Configuration&lt;/h2&gt;
&lt;p&gt;For most providers this is all that is needed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  security:
    authentication:
      authenticationRequired: true
      openId:
        identityProviderType: EXTERNAL_IDP
        openIdConfigurationEndpoint: &amp;quot;https://IDP_HOST/.well-known/openid-configuration&amp;quot;
        clientId: &amp;quot;StroomClient&amp;quot;
        clientSecret: &amp;quot;CLIENT_SECRET&amp;quot;
        logoutEndpoint: &amp;quot;https://IDP_HOST/logout&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;IDP_HOST&lt;/code&gt; with the host of your provider, &lt;code&gt;StroomClient&lt;/code&gt; with the client id it issued or you chose for Stroom, and &lt;code&gt;CLIENT_SECRET&lt;/code&gt; with that client&amp;rsquo;s secret.&lt;/p&gt;
&lt;p&gt;Stroom fetches the provider&amp;rsquo;s configuration document from &lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt; at startup and takes the issuer, authorization endpoint, token endpoint and JWKS URI from it.
The logout endpoint is not part of that document, so it is set separately.&lt;/p&gt;
&lt;p&gt;If you also want data receipt to be authenticated:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  receive:
    # Require authentication for /datafeed requests
    authenticationRequired: true
    # Allow authentication using an Open ID token
    tokenAuthenticationEnabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;choosing-the-identity-provider&#34;&gt;Choosing the Identity Provider&lt;/h2&gt;
&lt;h3 id=&#34;identityprovidertype&#34;&gt;&lt;code&gt;identityProviderType&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;identityProviderType: EXTERNAL_IDP
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INTERNAL_IDP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stroom&amp;rsquo;s own built in IDP. The default for Stroom, and not valid for Stroom-Proxy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXTERNAL_IDP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A 3rd party IDP. Stroom&amp;rsquo;s internal IDP can be the external IDP of a Stroom-Proxy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NO_IDP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No IDP at all. Only for a Stroom-Proxy that forwards to a downstream proxy or Stroom and authenticates with an API key or certificate.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Setting this to &lt;code&gt;EXTERNAL_IDP&lt;/code&gt; makes &lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt; mandatory; Stroom will refuse to start without it.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;



    &lt;p&gt;A &lt;code&gt;TEST_CREDENTIALS&lt;/code&gt; value existed in earlier versions and has been removed, along with the hard coded credentials behind it.
The replacement is described in &lt;a href=&#34;../../docs/install-guide/setup/open-id/test-credentials/&#34;&gt;Insecure Test Credential&lt;/a&gt;.&lt;/p&gt;


&lt;/div&gt;


&lt;h2 id=&#34;endpoints&#34;&gt;Endpoints&lt;/h2&gt;
&lt;h3 id=&#34;openidconfigurationendpoint&#34;&gt;&lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The provider&amp;rsquo;s OIDC discovery document, conventionally at &lt;code&gt;https://IDP_HOST/.well-known/openid-configuration&lt;/code&gt;.
Setting this is much the easiest approach, as Stroom reads the other endpoints from it.&lt;/p&gt;
&lt;h3 id=&#34;issuer-authendpoint-tokenendpoint-jwksuri&#34;&gt;&lt;code&gt;issuer&lt;/code&gt;, &lt;code&gt;authEndpoint&lt;/code&gt;, &lt;code&gt;tokenEndpoint&lt;/code&gt;, &lt;code&gt;jwksUri&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Set these only if you are not using a configuration endpoint, or to override a value the provider advertises incorrectly.
Anything set here takes precedence over the discovery document.&lt;/p&gt;
&lt;h3 id=&#34;logoutendpoint&#34;&gt;&lt;code&gt;logoutEndpoint&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Where Stroom sends the user to sign out at the provider.
This is not part of the discovery document, so it always has to be set by hand, and some providers do not offer one at all.&lt;/p&gt;
&lt;p&gt;If it is not set, signing out ends the Stroom session but leaves the user signed in at the provider, so their next visit signs them straight back in without being asked for credentials.&lt;/p&gt;
&lt;h3 id=&#34;logoutredirectparamname&#34;&gt;&lt;code&gt;logoutRedirectParamName&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;logoutRedirectParamName: &amp;quot;post_logout_redirect_uri&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The query parameter Stroom uses to tell the provider where to send the user after signing out.
The only permitted values are &lt;code&gt;post_logout_redirect_uri&lt;/code&gt;, the default and what the current specification says, and &lt;code&gt;redirect_uri&lt;/code&gt; for older providers.&lt;/p&gt;
&lt;h2 id=&#34;client-credentials&#34;&gt;Client Credentials&lt;/h2&gt;
&lt;h3 id=&#34;clientid-and-clientsecret&#34;&gt;&lt;code&gt;clientId&lt;/code&gt; and &lt;code&gt;clientSecret&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The client, sometimes called an application, registered at the provider.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;clientSecret&lt;/code&gt; may be left unset when the provider authenticates Stroom by mutual TLS instead of a secret.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    The client secret is a credential.
Supply it through an environment variable or your secret management system rather than committing it to &lt;code&gt;config.yml&lt;/code&gt;, and rotate it if it is ever exposed.

&lt;/div&gt;


&lt;h3 id=&#34;requestscopes&#34;&gt;&lt;code&gt;requestScopes&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;requestScopes:
  - &amp;quot;openid&amp;quot;
  - &amp;quot;email&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The scopes Stroom asks for during an interactive sign in.
Setting this replaces the defaults rather than adding to them, so include &lt;code&gt;openid&lt;/code&gt; in whatever you set.
Add &lt;code&gt;profile&lt;/code&gt; if you need the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;given_name&lt;/code&gt; or &lt;code&gt;family_name&lt;/code&gt; claims for &lt;code&gt;fullNameClaimTemplate&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;clientcredentialsscopes&#34;&gt;&lt;code&gt;clientCredentialsScopes&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;clientCredentialsScopes:
  - &amp;quot;openid&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The scopes used when Stroom or Stroom-Proxy requests a token for its own service user, rather than for a person.
Again, this replaces the default.
For Entra ID you will likely need &lt;code&gt;api://CLIENT_ID/.default&lt;/code&gt;, where &lt;code&gt;CLIENT_ID&lt;/code&gt; is that of the Stroom app registration, and possibly &lt;code&gt;openid&lt;/code&gt;; see &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/azure-ad/#stroom-proxy-with-entra-id&#34;&gt;Microsoft Entra ID&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;formtokenrequest&#34;&gt;&lt;code&gt;formTokenRequest&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;formTokenRequest: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whether the token request is sent as an HTML form body.
Some providers, Cognito among them, require this.
It is on by default and rarely needs changing.&lt;/p&gt;
&lt;h2 id=&#34;audience-validation&#34;&gt;Audience Validation&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;aud&lt;/code&gt; claim of a token names the application the token was minted for.
Checking it is what stops a token issued to some other application at the same provider being replayed against Stroom.&lt;/p&gt;
&lt;p&gt;Three settings control this.&lt;/p&gt;
&lt;h3 id=&#34;validateaudience&#34;&gt;&lt;code&gt;validateAudience&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;validateAudience: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On by default.
The audience is checked against &lt;code&gt;allowedAudiences&lt;/code&gt;, or against &lt;code&gt;clientId&lt;/code&gt; when &lt;code&gt;allowedAudiences&lt;/code&gt; is empty.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    Setting this to &lt;code&gt;false&lt;/code&gt; disables audience checking altogether and is not recommended.
Any token that any application at the same provider can obtain would then be accepted by Stroom.

&lt;/div&gt;


&lt;p&gt;With &lt;code&gt;identityProviderType: EXTERNAL_IDP&lt;/code&gt; and &lt;code&gt;validateAudience&lt;/code&gt; left on, at least one of &lt;code&gt;allowedAudiences&lt;/code&gt; or &lt;code&gt;clientId&lt;/code&gt; must be set.
Stroom refuses to start otherwise, rather than letting mandatory validation quietly become a no-op.&lt;/p&gt;
&lt;h3 id=&#34;allowedaudiences&#34;&gt;&lt;code&gt;allowedAudiences&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;allowedAudiences: []
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A set of acceptable audience values, of which a token must carry at least one.
When empty, Stroom validates against &lt;code&gt;clientId&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;Set this when the provider puts something other than the client id in the &lt;code&gt;aud&lt;/code&gt; claim of its access tokens, which is common.&lt;/p&gt;
&lt;h3 id=&#34;audienceclaimrequired&#34;&gt;&lt;code&gt;audienceClaimRequired&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;audienceClaimRequired: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On by default: a token with no &lt;code&gt;aud&lt;/code&gt; claim at all is refused.&lt;/p&gt;
&lt;p&gt;Set it to &lt;code&gt;false&lt;/code&gt; only for a provider that omits the claim from its access tokens, Cognito being the obvious example.
Doing so does not disable validation; an &lt;code&gt;aud&lt;/code&gt; claim, where one is present, still has to match.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    &lt;p&gt;The default changed from &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, and an empty &lt;code&gt;allowedAudiences&lt;/code&gt; used to mean no audience checking rather than checking against the client id.&lt;/p&gt;
&lt;p&gt;On upgrade, a deployment whose provider does not put the Stroom client id in the &lt;code&gt;aud&lt;/code&gt; claim of its access tokens will start rejecting API calls that previously worked.
Interactive sign in is unaffected, because &lt;code&gt;id_token&lt;/code&gt;s always carry the client id.&lt;/p&gt;
&lt;p&gt;The fix is either to make the provider issue the right audience, which is preferable, or to list what it does issue in &lt;code&gt;allowedAudiences&lt;/code&gt;.
See the provider pages for which applies to you.&lt;/p&gt;


&lt;/div&gt;


&lt;h2 id=&#34;token-validation&#34;&gt;Token Validation&lt;/h2&gt;
&lt;h3 id=&#34;requiredaccesstokentype&#34;&gt;&lt;code&gt;requiredAccessTokenType&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;requiredAccessTokenType: null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The JOSE &lt;code&gt;typ&lt;/code&gt; header value a token must carry to be accepted as a bearer access token on the API, for example &lt;code&gt;at+jwt&lt;/code&gt; for a provider following &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc9068&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;RFC 9068 (external link to https://datatracker.ietf.org/doc/html/rfc9068)&#34;&gt;
    &lt;span&gt;RFC 9068&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
, or &lt;code&gt;Bearer&lt;/code&gt; for KeyCloak.&lt;/p&gt;
&lt;p&gt;When set, a token of any other type, such as an &lt;code&gt;id_token&lt;/code&gt;, is refused on the API even though its signature is perfectly valid.
That prevents an &lt;code&gt;id_token&lt;/code&gt;, which is meant only to tell Stroom who signed in, being replayed as an access token.&lt;/p&gt;
&lt;p&gt;Leave it unset, the default, to accept any type.
Set it once you have confirmed what your provider actually puts in that header; decoding the header of a real access token is the reliable way to find out.&lt;/p&gt;
&lt;p&gt;This applies only to bearer tokens on the API.
It has no effect on the interactive sign in flow or on an AWS load balancer data token.&lt;/p&gt;
&lt;h3 id=&#34;validissuers&#34;&gt;&lt;code&gt;validIssuers&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;validIssuers: []
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additional issuers to accept on a token, beyond the one the provider advertises.
A token is accepted if its &lt;code&gt;iss&lt;/code&gt; claim matches either the advertised issuer or any value listed here, so this is how to accept tokens from two generations of endpoint at once, e.g. Entra ID v1.0 and v2.0.&lt;/p&gt;
&lt;p&gt;Stroom also checks at startup that the issuer in the provider&amp;rsquo;s configuration response is consistent with &lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt;.
When &lt;code&gt;validIssuers&lt;/code&gt; is empty, the advertised issuer must be a parent path of that endpoint.
When &lt;code&gt;validIssuers&lt;/code&gt; is set, the advertised issuer must instead be one of the listed values, and the parent path check is not applied.
So where a provider legitimately reports an issuer that is not a parent path of the endpoint, list it here, and whenever you set this property for any other reason, include the advertised issuer alongside the others or Stroom will not start.
Values are compared exactly, so trailing slashes matter.&lt;/p&gt;
&lt;h3 id=&#34;signature-algorithms&#34;&gt;Signature Algorithms&lt;/h3&gt;
&lt;p&gt;Not configurable.
Stroom accepts RS256/384/512, PS256/384/512 and ES256/384/512, and refuses unsigned tokens and tokens signed with an HMAC algorithm.&lt;/p&gt;
&lt;p&gt;There is no reason to expect a mainstream provider to fall foul of this.&lt;/p&gt;
&lt;h2 id=&#34;claims&#34;&gt;Claims&lt;/h2&gt;
&lt;h3 id=&#34;uniqueidentityclaim&#34;&gt;&lt;code&gt;uniqueIdentityClaim&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;uniqueIdentityClaim: &amp;quot;sub&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The claim used to link an identity at the provider to a Stroom user.
It must be unique at the provider and must never change for a given person, which is why &lt;code&gt;sub&lt;/code&gt; is the default and normally the right answer.&lt;/p&gt;
&lt;h3 id=&#34;userdisplaynameclaim&#34;&gt;&lt;code&gt;userDisplayNameClaim&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;userDisplayNameClaim: &amp;quot;preferred_username&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A friendlier name for the user in the Stroom UI.
Not used for identity, so it need not be unique and may change.&lt;/p&gt;
&lt;p&gt;Change it if your provider does not issue &lt;code&gt;preferred_username&lt;/code&gt;; &lt;code&gt;email&lt;/code&gt; is the usual alternative.&lt;/p&gt;
&lt;h3 id=&#34;fullnameclaimtemplate&#34;&gt;&lt;code&gt;fullNameClaimTemplate&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;fullNameClaimTemplate: &#39;${name}&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Builds the user&amp;rsquo;s full name from claim values, for example &lt;code&gt;&#39;${given_name} ${family_name}&#39;&lt;/code&gt;.
Claim names are case sensitive.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    Use single quotes in the YAML file, otherwise the &lt;code&gt;${...}&lt;/code&gt; variables are expanded when the configuration file is loaded rather than when a user signs in.

&lt;/div&gt;


&lt;h2 id=&#34;aws-load-balancer-authentication&#34;&gt;AWS Load Balancer Authentication&lt;/h2&gt;
&lt;p&gt;These apply when an AWS Application Load Balancer in front of Stroom performs the authentication and passes the result on in an &lt;code&gt;x-amzn-oidc-data&lt;/code&gt; header.&lt;/p&gt;
&lt;h3 id=&#34;expectedsignerprefixes&#34;&gt;&lt;code&gt;expectedSignerPrefixes&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;expectedSignerPrefixes: []
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Amazon Resource Names of the load balancer(s) fronting Stroom, used to verify the &lt;code&gt;signer&lt;/code&gt; in the JWT header.
Each value is the first N characters of an ARN and must include at least everything up to the colon after the account id, i.e. &lt;code&gt;arn:aws:elasticloadbalancing:region-code:account-id:&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;publickeyuripattern&#34;&gt;&lt;code&gt;publicKeyUriPattern&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;publicKeyUriPattern: &#39;https://public-keys.auth.elb.${awsRegion}.amazonaws.com/${keyId}&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pattern used to build the URI the load balancer&amp;rsquo;s public key is fetched from.
Supports the &lt;code&gt;${awsRegion}&lt;/code&gt; and &lt;code&gt;${keyId}&lt;/code&gt; variables, each of which may appear more than once.
Use single quotes, as with &lt;code&gt;fullNameClaimTemplate&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;stroom-proxy&#34;&gt;Stroom-Proxy&lt;/h2&gt;
&lt;p&gt;Stroom-Proxy takes the same &lt;code&gt;security.authentication.openId&lt;/code&gt; block, under &lt;code&gt;proxyConfig&lt;/code&gt;.
&lt;code&gt;identityProviderType: INTERNAL_IDP&lt;/code&gt; is not valid for a proxy; use &lt;code&gt;EXTERNAL_IDP&lt;/code&gt;, or &lt;code&gt;NO_IDP&lt;/code&gt; where the proxy has no OIDC infrastructure available to it.&lt;/p&gt;
&lt;p&gt;A proxy has no interactive users, so the settings concerned with the sign in flow, i.e. the redirect URIs, &lt;code&gt;requestScopes&lt;/code&gt; and the claim settings, do not come into play.
What it needs is the ability to obtain a token for its own service user via the client credentials grant, and to validate tokens on data it receives.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  receive:
    # Require authentication for /datafeed requests
    authenticationRequired: true
    # Allow authentication using an Open ID token
    tokenAuthenticationEnabled: true
  security:
    authentication:
      openId:
        identityProviderType: EXTERNAL_IDP
        openIdConfigurationEndpoint: &amp;quot;https://IDP_HOST/.well-known/openid-configuration&amp;quot;
        clientId: &amp;quot;StroomProxyClient&amp;quot;
        clientSecret: &amp;quot;CLIENT_SECRET&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where the proxy forwards data to another proxy or to Stroom, it can attach a token for its service user, provided the destination is configured against the same provider:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  forwardHttpDestinations:
      # Adds a token for the service user to the request
    - addOpenIdAccessToken: true
      enabled: true
      name: &amp;quot;downstream&amp;quot;
      forwardUrl: &amp;quot;http://somehost/stroom/datafeed&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The client used by the proxy needs the client credentials grant enabled at the provider, and the destination must be willing to accept the audience that grant produces.
Not every provider supports issuing an OIDC token for a client credentials grant, so check the provider page.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;See &lt;a href=&#34;../../docs/install-guide/configuration/stroom-and-proxy/common-configuration/#open-id-configuration&#34;&gt;Common Configuration&lt;/a&gt; for this configuration block in the context of the whole file.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;troubleshooting&#34;&gt;Troubleshooting&lt;/h2&gt;
&lt;!--
The headings below describe a symptom rather than naming a thing, so they read
as sentences and are deliberately in sentence case. Each is opted out of the
style check individually so that the rest of the section is still checked.
--&gt;
&lt;!-- style-check: disable --&gt;
&lt;h3 id=&#34;stroom-will-not-start&#34;&gt;Stroom will not start&lt;/h3&gt;
&lt;!-- style-check: enable --&gt;
&lt;blockquote&gt;
&lt;p&gt;If &lt;code&gt;identityProviderType&lt;/code&gt; is set to &amp;lsquo;EXTERNAL&amp;rsquo;, property &lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt; must be set.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;EXTERNAL_IDP&lt;/code&gt; requires a discovery endpoint.
If your provider genuinely has none, you cannot use this validation route; set the individual endpoints instead and raise it as an issue.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When &lt;code&gt;identityProviderType&lt;/code&gt; is EXTERNAL_IDP and &lt;code&gt;validateAudience&lt;/code&gt; is true (the default), you must configure either &lt;code&gt;allowedAudiences&lt;/code&gt; or &lt;code&gt;clientId&lt;/code&gt;&amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Stroom will not start with audience validation switched on and nothing to validate against, rather than let the check quietly become a no-op.
Set &lt;code&gt;clientId&lt;/code&gt;, which you almost certainly want anyway, or &lt;code&gt;allowedAudiences&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Issuer &amp;lsquo;X&amp;rsquo; obtained from configuration endpoint Y does not share the same base URI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The provider is advertising an issuer that is not a parent path of the endpoint the document was fetched from, which the OIDC discovery specification says it should be.
Some providers do not follow this.
Where the value is genuinely correct for your provider, add it to &lt;code&gt;validIssuers&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Issuer &amp;lsquo;X&amp;rsquo; obtained from configuration endpoint Y does not match those in the &amp;lsquo;issuer&amp;rsquo; or &amp;lsquo;validIssuers&amp;rsquo; properties.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You have set &lt;code&gt;issuer&lt;/code&gt; or &lt;code&gt;validIssuers&lt;/code&gt;, and what the provider advertised is not among them.
Correct the configured value, or add the advertised one.&lt;/p&gt;
&lt;!-- style-check: disable --&gt;
&lt;h3 id=&#34;the-provider-refuses-the-sign-in&#34;&gt;The provider refuses the sign in&lt;/h3&gt;
&lt;!-- style-check: enable --&gt;
&lt;p&gt;An error at the provider, before the user gets back to Stroom, is almost always the redirect URI.
Check that &lt;code&gt;https://STROOM_FQDN/api/auth/flow/v1/signin-oidc&lt;/code&gt; is registered exactly, using the same scheme, host, port and path prefix as &lt;code&gt;appConfig.publicUri&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is the single most common problem when upgrading, because Stroom used to send a different redirect URI for every page.&lt;/p&gt;
&lt;!-- style-check: disable --&gt;
&lt;h3 id=&#34;sign-in-works-but-api-calls-are-refused&#34;&gt;Sign in works but API calls are refused&lt;/h3&gt;
&lt;!-- style-check: enable --&gt;
&lt;p&gt;Interactive sign in validates the &lt;code&gt;id_token&lt;/code&gt;, whereas the API validates an access token, and providers treat the two differently.
So sign in working tells you the client id, secret and endpoints are all correct, and points at the token validation settings.&lt;/p&gt;
&lt;p&gt;In order of likelihood:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Audience.&lt;/strong&gt;
The access token&amp;rsquo;s &lt;code&gt;aud&lt;/code&gt; claim does not match &lt;code&gt;clientId&lt;/code&gt; or &lt;code&gt;allowedAudiences&lt;/code&gt;, or the token has no &lt;code&gt;aud&lt;/code&gt; claim and &lt;code&gt;audienceClaimRequired&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;.
See &lt;a href=&#34;#audience-validation&#34;&gt;Audience validation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token type.&lt;/strong&gt;
&lt;code&gt;requiredAccessTokenType&lt;/code&gt; is set to something the provider does not put in the token&amp;rsquo;s &lt;code&gt;typ&lt;/code&gt; header.
Unset it, or correct it to the value the provider actually uses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token type, the other way round.&lt;/strong&gt;
The caller is presenting an &lt;code&gt;id_token&lt;/code&gt; rather than an access token.
Setting &lt;code&gt;requiredAccessTokenType&lt;/code&gt; is what catches this.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Enable debug logging for &lt;code&gt;stroom.security.common.impl.StandardJwtContextFactory&lt;/code&gt; to see the issuers, audiences and settings actually in use when a token is validated.&lt;/p&gt;
&lt;!-- style-check: disable --&gt;
&lt;h3 id=&#34;users-sign-in-but-can-see-nothing&#34;&gt;Users sign in but can see nothing&lt;/h3&gt;
&lt;!-- style-check: enable --&gt;
&lt;p&gt;That is expected for a new user.
Authentication is all the provider does; permissions are granted in Stroom, and a new user has none.
See &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/#users-and-permissions&#34;&gt;Users and permissions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If an administrator you set up with &lt;code&gt;manage_users&lt;/code&gt; cannot see anything either, remember that permissions are cached, so a restart may be needed if Stroom was running when the command was issued.&lt;/p&gt;
&lt;!-- style-check: disable --&gt;
&lt;h3 id=&#34;signing-out-does-not-sign-the-user-out-of-the-provider&#34;&gt;Signing out does not sign the user out of the provider&lt;/h3&gt;
&lt;!-- style-check: enable --&gt;
&lt;p&gt;Either &lt;code&gt;logoutEndpoint&lt;/code&gt; is unset, or the provider has no OIDC sign out endpoint, as is the case for Google.
The Stroom session ends either way, but the provider&amp;rsquo;s session does not, so the user&amp;rsquo;s next visit signs them straight back in.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: AWS Application Load Balancer</title>
      <link>/docs/install-guide/setup/open-id/edge-proxy/aws-alb/</link>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/edge-proxy/aws-alb/</guid>
      <description>
        
        
        &lt;p&gt;In this deployment the &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;Application Load Balancer (external link to https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html)&#34;&gt;
    &lt;span&gt;Application Load Balancer&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 is the Open ID Connect Relying Party.
Its listener rule sends unauthenticated browsers to the &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, completes the code flow, holds the session in &lt;code&gt;AWSELBAuthSessionCookie&lt;/code&gt; cookies, and forwards each authenticated request to Stroom with three extra headers:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;Contents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x-amzn-oidc-data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The user&amp;rsquo;s claims as a JWT, &lt;strong&gt;signed by the ALB&lt;/strong&gt; with a regional AWS key (ES256)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x-amzn-oidc-accesstoken&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The access token from the IDP, in plain text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x-amzn-oidc-identity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;sub&lt;/code&gt; claim, in plain text&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Stroom authenticates the request by verifying the &lt;code&gt;x-amzn-oidc-data&lt;/code&gt; signature against AWS&amp;rsquo;s regional public key endpoint, checking the token&amp;rsquo;s issuer against the configured one, and checking that the signing load balancer is one of yours.&lt;/p&gt;
&lt;p&gt;The ALB can authenticate against an Amazon Cognito user pool (an &lt;code&gt;authenticate-cognito&lt;/code&gt; rule) or against any OIDC provider directly (an &lt;code&gt;authenticate-oidc&lt;/code&gt; rule).
Stroom does not care which: the token it verifies is minted and signed by the ALB either way.
What the IDP changes is covered in &lt;a href=&#34;#what-depends-on-the-idp&#34;&gt;What Depends on the IDP&lt;/a&gt;, with worked examples for &lt;a href=&#34;#amazon-cognito&#34;&gt;Cognito&lt;/a&gt; and &lt;a href=&#34;#microsoft-entra-id&#34;&gt;Entra ID&lt;/a&gt; below.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;Read &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/&#34;&gt;Edge Proxy RP&lt;/a&gt; first for the model, the path scoping rules and the trust prerequisites.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;how-the-alb-builds-the-token&#34;&gt;How the ALB Builds the Token&lt;/h2&gt;
&lt;p&gt;It matters where the claims in &lt;code&gt;x-amzn-oidc-data&lt;/code&gt; come from, because it is not where the &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;External IDP&lt;/a&gt; pages assume.&lt;/p&gt;
&lt;p&gt;After exchanging the authorization code, the ALB sends the access token to the IDP&amp;rsquo;s &lt;strong&gt;user info endpoint&lt;/strong&gt; and signs the claims that come back.
The ID token is discarded; nothing from it reaches Stroom.
The payload therefore contains only what the IDP&amp;rsquo;s user info endpoint returns, which is typically a small subset of the ID token&amp;rsquo;s claims, plus an &lt;code&gt;iss&lt;/code&gt; and &lt;code&gt;exp&lt;/code&gt; that the ALB adds.&lt;/p&gt;
&lt;p&gt;Two consequences for the Stroom configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;uniqueIdentityClaim&lt;/code&gt; and &lt;code&gt;userDisplayNameClaim&lt;/code&gt; must name claims that the &lt;strong&gt;user info endpoint&lt;/strong&gt; returns.
A claim that is only in the ID token, such as Entra ID&amp;rsquo;s &lt;code&gt;oid&lt;/code&gt;, cannot be used.&lt;/li&gt;
&lt;li&gt;Stroom checks the payload&amp;rsquo;s &lt;code&gt;iss&lt;/code&gt; against the issuer from &lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt;, so the issuer the ALB is configured with must be exactly the one the IDP&amp;rsquo;s discovery document advertises.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;load-balancer-setup&#34;&gt;Load Balancer Setup&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;authenticate-cognito&lt;/code&gt; and &lt;code&gt;authenticate-oidc&lt;/code&gt; actions are only supported on an &lt;strong&gt;HTTPS&lt;/strong&gt; listener.&lt;/p&gt;
&lt;p&gt;Order the listener rules so machine traffic and the post logout page are served &lt;em&gt;without&lt;/em&gt; authentication, then authenticate everything else:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Paths &lt;code&gt;/datafeed*&lt;/code&gt;, &lt;code&gt;/stroom/datafeed*&lt;/code&gt;, &lt;code&gt;/remoting/*&lt;/code&gt;, &lt;code&gt;/status&lt;/code&gt; → &lt;strong&gt;forward&lt;/strong&gt; to the Stroom target group.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Path &lt;code&gt;/signed-out&lt;/code&gt; → &lt;strong&gt;fixed-response&lt;/strong&gt; with status &lt;code&gt;200&lt;/code&gt;, content type &lt;code&gt;text/html&lt;/code&gt; and body:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;p&amp;gt;You have been signed out.&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;/&amp;quot;&amp;gt;Sign in again&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Default → &lt;strong&gt;authenticate-cognito&lt;/strong&gt; or &lt;strong&gt;authenticate-oidc&lt;/strong&gt; (see the IDP sections below) then &lt;strong&gt;forward&lt;/strong&gt; to the Stroom target group.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;code&gt;/signed-out&lt;/code&gt; rule is the landing page for &lt;a href=&#34;#logout&#34;&gt;logout&lt;/a&gt;.
Stroom does not serve one of its own, and it must be a page the authenticate rule does not cover, so the load balancer answers it directly.&lt;/p&gt;
&lt;p&gt;Points worth knowing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SessionCookieName&lt;/code&gt; defaults to &lt;code&gt;AWSELBAuthSessionCookie&lt;/code&gt;; if you change it, change &lt;code&gt;edgeAuthentication.logout.cookiesToExpire&lt;/code&gt; to match.&lt;/li&gt;
&lt;li&gt;The session cookie is sharded at 4KB per shard (&lt;code&gt;-0&lt;/code&gt;, &lt;code&gt;-1&lt;/code&gt;, &amp;hellip;), which is why the &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/#request-header-sizes&#34;&gt;header size limit&lt;/a&gt; matters.&lt;/li&gt;
&lt;li&gt;If the total claims and access token exceed 11KB the ALB itself returns HTTP 500 and increments its &lt;code&gt;ELBAuthUserClaimsSizeExceeded&lt;/code&gt; metric — trim what the IDP puts in the token if you hit this.&lt;/li&gt;
&lt;li&gt;The ALB holds the IDP client secret, so the secret&amp;rsquo;s rotation is now a listener rule change, not a Stroom one.&lt;/li&gt;
&lt;li&gt;Restrict the Stroom target&amp;rsquo;s security group to accept traffic only from the ALB&amp;rsquo;s security group; this is &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/#trust-prerequisites&#34;&gt;trust prerequisite one&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;stroom-configuration&#34;&gt;Stroom Configuration&lt;/h2&gt;
&lt;p&gt;The parts that do not depend on the IDP:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;server:
  applicationConnectors:
    - type: http
      port: 8080
      useForwardedHeaders: true
      maxRequestHeaderSize: 32KiB

appConfig:
  publicUri: &amp;quot;https://STROOM_FQDN&amp;quot;    # the ALB&#39;s public address
  security:
    authentication:
      edgeAuthentication:
        enabled: true
        logout:
          cookiesToExpire: [ &amp;quot;AWSELBAuthSessionCookie&amp;quot; ]
          signOutUrl: &amp;quot;IDP_SIGN_OUT_URL&amp;quot;    # see the IDP sections below
      openId:
        identityProviderType: EXTERNAL_IDP
        # The IDP&#39;s discovery document; supplies the issuer that x-amzn-oidc-data is
        # checked against. Stroom runs no flow of its own, so no clientSecret is needed.
        openIdConfigurationEndpoint: &amp;quot;IDP_DISCOVERY_URL&amp;quot;
        # The ALB&#39;s client at the IDP.
        clientId: &amp;quot;ALB_CLIENT_ID&amp;quot;
        # MANDATORY - pins the JWT&#39;s &#39;signer&#39; header to your load balancer(s).
        # Without it, every x-amzn-oidc-data token is rejected. Each value must reach at
        # least the account id; use the full ALB ARN where you know it.
        expectedSignerPrefixes:
          - &amp;quot;arn:aws:elasticloadbalancing:REGION:ACCOUNT_ID:&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;expectedsignerprefixes&#34;&gt;&lt;code&gt;expectedSignerPrefixes&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The regional AWS endpoint that Stroom fetches verification keys from serves the keys of &lt;strong&gt;every&lt;/strong&gt; load balancer in that region, so the signature alone proves a token came from &lt;em&gt;an&lt;/em&gt; ALB, not from &lt;em&gt;your&lt;/em&gt; ALB.
This setting closes that gap: the &lt;code&gt;signer&lt;/code&gt; field in the token&amp;rsquo;s header, which is the signing load balancer&amp;rsquo;s ARN, must start with one of the configured values.&lt;/p&gt;
&lt;p&gt;It is required — with it unset, every ALB token is rejected, and the log message names this property.&lt;/p&gt;
&lt;h3 id=&#34;publickeyuripattern&#34;&gt;&lt;code&gt;publicKeyUriPattern&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The default value fetches keys from &lt;code&gt;https://public-keys.auth.elb.${awsRegion}.amazonaws.com/${keyId}&lt;/code&gt;, which is correct for the commercial AWS regions.
AWS GovCloud serves the keys from different, S3 hosted endpoints, so GovCloud deployments must override it, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;        publicKeyUriPattern: &amp;quot;https://s3-us-gov-west-1.amazonaws.com/\
aws-elb-public-keys-prod-us-gov-west-1/${keyId}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;what-depends-on-the-idp&#34;&gt;What Depends on the IDP&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Amazon Cognito&lt;/th&gt;
&lt;th&gt;Microsoft Entra ID&lt;/th&gt;
&lt;th&gt;Other OIDC provider&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ALB action&lt;/td&gt;
&lt;td&gt;&lt;code&gt;authenticate-cognito&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;authenticate-oidc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;authenticate-oidc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client registration&lt;/td&gt;
&lt;td&gt;User pool app client&lt;/td&gt;
&lt;td&gt;App registration&lt;/td&gt;
&lt;td&gt;Per provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claims in &lt;code&gt;x-amzn-oidc-data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v2.0: &lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;given_name&lt;/code&gt;, &lt;code&gt;family_name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;; v1.0: &lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;oid&lt;/code&gt;, &lt;code&gt;tid&lt;/code&gt;, name claims&lt;/td&gt;
&lt;td&gt;Whatever the user info endpoint returns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uniqueIdentityClaim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt; (default)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt; (default), or &lt;code&gt;oid&lt;/code&gt; on v1.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt; (default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;userDisplayNameClaim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;username&lt;/code&gt; or &lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;email&lt;/code&gt; or &lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check the user info claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;signOutUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hosted UI &lt;code&gt;/logout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;oauth2/v2.0/logout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The end session endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The default &lt;code&gt;userDisplayNameClaim&lt;/code&gt; of &lt;code&gt;preferred_username&lt;/code&gt; is not returned by either Cognito&amp;rsquo;s or Entra ID&amp;rsquo;s user info endpoint, so set it explicitly, or users will sign in with no display name.&lt;/p&gt;
&lt;h2 id=&#34;logout&#34;&gt;Logout&lt;/h2&gt;
&lt;p&gt;AWS documents ending an ALB session as the application&amp;rsquo;s job: expire the session cookies and send the browser to the IDP&amp;rsquo;s logout endpoint.
The Stroom configuration above does exactly that — &lt;code&gt;cookiesToExpire&lt;/code&gt; removes the &lt;code&gt;AWSELBAuthSessionCookie&lt;/code&gt; shards and &lt;code&gt;signOutUrl&lt;/code&gt; sends the browser to the IDP.&lt;/p&gt;
&lt;p&gt;The IDP then returns the browser to a landing page, which is the &lt;code&gt;/signed-out&lt;/code&gt; fixed response in the &lt;a href=&#34;#load-balancer-setup&#34;&gt;listener rules&lt;/a&gt;.
Two things must be true of it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It must be served by a rule that comes &lt;strong&gt;before&lt;/strong&gt; the authenticate rule, or the sign in flow simply restarts and the user never appears to sign out.&lt;/li&gt;
&lt;li&gt;Its full URL, &lt;code&gt;https://STROOM_FQDN/signed-out&lt;/code&gt;, must be registered with the IDP as an allowed post logout destination; the sections below say where.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;amazon-cognito&#34;&gt;Amazon Cognito&lt;/h2&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;The &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/cognito/&#34;&gt;Amazon Cognito&lt;/a&gt; page covers creating the user pool; this section covers what is different when the ALB, not Stroom, is the client.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&#34;cognito-setup&#34;&gt;Cognito Setup&lt;/h3&gt;
&lt;p&gt;Create a user pool, hosted UI domain and app client as described on the &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/cognito/&#34;&gt;Cognito&lt;/a&gt; page, with these differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The app client belongs to the &lt;strong&gt;ALB&lt;/strong&gt;, not to Stroom, so its allowed callback URL is the ALB&amp;rsquo;s own:
&lt;code&gt;https://STROOM_FQDN/oauth2/idpresponse&lt;/code&gt;
(this fixed path is handled by the load balancer itself and never reaches Stroom).&lt;/li&gt;
&lt;li&gt;The client must have a &lt;strong&gt;client secret&lt;/strong&gt; and use the code grant; the ALB requires both.&lt;/li&gt;
&lt;li&gt;Register &lt;code&gt;https://STROOM_FQDN/signed-out&lt;/code&gt; as an allowed &lt;strong&gt;sign out URL&lt;/strong&gt; for the client.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No second app client for Stroom is needed.
The ALB is the only OIDC client in this topology.&lt;/p&gt;
&lt;h3 id=&#34;listener-rule&#34;&gt;Listener Rule&lt;/h3&gt;
&lt;p&gt;The default rule is an &lt;strong&gt;authenticate-cognito&lt;/strong&gt; action naming your user pool, app client and hosted UI domain, followed by a forward to the Stroom target group.
Cognito&amp;rsquo;s default &lt;code&gt;openid&lt;/code&gt; scope returns an ID token, which the ALB needs to complete the flow; &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;profile&lt;/code&gt; add the claims of the same name.&lt;/p&gt;
&lt;h3 id=&#34;stroom-configuration-for-cognito&#34;&gt;Stroom Configuration for Cognito&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;      edgeAuthentication:
        enabled: true
        logout:
          cookiesToExpire: [ &amp;quot;AWSELBAuthSessionCookie&amp;quot; ]
          signOutUrl: &amp;quot;https://MY_DOMAIN.auth.REGION.amazoncognito.com/logout?\
client_id=ALB_CLIENT_ID&amp;amp;logout_uri=https://STROOM_FQDN/signed-out&amp;quot;
      openId:
        identityProviderType: EXTERNAL_IDP
        openIdConfigurationEndpoint: &amp;quot;https://cognito-idp.REGION.amazonaws.com/\
POOL_ID/.well-known/openid-configuration&amp;quot;
        clientId: &amp;quot;ALB_CLIENT_ID&amp;quot;
        expectedSignerPrefixes:
          - &amp;quot;arn:aws:elasticloadbalancing:REGION:ACCOUNT_ID:&amp;quot;
        # Cognito&#39;s user info endpoint returns &#39;username&#39; and &#39;email&#39;, not &#39;preferred_username&#39;.
        userDisplayNameClaim: &amp;quot;username&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default &lt;code&gt;uniqueIdentityClaim&lt;/code&gt; of &lt;code&gt;sub&lt;/code&gt; is correct and stable.&lt;/p&gt;
&lt;h2 id=&#34;microsoft-entra-id&#34;&gt;Microsoft Entra ID&lt;/h2&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;The &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/azure-ad/&#34;&gt;Microsoft Entra ID&lt;/a&gt; page covers Entra ID generally; this section covers what is different when the ALB, not Stroom, is the client.
Much of that page does not apply here, as explained below.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&#34;app-registration&#34;&gt;App Registration&lt;/h3&gt;
&lt;p&gt;Create an app registration as described under &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/azure-ad/#creating-the-app-registration&#34;&gt;Creating the App Registration&lt;/a&gt;, with these differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The registration belongs to the &lt;strong&gt;ALB&lt;/strong&gt;, so the Web platform redirect URI is &lt;code&gt;https://STROOM_FQDN/oauth2/idpresponse&lt;/code&gt;, not Stroom&amp;rsquo;s &lt;code&gt;signin-oidc&lt;/code&gt; callback.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;https://STROOM_FQDN/signed-out&lt;/code&gt; as a second Web redirect URI; Entra ID requires the &lt;code&gt;post_logout_redirect_uri&lt;/code&gt; to be registered.&lt;/li&gt;
&lt;li&gt;A client secret is required, and it goes in the listener rule rather than Stroom&amp;rsquo;s configuration.
Entra ID secrets expire, with a maximum lifetime of 24 months; when this one does, the ALB can no longer complete sign in for anyone, so record the date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/azure-ad/#exposing-an-api-for-access-tokens&#34;&gt;Exposing an API for Access Tokens&lt;/a&gt; step is &lt;strong&gt;not&lt;/strong&gt; needed for browser sign in.
The access token Entra ID gives the ALB is a Microsoft Graph token, and the ALB only uses it to call Graph&amp;rsquo;s user info endpoint, which is exactly what it is for.
It is still needed if Stroom-Proxies or other machine clients obtain Entra ID tokens to present to Stroom directly, as that traffic does not go through the ALB&amp;rsquo;s authenticate rule.&lt;/p&gt;
&lt;p&gt;Use a single tenant registration.
The example below uses the &lt;strong&gt;v2.0&lt;/strong&gt; endpoints, as the Entra ID page recommends, but behind an ALB the choice of endpoint generation decides which claims Stroom gets, so read &lt;a href=&#34;#identity-claims&#34;&gt;Identity Claims&lt;/a&gt; before settling on it.&lt;/p&gt;
&lt;h3 id=&#34;listener-rule-1&#34;&gt;Listener Rule&lt;/h3&gt;
&lt;p&gt;The default rule is an &lt;strong&gt;authenticate-oidc&lt;/strong&gt; action, followed by a forward to the Stroom target group.
Entra ID&amp;rsquo;s discovery document does not need to be, and cannot be, given to the ALB; the endpoints are entered individually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;Type&amp;quot;: &amp;quot;authenticate-oidc&amp;quot;,
    &amp;quot;AuthenticateOidcConfig&amp;quot;: {
        &amp;quot;Issuer&amp;quot;: &amp;quot;https://login.microsoftonline.com/TENANT_ID/v2.0&amp;quot;,
        &amp;quot;AuthorizationEndpoint&amp;quot;: &amp;quot;https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/authorize&amp;quot;,
        &amp;quot;TokenEndpoint&amp;quot;: &amp;quot;https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token&amp;quot;,
        &amp;quot;UserInfoEndpoint&amp;quot;: &amp;quot;https://graph.microsoft.com/oidc/userinfo&amp;quot;,
        &amp;quot;ClientId&amp;quot;: &amp;quot;ALB_CLIENT_ID&amp;quot;,
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;ALB_CLIENT_SECRET&amp;quot;,
        &amp;quot;Scope&amp;quot;: &amp;quot;openid profile email offline_access&amp;quot;,
        &amp;quot;OnUnauthenticatedRequest&amp;quot;: &amp;quot;authenticate&amp;quot;
    },
    &amp;quot;Order&amp;quot;: 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Issuer&lt;/code&gt; must be &lt;strong&gt;exactly&lt;/strong&gt; &lt;code&gt;https://login.microsoftonline.com/TENANT_ID/v2.0&lt;/code&gt;, with the &lt;code&gt;/v2.0&lt;/code&gt; and no trailing slash.
It becomes the &lt;code&gt;iss&lt;/code&gt; claim of &lt;code&gt;x-amzn-oidc-data&lt;/code&gt;, and Stroom checks it against the issuer from the v2.0 discovery document, so any difference rejects every request.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Scope&lt;/code&gt; must include &lt;code&gt;profile&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;, otherwise the user info endpoint returns only &lt;code&gt;sub&lt;/code&gt;.
&lt;code&gt;offline_access&lt;/code&gt; makes Entra ID issue a refresh token, which the ALB uses to renew the session silently; without it every user is bounced back through Entra ID when the access token expires, typically hourly.&lt;/li&gt;
&lt;li&gt;The user info endpoint is on &lt;code&gt;graph.microsoft.com&lt;/code&gt;, not &lt;code&gt;login.microsoftonline.com&lt;/code&gt;, so the ALB needs outbound access to both.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;identity-claims&#34;&gt;Identity Claims&lt;/h3&gt;
&lt;p&gt;The v1.0 and v2.0 generations of Entra ID endpoint have &lt;strong&gt;different user info endpoints&lt;/strong&gt;, and they return different claims.
Because the ALB signs the user info claims and discards the ID token, this decides which identity claims Stroom can use:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;v2.0 endpoints&lt;/th&gt;
&lt;th&gt;v1.0 endpoints&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User info endpoint&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://graph.microsoft.com/oidc/userinfo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://login.microsoftonline.com/TENANT_ID/openid/userinfo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claims returned&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;given_name&lt;/code&gt;, &lt;code&gt;family_name&lt;/code&gt;, &lt;code&gt;picture&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;oid&lt;/code&gt;, &lt;code&gt;tid&lt;/code&gt; and the v1.0 name claims, but &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uniqueIdentityClaim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt; only; &lt;code&gt;oid&lt;/code&gt; is not returned&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt; or &lt;code&gt;oid&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Issuer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://login.microsoftonline.com/TENANT_ID/v2.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://sts.windows.net/TENANT_ID/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Neither returns &lt;code&gt;preferred_username&lt;/code&gt;, so &lt;code&gt;userDisplayNameClaim&lt;/code&gt; must be set explicitly either way, and the default &lt;code&gt;fullNameClaimTemplate&lt;/code&gt; of &lt;code&gt;${name}&lt;/code&gt; works with both given the &lt;code&gt;profile&lt;/code&gt; scope.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With the v2.0 endpoints&lt;/strong&gt; &lt;code&gt;uniqueIdentityClaim&lt;/code&gt; must be left at the default of &lt;code&gt;sub&lt;/code&gt;.
With &lt;code&gt;uniqueIdentityClaim: &amp;quot;oid&amp;quot;&lt;/code&gt; copied from the Entra ID page, every request is rejected and the log says &lt;code&gt;Expecting claims to contain configured uniqueIdentityClaim &#39;oid&#39; but it is not there&lt;/code&gt;, followed by the claims that did arrive.
In Entra ID &lt;code&gt;sub&lt;/code&gt; is pairwise, i.e. specific to the app registration, and stable for as long as that registration exists.
Deleting and recreating the ALB&amp;rsquo;s app registration changes every user&amp;rsquo;s &lt;code&gt;sub&lt;/code&gt; and orphans their Stroom user, so treat the registration as permanent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With the v1.0 endpoints&lt;/strong&gt; &lt;code&gt;oid&lt;/code&gt; is available, which is the durable identifier the Entra ID page recommends, and the one an existing Stroom that used to be Entra ID&amp;rsquo;s own client will already be keyed on.
It also means users can be created in Stroom before they first sign in, because &lt;code&gt;oid&lt;/code&gt; can be read from the directory, which pairwise &lt;code&gt;sub&lt;/code&gt; cannot.
The cost is that v1.0 is the legacy generation of the platform, and its user info endpoint does not return &lt;code&gt;email&lt;/code&gt;.
The full configuration is under &lt;a href=&#34;#using-the-v10-endpoints&#34;&gt;Using the v1.0 Endpoints&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Whichever generation you pick, check what actually arrives before the first user signs in, with the &lt;code&gt;DEBUG&lt;/code&gt; logging described under &lt;a href=&#34;#setting-up-the-admin-user&#34;&gt;Setting up the Admin User&lt;/a&gt;, and decide the identity claim then.
Changing it later orphans every user.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    Moving an existing Stroom from being Entra ID&amp;rsquo;s client itself to sitting behind the ALB on the &lt;strong&gt;v2.0&lt;/strong&gt; endpoints &lt;strong&gt;changes every user&amp;rsquo;s identity&lt;/strong&gt;: they were known by &lt;code&gt;oid&lt;/code&gt; (or by the &lt;code&gt;sub&lt;/code&gt; pairwise to Stroom&amp;rsquo;s own app registration) and are now known by the &lt;code&gt;sub&lt;/code&gt; pairwise to the ALB&amp;rsquo;s.
None of the existing Stroom users will match, so their permissions and group memberships have to be reapplied to the new identities.
Plan this before switching, rather than discovering it when the administrator signs in to an empty UI.
Using the v1.0 endpoints with &lt;code&gt;uniqueIdentityClaim: &amp;quot;oid&amp;quot;&lt;/code&gt; avoids this for a Stroom that was already keyed on &lt;code&gt;oid&lt;/code&gt;.

&lt;/div&gt;


&lt;h3 id=&#34;stroom-configuration-for-entra-id&#34;&gt;Stroom Configuration for Entra ID&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;      edgeAuthentication:
        enabled: true
        logout:
          cookiesToExpire: [ &amp;quot;AWSELBAuthSessionCookie&amp;quot; ]
          signOutUrl: &amp;quot;https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/logout?\
post_logout_redirect_uri=https://STROOM_FQDN/signed-out&amp;quot;
      openId:
        identityProviderType: EXTERNAL_IDP
        # Note the &#39;/v2.0&#39; path part, which must agree with the listener rule&#39;s Issuer.
        openIdConfigurationEndpoint: &amp;quot;https://login.microsoftonline.com/TENANT_ID/v2.0/\
.well-known/openid-configuration&amp;quot;
        clientId: &amp;quot;ALB_CLIENT_ID&amp;quot;
        expectedSignerPrefixes:
          - &amp;quot;arn:aws:elasticloadbalancing:REGION:ACCOUNT_ID:&amp;quot;
        # Entra ID&#39;s user info endpoint does not return &#39;preferred_username&#39;.
        userDisplayNameClaim: &amp;quot;email&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compared with the &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/azure-ad/#configuring-stroom&#34;&gt;Entra ID&lt;/a&gt; page&amp;rsquo;s configuration, there is no &lt;code&gt;clientSecret&lt;/code&gt;, &lt;code&gt;validIssuers&lt;/code&gt;, &lt;code&gt;allowedAudiences&lt;/code&gt;, &lt;code&gt;requestScopes&lt;/code&gt; or &lt;code&gt;uniqueIdentityClaim: oid&lt;/code&gt;.
Stroom runs no flow, only v2.0 tokens are ever involved, the ALB&amp;rsquo;s token carries no audience, the scopes are set on the listener rule, and &lt;code&gt;oid&lt;/code&gt; is not returned by the v2.0 user info endpoint.
For the v1.0 variant, see &lt;a href=&#34;#using-the-v10-endpoints&#34;&gt;Using the v1.0 Endpoints&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;using-the-v10-endpoints&#34;&gt;Using the v1.0 Endpoints&lt;/h3&gt;
&lt;p&gt;The same app registration works for either generation; nothing in the registration selects v1.0 or v2.0.
What changes is the listener rule, which names the v1.0 endpoints, and the Stroom configuration, which has to accept the v1.0 issuer.&lt;/p&gt;
&lt;p&gt;The v1.0 endpoints support only the &lt;code&gt;openid&lt;/code&gt; scope; &lt;code&gt;profile&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;offline_access&lt;/code&gt; are v2.0 concepts.
The user info endpoint returns the name claims regardless, and the code flow returns a refresh token without being asked, so the ALB can still renew the session silently.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;Type&amp;quot;: &amp;quot;authenticate-oidc&amp;quot;,
    &amp;quot;AuthenticateOidcConfig&amp;quot;: {
        &amp;quot;Issuer&amp;quot;: &amp;quot;https://sts.windows.net/TENANT_ID/&amp;quot;,
        &amp;quot;AuthorizationEndpoint&amp;quot;: &amp;quot;https://login.microsoftonline.com/TENANT_ID/oauth2/authorize&amp;quot;,
        &amp;quot;TokenEndpoint&amp;quot;: &amp;quot;https://login.microsoftonline.com/TENANT_ID/oauth2/token&amp;quot;,
        &amp;quot;UserInfoEndpoint&amp;quot;: &amp;quot;https://login.microsoftonline.com/TENANT_ID/openid/userinfo&amp;quot;,
        &amp;quot;ClientId&amp;quot;: &amp;quot;ALB_CLIENT_ID&amp;quot;,
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;ALB_CLIENT_SECRET&amp;quot;,
        &amp;quot;Scope&amp;quot;: &amp;quot;openid&amp;quot;,
        &amp;quot;OnUnauthenticatedRequest&amp;quot;: &amp;quot;authenticate&amp;quot;
    },
    &amp;quot;Order&amp;quot;: 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Issuer&lt;/code&gt; must be &lt;strong&gt;exactly&lt;/strong&gt; &lt;code&gt;https://sts.windows.net/TENANT_ID/&lt;/code&gt;, including the trailing slash; it is what the v1.0 discovery document advertises and what Stroom checks the token&amp;rsquo;s &lt;code&gt;iss&lt;/code&gt; against.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;      edgeAuthentication:
        enabled: true
        logout:
          cookiesToExpire: [ &amp;quot;AWSELBAuthSessionCookie&amp;quot; ]
          signOutUrl: &amp;quot;https://login.microsoftonline.com/TENANT_ID/oauth2/logout?\
post_logout_redirect_uri=https://STROOM_FQDN/signed-out&amp;quot;
      openId:
        identityProviderType: EXTERNAL_IDP
        # The v1.0 discovery document: no &#39;/v2.0&#39; path part.
        openIdConfigurationEndpoint: &amp;quot;https://login.microsoftonline.com/TENANT_ID/\
.well-known/openid-configuration&amp;quot;
        clientId: &amp;quot;ALB_CLIENT_ID&amp;quot;
        expectedSignerPrefixes:
          - &amp;quot;arn:aws:elasticloadbalancing:REGION:ACCOUNT_ID:&amp;quot;
        # The v1.0 issuer shares no base URI with the discovery endpoint, so Stroom refuses
        # to start unless it is listed here; see the Entra ID page. With validIssuers set,
        # the discovery document&#39;s issuer must be in the list, which this one is.
        validIssuers:
          - &amp;quot;https://sts.windows.net/TENANT_ID/&amp;quot;
        # The v1.0 user info endpoint returns &#39;oid&#39;, so the durable identifier is available.
        uniqueIdentityClaim: &amp;quot;oid&amp;quot;
        # It does not return &#39;email&#39; or &#39;preferred_username&#39;.
        userDisplayNameClaim: &amp;quot;name&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The differences from the &lt;a href=&#34;#stroom-configuration-for-entra-id&#34;&gt;v2.0 configuration&lt;/a&gt; are the &lt;code&gt;oauth2/logout&lt;/code&gt; sign out endpoint (no &lt;code&gt;v2.0&lt;/code&gt;), the discovery document, &lt;code&gt;validIssuers&lt;/code&gt;, and the two claim settings.
&lt;code&gt;https://STROOM_FQDN/signed-out&lt;/code&gt; still has to be registered as a redirect URI in the app registration.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;oid&lt;/code&gt; as the identity, the administrator can be set up &lt;strong&gt;before&lt;/strong&gt; anyone signs in, exactly as on the &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/azure-ad/#setting-up-the-admin-user-in-stroom&#34;&gt;Entra ID page&lt;/a&gt;: the Object ID is shown on the user&amp;rsquo;s page in the Entra admin centre.
The sign in first procedure under &lt;a href=&#34;#setting-up-the-admin-user&#34;&gt;Setting up the Admin User&lt;/a&gt; is only needed for &lt;code&gt;sub&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;setting-up-the-admin-user&#34;&gt;Setting up the Admin User&lt;/h3&gt;
&lt;p&gt;With the v2.0 endpoints the identity is the pairwise &lt;code&gt;sub&lt;/code&gt;, and that &lt;strong&gt;cannot be looked up in advance&lt;/strong&gt;: it is computed by Entra ID for each app registration, is not exposed by the Graph API or the admin centre, and only ever appears in a token issued for that user to that client.
Any process that reads identities from the directory and pre-creates Stroom users, as is possible with &lt;code&gt;oid&lt;/code&gt;, does not work with &lt;code&gt;sub&lt;/code&gt;.
Users have to sign in first, which creates their Stroom user, and be granted permissions afterwards.&lt;/p&gt;
&lt;p&gt;For the first administrator:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Have them sign in once through the ALB.
Stroom creates a user with &lt;code&gt;name&lt;/code&gt; set to their &lt;code&gt;sub&lt;/code&gt; and &lt;code&gt;display_name&lt;/code&gt; set to the &lt;code&gt;email&lt;/code&gt; claim, with no permissions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read the &lt;code&gt;sub&lt;/code&gt; from the database:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-sql&#34;&gt;SELECT name FROM stroom_user WHERE display_name = &#39;admin@example.com&#39; AND is_group = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Grant permissions with &lt;code&gt;manage_users&lt;/code&gt; as described under &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/azure-ad/#setting-up-the-admin-user-in-stroom&#34;&gt;Setting up the Admin User in Stroom&lt;/a&gt;, using that value as the subject id.
The command is repeatable, so running it against a user that already exists is fine.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once one administrator exists, further users can be found under &lt;em&gt;Users&lt;/em&gt; in the UI after their first sign in, or a script can find them by display name with &lt;code&gt;POST /api/users/v1/find&lt;/code&gt; and add them to groups with &lt;code&gt;PUT /api/users/v1/{userUuid}/{groupUuid}&lt;/code&gt;, matched on the email address the directory reports for them.&lt;/p&gt;
&lt;p&gt;If the &lt;code&gt;sub&lt;/code&gt; is not where you expect, the logger &lt;code&gt;stroom.security.common.impl.StandardJwtContextFactory&lt;/code&gt; at &lt;code&gt;DEBUG&lt;/code&gt; logs the claims of every token Stroom verifies.&lt;/p&gt;
&lt;h3 id=&#34;migrating-existing-users&#34;&gt;Migrating Existing Users&lt;/h3&gt;
&lt;p&gt;A Stroom that was Entra ID&amp;rsquo;s own client already has users keyed on &lt;code&gt;oid&lt;/code&gt;, or on the &lt;code&gt;sub&lt;/code&gt; pairwise to Stroom&amp;rsquo;s app registration.
Behind the ALB on the v2.0 endpoints those users will never be matched again; on the v1.0 endpoints with &lt;code&gt;uniqueIdentityClaim: &amp;quot;oid&amp;quot;&lt;/code&gt; they will, and nothing needs migrating.&lt;/p&gt;
&lt;p&gt;To migrate to the v2.0 identities, each user signs in once, which creates an empty user keyed on their new &lt;code&gt;sub&lt;/code&gt;, and their groups and permissions are then copied across from the old user:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the old user&amp;rsquo;s UUID: &lt;code&gt;GET /api/users/v1/fetchBySubjectId/{oid}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Find the new user&amp;rsquo;s UUID by display name, as above.
The old user&amp;rsquo;s display name is the UPN (the &lt;code&gt;preferred_username&lt;/code&gt; claim) and the new one&amp;rsquo;s is the &lt;code&gt;email&lt;/code&gt; claim; where those differ in your tenant, resolve &lt;code&gt;oid&lt;/code&gt; to &lt;code&gt;mail&lt;/code&gt; with the Graph API.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/users/v1/{newUserUuid}/copyPermissions&lt;/code&gt; with the old user&amp;rsquo;s UUID as the body.
This copies group memberships, application permissions and document permissions, and is the same operation as &lt;em&gt;Copy Permissions&lt;/em&gt; in the &lt;em&gt;Users&lt;/em&gt; screen.&lt;/li&gt;
&lt;li&gt;Disable the old user.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ownership of things such as API keys and processor filters is not copied, as it is tied to the user&amp;rsquo;s UUID rather than its permissions; recreate those against the new user where needed.&lt;/p&gt;
&lt;h2 id=&#34;other-oidc-providers&#34;&gt;Other OIDC Providers&lt;/h2&gt;
&lt;p&gt;Any provider the ALB can reach works the same way as Entra ID: an &lt;code&gt;authenticate-oidc&lt;/code&gt; rule with the provider&amp;rsquo;s issuer and endpoints, and the &lt;a href=&#34;#stroom-configuration&#34;&gt;generic Stroom configuration&lt;/a&gt; with the provider&amp;rsquo;s discovery document.
Check three things against the provider&amp;rsquo;s documentation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;Issuer&lt;/code&gt; on the rule is exactly the issuer its discovery document advertises.&lt;/li&gt;
&lt;li&gt;What its user info endpoint returns, and set &lt;code&gt;userDisplayNameClaim&lt;/code&gt; accordingly.&lt;/li&gt;
&lt;li&gt;What its end session endpoint is called, what parameter it takes for the return address, and whether that address must be registered.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;verifying-it-works&#34;&gt;Verifying it Works&lt;/h2&gt;
&lt;p&gt;After deploying, load Stroom in a browser and check, in the developer tools network tab:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You are redirected to the IDP&amp;rsquo;s sign in page, sign in, and land back at Stroom.&lt;/li&gt;
&lt;li&gt;The request to &lt;code&gt;/api/auth/flow/v1/status&lt;/code&gt; returns &lt;code&gt;200&lt;/code&gt; with &lt;code&gt;&amp;quot;authenticated&amp;quot;: true&lt;/code&gt; and the UI loads.&lt;/li&gt;
&lt;li&gt;There is &lt;strong&gt;no&lt;/strong&gt; further navigation to the IDP&amp;rsquo;s authorization endpoint (&lt;code&gt;.../oauth2/authorize&lt;/code&gt; for Cognito, &lt;code&gt;.../oauth2/v2.0/authorize&lt;/code&gt; for Entra ID) after that first sign in — if there is, Stroom is running a second flow and &lt;code&gt;edgeAuthentication.enabled&lt;/code&gt; is not set.&lt;/li&gt;
&lt;li&gt;Signing out lands on the &lt;em&gt;You have been signed out&lt;/em&gt; page, and following its link asks you to sign in again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On the Stroom side, the log should not contain &lt;code&gt;Redirecting with an AuthenticationRequest to:&lt;/code&gt; during normal browsing.&lt;/p&gt;
&lt;p&gt;If every request is rejected with &lt;code&gt;Authenticated user is not permitted to use stroom&lt;/code&gt;, set the logger &lt;code&gt;stroom.security.common.impl.StandardJwtContextFactory&lt;/code&gt; to &lt;code&gt;DEBUG&lt;/code&gt;: it logs the raw &lt;code&gt;x-amzn-oidc-data&lt;/code&gt; token (&lt;code&gt;jws:&lt;/code&gt;) and why verification failed.
Decode the token&amp;rsquo;s header and payload (base64 JSON, the first and second dot separated segments) and compare the payload&amp;rsquo;s &lt;code&gt;iss&lt;/code&gt; with the issuer in the discovery document at &lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt;, and the header&amp;rsquo;s &lt;code&gt;signer&lt;/code&gt; with &lt;code&gt;expectedSignerPrefixes&lt;/code&gt;.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: KeyCloak</title>
      <link>/docs/install-guide/setup/open-id/external-idp/keycloak/</link>
      <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/external-idp/keycloak/</guid>
      <description>
        
        
        &lt;p&gt;This is a guide to setting up a new Stroom instance or cluster with &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://www.keycloak.org/&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;KeyCloak (external link to https://www.keycloak.org/)&#34;&gt;
    &lt;span&gt;KeyCloak&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 as the 3rd party &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.
It assumes you have deployed a new instance or cluster of Stroom and have &lt;strong&gt;not&lt;/strong&gt; yet started it.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;Read &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;External IDP&lt;/a&gt; first for what Stroom needs from any provider, and &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/stroom-configuration/&#34;&gt;Stroom Configuration&lt;/a&gt; for what each setting does.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;



    &lt;p&gt;This page covers Stroom itself being the OIDC client of the realm.
If an authenticating reverse proxy (e.g. NGINX with oauth2-proxy) in front of Stroom does the sign in against KeyCloak instead, see &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/nginx-oauth2-proxy/&#34;&gt;NGINX, oauth2-proxy and KeyCloak&lt;/a&gt;.&lt;/p&gt;


&lt;/div&gt;


&lt;h2 id=&#34;running-keycloak&#34;&gt;Running KeyCloak&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;If you already have a KeyCloak instance running then move on to the next section.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This section is not a definitive guide to running or administering KeyCloak.
It describes how to run KeyCloak using non-production settings for simplicity and to demonstrate using a 3rd party IDP.
You should consult the KeyCloak documentation on how to set up a production ready instance.&lt;/p&gt;
&lt;p&gt;The easiest way to run KeyCloak is using Docker.
To create a KeyCloak container do the following:&lt;/p&gt;

  






&lt;div class=&#34;code-toolbar&#34;&gt;
  &lt;pre 
    class=&#34;command-line language-bash&#34; 
    data-user=&#34;user&#34; 
    data-host=&#34;localhost&#34; 
    data-continuation-str=&#34;\&#34;
    data-filter-output=&#34;(out)&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker create \
  --name keycloak \
  -p 9999:8080 \
  -e KEYCLOAK_ADMIN=admin \
  -e KEYCLOAK_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:20.0.1 \
  start-dev&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This example maps KeyCloak&amp;rsquo;s port to port &lt;code&gt;9999&lt;/code&gt; to avoid any clash with Stroom that also runs on &lt;code&gt;8080&lt;/code&gt;.
This will create a docker container called &lt;code&gt;keycloak&lt;/code&gt; that uses an embedded H2 database to hold its state.&lt;/p&gt;
&lt;p&gt;To start the container in the foreground, do:&lt;/p&gt;

  






&lt;div class=&#34;code-toolbar&#34;&gt;
  &lt;pre 
    class=&#34;command-line language-bash&#34; 
    data-user=&#34;user&#34; 
    data-host=&#34;localhost&#34; 
    data-continuation-str=&#34;\&#34;
    data-filter-output=&#34;(out)&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker start -a keycloak&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;KeyCloak should now be running on &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;http://localhost:9999/admin&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;External link to http://localhost:9999/admin&#34;&gt;
    &lt;span&gt;http://localhost:9999/admin&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
.
If you want to run KeyCloak on a different port then delete the container and create it with a different port for the &lt;code&gt;-p&lt;/code&gt; argument.&lt;/p&gt;
&lt;p&gt;Log into KeyCloak using the username &lt;code&gt;admin&lt;/code&gt; and password &lt;code&gt;admin&lt;/code&gt; as specified in the environment variables set in the container creation command above.
You should see the admin console.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    The admin console layout and the names of some settings vary between KeyCloak versions.
The steps below were written against the version above.
On a newer version the same settings may sit under differently named tabs, and the bootstrap admin environment variables have been renamed.

&lt;/div&gt;


&lt;h2 id=&#34;creating-a-realm&#34;&gt;Creating a Realm&lt;/h2&gt;
&lt;p&gt;First you need to create a Realm.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click on the drop-down in the left pane that contains the word &lt;code&gt;master&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Create Realm&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Set the &lt;em&gt;Realm name&lt;/em&gt; to &lt;code&gt;StroomRealm&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Create&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;creating-a-client&#34;&gt;Creating a Client&lt;/h2&gt;
&lt;p&gt;In the new realm click on &lt;em&gt;Clients&lt;/em&gt; in the left pane, then &lt;em&gt;Create client&lt;/em&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set the &lt;em&gt;Client ID&lt;/em&gt; to &lt;code&gt;StroomClient&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Next&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;em&gt;Client authentication&lt;/em&gt; to on, which makes this a confidential client.&lt;/li&gt;
&lt;li&gt;Ensure the following are ticked:
&lt;ul&gt;
&lt;li&gt;Standard flow&lt;/li&gt;
&lt;li&gt;Direct access grants&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Save&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Open the new &lt;em&gt;Client&lt;/em&gt; and on the &lt;em&gt;Settings&lt;/em&gt; tab set:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Valid redirect URIs&lt;/em&gt; to &lt;code&gt;https://STROOM_FQDN/api/auth/flow/v1/signin-oidc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Valid post logout redirect URIs&lt;/em&gt; to &lt;code&gt;https://STROOM_FQDN/*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where &lt;code&gt;STROOM_FQDN&lt;/code&gt; is the public address of Stroom, i.e. what you have set as &lt;code&gt;appConfig.publicUri&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    &lt;p&gt;The redirect URI is a single exact value.
Do not use a wildcard such as &lt;code&gt;https://STROOM_FQDN/*&lt;/code&gt; for it.&lt;/p&gt;
&lt;p&gt;Earlier versions of Stroom sent the user&amp;rsquo;s current page as the redirect URI and so did need a wildcard here.
If you are upgrading, replace it with the exact URI above.&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;The post logout redirect URI does use a wildcard, because Stroom appends a &lt;code&gt;state&lt;/code&gt; parameter to it.&lt;/p&gt;
&lt;p&gt;On the &lt;em&gt;Advanced&lt;/em&gt; tab, under &lt;em&gt;Advanced settings&lt;/em&gt;, set &lt;em&gt;Proof Key for Code Exchange Code Challenge Method&lt;/em&gt; to &lt;code&gt;S256&lt;/code&gt;.
Stroom always sends a PKCE challenge, so KeyCloak can be told to insist on one.&lt;/p&gt;
&lt;p&gt;On the &lt;em&gt;Credentials&lt;/em&gt; tab copy the &lt;em&gt;Client secret&lt;/em&gt; for use later in Stroom config.&lt;/p&gt;
&lt;h2 id=&#34;adding-an-audience-mapper&#34;&gt;Adding an Audience Mapper&lt;/h2&gt;
&lt;p&gt;This step matters, and is easy to miss.&lt;/p&gt;
&lt;p&gt;By default KeyCloak does not put the client id in the &lt;code&gt;aud&lt;/code&gt; claim of the access tokens it issues; typically it puts &lt;code&gt;account&lt;/code&gt; there instead.
Stroom validates the audience of every token it is given, so without this step API calls made with a KeyCloak access token are refused.
Interactive sign in still works, because the &lt;code&gt;id_token&lt;/code&gt; does carry the client id.&lt;/p&gt;
&lt;p&gt;In the realm, click &lt;em&gt;Client scopes&lt;/em&gt; in the left pane, then the &lt;code&gt;StroomClient-dedicated&lt;/code&gt; scope belonging to the client.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click &lt;em&gt;Add mapper&lt;/em&gt; =&amp;gt; &lt;em&gt;By configuration&lt;/em&gt; =&amp;gt; &lt;em&gt;Audience&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;em&gt;Name&lt;/em&gt; to &lt;code&gt;stroom-audience&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;em&gt;Included Client Audience&lt;/em&gt; to &lt;code&gt;StroomClient&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ensure &lt;em&gt;Add to access token&lt;/em&gt; is on.&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Save&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The alternative, if you would rather not change KeyCloak, is to tell Stroom what KeyCloak actually issues:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;        allowedAudiences:
          - &amp;quot;account&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is weaker, since &lt;code&gt;account&lt;/code&gt; is an audience every client in the realm can obtain, so a token minted for another application in the same realm would be accepted by Stroom.
Prefer the mapper.&lt;/p&gt;
&lt;h2 id=&#34;creating-users&#34;&gt;Creating Users&lt;/h2&gt;
&lt;p&gt;Click on &lt;em&gt;Users&lt;/em&gt; in the left pane then &lt;em&gt;Add user&lt;/em&gt;.
Set the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Username - &lt;code&gt;admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;First name - &lt;code&gt;Administrator&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Last name - &lt;code&gt;Administrator&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Click &lt;em&gt;Create&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Select the &lt;em&gt;Credentials&lt;/em&gt; tab and click &lt;em&gt;Set password&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Set the password to &lt;code&gt;admin&lt;/code&gt; and set &lt;em&gt;Temporary&lt;/em&gt; to off.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    Standard practice would be for there to be a number of administrators where each has their own identity (in their own name) on the IDP.
Each would be granted the &lt;code&gt;Administrator&lt;/code&gt; application permission (directly or via a group).
For this example we are calling our administrator &lt;code&gt;admin&lt;/code&gt;.

&lt;/div&gt;


&lt;p&gt;Repeat this process for the following user:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Username - &lt;code&gt;jbloggs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;First name - &lt;code&gt;Joe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Last name - &lt;code&gt;Bloggs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Password - &lt;code&gt;password&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;configure-stroom-for-keycloak&#34;&gt;Configure Stroom for KeyCloak&lt;/h2&gt;
&lt;p&gt;Edit the &lt;code&gt;config.yml&lt;/code&gt; file and set the following values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  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:
        # Tells Stroom to use an external IDP for authentication
        identityProviderType: EXTERNAL_IDP
        # The endpoint to obtain the rest of the IDP&#39;s configuration. Specific to the realm/issuer.
        openIdConfigurationEndpoint: &amp;quot;http://localhost:9999/realms/StroomRealm/.well-known/openid-configuration&amp;quot;
        # The client ID created in KeyCloak
        clientId: &amp;quot;StroomClient&amp;quot;
        # The client secret copied from KeyCloak above
        clientSecret: &amp;quot;XwTPPudGZkDK2hu31MZkotzRUdBWfHO6&amp;quot;
        # The URL on the IDP to redirect users to when logging out of Stroom
        logoutEndpoint: &amp;quot;http://localhost:9999/realms/StroomRealm/protocol/openid-connect/logout&amp;quot;
        # KeyCloak stamps its access tokens with a &#39;typ&#39; header of &#39;Bearer&#39;. Requiring it stops an
        # id_token being replayed against the API as though it were an access token.
        requiredAccessTokenType: &amp;quot;Bearer&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These values are obtained from the IDP.
In the case of KeyCloak they can be found by clicking on &lt;em&gt;Realm settings&lt;/em&gt; =&amp;gt; &lt;em&gt;Endpoints&lt;/em&gt; =&amp;gt; &lt;em&gt;OpenID Endpoint Configuration&lt;/em&gt; and extracting the various values from the JSON response.
Alternatively they can typically be found at &lt;em&gt;https://host/.well-known/openid-configuration&lt;/em&gt; on any Open ID Connect IDP.
The values will reflect the host and port that the IDP is running on along with the name of the realm.&lt;/p&gt;
&lt;p&gt;Setting the above values assumes KeyCloak is running on &lt;code&gt;localhost:9999&lt;/code&gt; and the realm name is &lt;code&gt;StroomRealm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The claim defaults suit KeyCloak, so there is nothing to set for them.
KeyCloak issues &lt;code&gt;preferred_username&lt;/code&gt;, which Stroom uses as the display name, and issues &lt;code&gt;name&lt;/code&gt; where the user has a first and last name, which satisfies the default &lt;code&gt;fullNameClaimTemplate&lt;/code&gt; of &lt;code&gt;${name}&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    Before setting &lt;code&gt;requiredAccessTokenType&lt;/code&gt;, confirm the value your KeyCloak version actually uses by decoding the header of a real access token.
Leave it unset if in doubt; it is a hardening measure rather than a requirement.

&lt;/div&gt;


&lt;h2 id=&#34;setting-up-the-admin-user-in-stroom&#34;&gt;Setting up the Admin User in Stroom&lt;/h2&gt;
&lt;p&gt;Now that the &lt;code&gt;admin&lt;/code&gt; user exists in the IDP we need to grant it &lt;code&gt;Administrator&lt;/code&gt; rights in Stroom.&lt;/p&gt;
&lt;p&gt;In the &lt;em&gt;Users&lt;/em&gt; section of KeyCloak click on user &lt;code&gt;admin&lt;/code&gt;.
On the &lt;em&gt;Details&lt;/em&gt; tab copy the value of the &lt;em&gt;ID&lt;/em&gt; field.
The ID is in the form of a &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/u/glossary-uuid&#34;&gt;
      &lt;span&gt;UUID&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;UUID&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;A &lt;strong&gt;U&lt;/strong&gt;niversally &lt;strong&gt;U&lt;/strong&gt;nique &lt;strong&gt;Id&lt;/strong&gt;entifier for uniquely identifying something. &lt;em&gt;UUIDs&lt;/em&gt; are used as the identifier in &lt;em&gt;Doc Refs&lt;/em&gt;. An example of a &lt;em&gt;UUID&lt;/em&gt; is &lt;code&gt;4ffeb895-53c9-40d6-bf33-3ef025401ad3&lt;/code&gt;.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.
This ID is the &lt;code&gt;sub&lt;/code&gt; claim, which is what Stroom uses to uniquely identify the user and associate it with the identity in KeyCloak.&lt;/p&gt;
&lt;p&gt;To set up Stroom with this admin user run the following (&lt;strong&gt;before&lt;/strong&gt; Stroom has been started for the first time):&lt;/p&gt;

  






&lt;div class=&#34;code-toolbar&#34;&gt;
  &lt;pre 
    class=&#34;command-line language-bash&#34; 
    data-user=&#34;user&#34; 
    data-host=&#34;localhost&#34; 
    data-continuation-str=&#34;\&#34;
    data-filter-output=&#34;(out)&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;subject_id=&amp;#34;XXX&amp;#34;; \
java -jar /absolute/path/to/stroom-app-all.jar \
  manage_users \
  --createUser &amp;#34;${subject_id}&amp;#34; \
  --createGroup Administrators \
  --addToGroup &amp;#34;${subject_id}&amp;#34; Administrators \
  --grantPermission Administrators &amp;#34;Administrator&amp;#34; \
  ../local.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Where &lt;code&gt;XXX&lt;/code&gt; is the user ID copied from the IDP as described above.
This command is repeatable as it will skip any users/groups/memberships that already exist.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;See &lt;a href=&#34;../../docs/user-guide/tools/command-line/&#34;&gt;Command Line Tools&lt;/a&gt; for more details on using the &lt;code&gt;manage_users&lt;/code&gt; command.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;This command will do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create the Stroom User by creating an entry in the &lt;code&gt;stroom_user&lt;/code&gt; database table for the IDP&amp;rsquo;s &lt;code&gt;admin&lt;/code&gt; user.&lt;/li&gt;
&lt;li&gt;Ensure that an &lt;code&gt;Administrators&lt;/code&gt; group exists (i.e. an entry in the &lt;code&gt;stroom_user&lt;/code&gt; database table for the &lt;code&gt;Administrators&lt;/code&gt; group).&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;admin&lt;/code&gt; user to the group &lt;code&gt;Administrators&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Grant the application permission &lt;code&gt;Administrator&lt;/code&gt; to the group &lt;code&gt;Administrators&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    This process is only required to bootstrap the admin user, to allow them to log in with administrator rights and manage the permissions and group memberships of everyone else.
It does not need to be done for every user.
Whenever a user successfully logs in via the IDP, Stroom will automatically create an entry in the &lt;code&gt;stroom_user&lt;/code&gt; table for that user.
The user will have no permissions or group memberships, so these will need to be applied by the administrator.
This does mean that new users will need to log in before the administrator can manage their permissions and memberships.

&lt;/div&gt;


&lt;h2 id=&#34;logging-into-stroom&#34;&gt;Logging into Stroom&lt;/h2&gt;
&lt;h3 id=&#34;as-the-administrator&#34;&gt;As the Administrator&lt;/h3&gt;
&lt;p&gt;Now that the user and permissions have been set up in Stroom, the administrator can log in.&lt;/p&gt;
&lt;p&gt;First start the Stroom instance or cluster.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    If the &lt;code&gt;manage_users&lt;/code&gt; command is run while Stroom is running you will likely not see the effect when logging in, as the user permissions are cached.
Without Administrator rights you will not be able to clear the caches, so you will need to wait for the cache entries to expire or restart Stroom.

&lt;/div&gt;


&lt;p&gt;Navigate to &lt;em&gt;https://STROOM_FQDN&lt;/em&gt; and Stroom should re-direct you to the IDP (KeyCloak) to authenticate.
Enter the username &lt;code&gt;admin&lt;/code&gt; and password &lt;code&gt;admin&lt;/code&gt;.
You should be authenticated by KeyCloak and re-directed back to Stroom.
Your user ID is shown in the bottom right corner of the Welcome tab.&lt;/p&gt;
&lt;p&gt;As an administrator, the 




  &lt;div class=&#34;stroom-theme-dark stroom-menu&#34;&gt;
    
    

      

      
      


      

  
  
  &lt;div class=&#34;stroom-menu-item-background&#34; style=&#34;margin-top: 0px;&#34;&gt;
    
    &lt;div class=&#34;stroom-menu-item &#34; &gt;

      &lt;div class=&#34;stroom-menu-item-text&#34;&gt;Tools
      &lt;/div&gt;
        &lt;div class=&#34;stroom-menu-item-arrow&#34;&gt;
          &lt;span class=&#34;stroom-icon inline-svg-button icon-button  &#34; &gt;
    &lt;span class=&#34;face&#34; title=&#34;Arrow right&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;1427.7665&#34; height=&#34;1427.7665&#34; viewBox=&#34;0 0 1427.7665 1427.7665&#34;&gt;&lt;g style=&#34;stroke:currentColor;stroke-width:167.567&#34;&gt;&lt;path d=&#34;m 415.05387,83.70929 c -39.92654,0.4303 -75.41463,48.3887 -40.0468,81.9121 C 567.552,348.69899 760.75555,531.15431 953.73208,713.82391 760.75555,896.49341 567.552,1078.9486 375.00707,1262.0262 c -46.19471,43.7855 28.48812,112.1966 76.0571,67.1681 195.93558,-185.823 392.95261,-370.82479 587.98693,-557.52749 5.6117,-4.8034 10.4633,-10.2473 14.4855,-16.1231 9.643,-12.4844 13.9638,-27.0796 13.6909,-41.7198 0.2729,-14.6402 -4.0479,-29.2354 -13.6909,-41.7198 -4.0222,-5.8758 -8.8738,-11.3198 -14.4855,-16.1231 C 844.01678,469.27821 646.99975,284.27629 451.06417,98.45319 439.91519,87.89979 427.27628,83.57749 415.05387,83.70929 Z&#34;/&gt;&lt;/g&gt;&lt;/svg&gt; &lt;/span&gt;
  &lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

      

      
      
        
      


      

  
  
  &lt;div class=&#34;stroom-menu-item-background&#34; style=&#34;margin-top: 7px;&#34;&gt;
    
    &lt;div class=&#34;stroom-menu-item stroom-menu-item-leaf&#34; &gt;
        &lt;div class=&#34;stroom-menu-item-icon&#34;&gt;
          &lt;span class=&#34;stroom-icon inline-svg-button icon-button  &#34; &gt;
    &lt;span class=&#34;face&#34; title=&#34;User&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;1792&#34; height=&#34;1792&#34; viewBox=&#34;0 0 1792 1792&#34;&gt;&lt;path style=&#34;fill:currentColor&#34; d=&#34;M1536 1399q0 109-62.5 187t-150.5 78h-854q-88 0-150.5-78t-62.5-187q0-85 8.5-160.5t31.5-152 58.5-131 94-89 134.5-34.5q131 128 313 128t313-128q76 0 134.5 34.5t94 89 58.5 131 31.5 152 8.5 160.5zm-256-887q0 159-112.5 271.5t-271.5 112.5-271.5-112.5-112.5-271.5 112.5-271.5 271.5-112.5 271.5 112.5 112.5 271.5z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/span&gt;
        &lt;/div&gt;

      &lt;div class=&#34;stroom-menu-item-text&#34;&gt;User Permissions
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;/div&gt;


 menu item will be available to manage the permissions of any users that have logged on at least once.&lt;/p&gt;
&lt;p&gt;Now select 




  &lt;div class=&#34;stroom-theme-dark stroom-menu&#34;&gt;
    
    

      

      
      


      

  
  
  &lt;div class=&#34;stroom-menu-item-background&#34; style=&#34;margin-top: 0px;&#34;&gt;
    
    &lt;div class=&#34;stroom-menu-item &#34; &gt;

      &lt;div class=&#34;stroom-menu-item-text&#34;&gt;User
      &lt;/div&gt;
        &lt;div class=&#34;stroom-menu-item-arrow&#34;&gt;
          &lt;span class=&#34;stroom-icon inline-svg-button icon-button  &#34; &gt;
    &lt;span class=&#34;face&#34; title=&#34;Arrow right&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;1427.7665&#34; height=&#34;1427.7665&#34; viewBox=&#34;0 0 1427.7665 1427.7665&#34;&gt;&lt;g style=&#34;stroke:currentColor;stroke-width:167.567&#34;&gt;&lt;path d=&#34;m 415.05387,83.70929 c -39.92654,0.4303 -75.41463,48.3887 -40.0468,81.9121 C 567.552,348.69899 760.75555,531.15431 953.73208,713.82391 760.75555,896.49341 567.552,1078.9486 375.00707,1262.0262 c -46.19471,43.7855 28.48812,112.1966 76.0571,67.1681 195.93558,-185.823 392.95261,-370.82479 587.98693,-557.52749 5.6117,-4.8034 10.4633,-10.2473 14.4855,-16.1231 9.643,-12.4844 13.9638,-27.0796 13.6909,-41.7198 0.2729,-14.6402 -4.0479,-29.2354 -13.6909,-41.7198 -4.0222,-5.8758 -8.8738,-11.3198 -14.4855,-16.1231 C 844.01678,469.27821 646.99975,284.27629 451.06417,98.45319 439.91519,87.89979 427.27628,83.57749 415.05387,83.70929 Z&#34;/&gt;&lt;/g&gt;&lt;/svg&gt; &lt;/span&gt;
  &lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

      

      
      
        
      


      

  
  
  &lt;div class=&#34;stroom-menu-item-background&#34; style=&#34;margin-top: 7px;&#34;&gt;
    
    &lt;div class=&#34;stroom-menu-item stroom-menu-item-leaf&#34; &gt;
        &lt;div class=&#34;stroom-menu-item-icon&#34;&gt;
          &lt;span class=&#34;stroom-icon inline-svg-button icon-button  &#34; &gt;
    &lt;span class=&#34;face&#34; title=&#34;Logout&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;1792&#34; height=&#34;1792&#34; viewBox=&#34;0 0 1792 1792&#34;&gt;&lt;path style=&#34;fill:currentColor&#34; d=&#34;M704 1440q0 4 1 20t.5 26.5-3 23.5-10 19.5-20.5 6.5h-320q-119 0-203.5-84.5t-84.5-203.5v-704q0-119 84.5-203.5t203.5-84.5h320q13 0 22.5 9.5t9.5 22.5q0 4 1 20t.5 26.5-3 23.5-10 19.5-20.5 6.5h-320q-66 0-113 47t-47 113v704q0 66 47 113t113 47h312l11.5 1 11.5 3 8 5.5 7 9 2 13.5zm928-544q0 26-19 45l-544 544q-19 19-45 19t-45-19-19-45v-288h-448q-26 0-45-19t-19-45v-384q0-26 19-45t45-19h448v-288q0-26 19-45t45-19 45 19l544 544q19 19 19 45z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/span&gt;
        &lt;/div&gt;

      &lt;div class=&#34;stroom-menu-item-text&#34;&gt;Logout
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;/div&gt;


 to be re-directed to the IDP to log out.
Once you log out of the IDP it should re-direct you back to Stroom, which will send you to the IDP login screen to log back in again.&lt;/p&gt;
&lt;h3 id=&#34;as-an-ordinary-user&#34;&gt;As an Ordinary User&lt;/h3&gt;
&lt;p&gt;On the IDP login screen, log in as user &lt;code&gt;jbloggs&lt;/code&gt; with the password &lt;code&gt;password&lt;/code&gt;.
You will be re-directed to Stroom, however the explorer tree will be empty and most of the menu items will be disabled.
In order to gain permissions to do anything in Stroom, a Stroom administrator will need to grant application and document permissions and/or group memberships to the user via the 




  &lt;div class=&#34;stroom-theme-dark stroom-menu&#34;&gt;
    
    

      

      
      


      

  
  
  &lt;div class=&#34;stroom-menu-item-background&#34; style=&#34;margin-top: 0px;&#34;&gt;
    
    &lt;div class=&#34;stroom-menu-item &#34; &gt;

      &lt;div class=&#34;stroom-menu-item-text&#34;&gt;Tools
      &lt;/div&gt;
        &lt;div class=&#34;stroom-menu-item-arrow&#34;&gt;
          &lt;span class=&#34;stroom-icon inline-svg-button icon-button  &#34; &gt;
    &lt;span class=&#34;face&#34; title=&#34;Arrow right&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;1427.7665&#34; height=&#34;1427.7665&#34; viewBox=&#34;0 0 1427.7665 1427.7665&#34;&gt;&lt;g style=&#34;stroke:currentColor;stroke-width:167.567&#34;&gt;&lt;path d=&#34;m 415.05387,83.70929 c -39.92654,0.4303 -75.41463,48.3887 -40.0468,81.9121 C 567.552,348.69899 760.75555,531.15431 953.73208,713.82391 760.75555,896.49341 567.552,1078.9486 375.00707,1262.0262 c -46.19471,43.7855 28.48812,112.1966 76.0571,67.1681 195.93558,-185.823 392.95261,-370.82479 587.98693,-557.52749 5.6117,-4.8034 10.4633,-10.2473 14.4855,-16.1231 9.643,-12.4844 13.9638,-27.0796 13.6909,-41.7198 0.2729,-14.6402 -4.0479,-29.2354 -13.6909,-41.7198 -4.0222,-5.8758 -8.8738,-11.3198 -14.4855,-16.1231 C 844.01678,469.27821 646.99975,284.27629 451.06417,98.45319 439.91519,87.89979 427.27628,83.57749 415.05387,83.70929 Z&#34;/&gt;&lt;/g&gt;&lt;/svg&gt; &lt;/span&gt;
  &lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

      

      
      
        
      


      

  
  
  &lt;div class=&#34;stroom-menu-item-background&#34; style=&#34;margin-top: 7px;&#34;&gt;
    
    &lt;div class=&#34;stroom-menu-item stroom-menu-item-leaf&#34; &gt;
        &lt;div class=&#34;stroom-menu-item-icon&#34;&gt;
          &lt;span class=&#34;stroom-icon inline-svg-button icon-button  &#34; &gt;
    &lt;span class=&#34;face&#34; title=&#34;User&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;1792&#34; height=&#34;1792&#34; viewBox=&#34;0 0 1792 1792&#34;&gt;&lt;path style=&#34;fill:currentColor&#34; d=&#34;M1536 1399q0 109-62.5 187t-150.5 78h-854q-88 0-150.5-78t-62.5-187q0-85 8.5-160.5t31.5-152 58.5-131 94-89 134.5-34.5q131 128 313 128t313-128q76 0 134.5 34.5t94 89 58.5 131 31.5 152 8.5 160.5zm-256-887q0 159-112.5 271.5t-271.5 112.5-271.5-112.5-112.5-271.5 112.5-271.5 271.5-112.5 271.5 112.5 112.5 271.5z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/span&gt;
        &lt;/div&gt;

      &lt;div class=&#34;stroom-menu-item-text&#34;&gt;User Permissions
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;/div&gt;


 menu item.&lt;/p&gt;
&lt;h2 id=&#34;configure-stroom-proxy-for-keycloak&#34;&gt;Configure Stroom-Proxy for KeyCloak&lt;/h2&gt;
&lt;p&gt;Create a second client in KeyCloak for the proxy, following the steps above but with &lt;em&gt;Service accounts roles&lt;/em&gt; enabled so that it can use the client credentials grant.
A proxy has no interactive users, so it needs no redirect URIs.&lt;/p&gt;
&lt;p&gt;Edit the proxy&amp;rsquo;s &lt;code&gt;config.yml&lt;/code&gt; file and set the following values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  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:
      openId:
        identityProviderType: EXTERNAL_IDP
        openIdConfigurationEndpoint: &amp;quot;http://localhost:9999/realms/StroomRealm/.well-known/openid-configuration&amp;quot;
        clientId: &amp;quot;StroomProxyClient&amp;quot;
        clientSecret: &amp;quot;THE_PROXY_CLIENT_SECRET&amp;quot;
        logoutEndpoint: &amp;quot;http://localhost:9999/realms/StroomRealm/protocol/openid-connect/logout&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If Stroom-Proxy is configured to forward data on to another Stroom-Proxy or Stroom instance then it can use tokens when forwarding that data.
This assumes the downstream Stroom or Stroom-Proxy is also configured to use the same external IDP.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  forwardHttpDestinations:

      # If true, adds a token for the service user to the request
    - addOpenIdAccessToken: true
      enabled: true
      name: &amp;quot;downstream&amp;quot;
      forwardUrl: &amp;quot;http://somehost/stroom/datafeed&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The token used will be for the service user account of the identity provider client used by Stroom-Proxy.&lt;/p&gt;
&lt;p&gt;That token&amp;rsquo;s audience is validated at the destination just like any other, so the destination needs either an audience mapper on the proxy&amp;rsquo;s client, or the audience the proxy&amp;rsquo;s tokens actually carry listed in its &lt;code&gt;allowedAudiences&lt;/code&gt;.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: External IDP</title>
      <link>/docs/install-guide/setup/open-id/external-idp/</link>
      <pubDate>Wed, 23 Sep 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/external-idp/</guid>
      <description>
        
        
        &lt;p&gt;You may be running Stroom in an environment with an existing &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; (KeyCloak, Cognito, Google, Active Directory, etc.) and want to use that for authenticating users.
Stroom supports 3rd party IDPs that conform to the &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://openid.net/connect/&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;Open ID Connect (external link to https://openid.net/connect/)&#34;&gt;
    &lt;span&gt;Open ID Connect&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 specification.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;



    &lt;p&gt;On this page and its children, &lt;strong&gt;Stroom itself signs the user in&lt;/strong&gt; at the provider.
If a load balancer or reverse proxy in front of Stroom performs the sign in instead - an AWS ALB with an &lt;code&gt;authenticate-cognito&lt;/code&gt; or &lt;code&gt;authenticate-oidc&lt;/code&gt; rule, NGINX with oauth2-proxy - you want &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/&#34;&gt;Edge Proxy RP&lt;/a&gt;, not this page.&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;This page describes what Stroom needs from any such provider.
It applies whichever provider you use, so read it before following one of the provider specific pages.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/stroom-configuration/&#34;&gt;Stroom Configuration&lt;/a&gt; - every Stroom setting involved, and what it does.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/keycloak/&#34;&gt;KeyCloak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/cognito/&#34;&gt;Amazon Cognito&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/google/&#34;&gt;Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/azure-ad/&#34;&gt;Microsoft Entra ID (Azure AD)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-flow&#34;&gt;The Flow&lt;/h2&gt;
&lt;p&gt;Stroom is the Open ID Connect client, or Relying Party, and runs the authorization code flow itself.&lt;/p&gt;







  
  
  
  
  







  



&lt;div class=&#34;card rounded shadow-stroom p-2 td-post-card mb-4 mt-4&#34; style=&#34;width: fit-content;&#34;&gt;

  &lt;a title=&#34;images/install-guide/open-id/external-idp-flow.puml.svg&#34; href=&#34;../../images/install-guide/open-id/external-idp-flow.puml.svg&#34;&gt;
    &lt;figure style=&#34;margin-block-end: 0px&#34; &gt;
      
      &lt;img class=&#34;card-img-top&#34; src=&#34;../../images/install-guide/open-id/external-idp-flow.puml.svg&#34; style=&#34;max-width: fit-content&#34; alt=&#34;images/install-guide/open-id/external-idp-flow.puml.svg&#34;&gt;
      

      
      &lt;div class=&#34;card-body px-0 pt-2 pb-0&#34;&gt;
        &lt;hr style=&#34;border-top: 1px solid #ddd; margin-top: 0px; margin-bottom:4px;&#34;&gt;
        &lt;figcaption class=&#34;card-text&#34; style=&#34;font-size: smaller; text-align: center;&#34;&gt;External IDP sign in flow&lt;/figcaption&gt;
      &lt;/div&gt;
      
    &lt;/figure&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;The requirements in the rest of this page are all steps in that sequence: the &lt;a href=&#34;#the-redirect-uri&#34;&gt;redirect URI&lt;/a&gt; the IDP sends the code to, the &lt;a href=&#34;#pkce&#34;&gt;PKCE&lt;/a&gt; challenge and verifier, the &lt;a href=&#34;#claims&#34;&gt;claims&lt;/a&gt; Stroom reads from the ID token, and the &lt;a href=&#34;#token-validation&#34;&gt;token validation&lt;/a&gt; it performs before creating the session.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;If a proxy in front of Stroom performs this flow instead, see &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/#the-flow&#34;&gt;Edge Proxy RP&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;what-stroom-needs-from-the-provider&#34;&gt;What Stroom Needs from the Provider&lt;/h2&gt;
&lt;p&gt;Stroom is a confidential OAuth 2.0 client using the authorization code flow.
To register it with a provider you need the following.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client type&lt;/td&gt;
&lt;td&gt;Confidential, i.e. one that is issued a client secret. Stroom is a server side application and keeps its secret on the server.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grant type / flow&lt;/td&gt;
&lt;td&gt;Authorization code. Stroom does not use the implicit or password flows.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redirect URI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://STROOM_FQDN/api/auth/flow/v1/signin-oidc&lt;/code&gt; (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post logout redirect URI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://STROOM_FQDN/&lt;/code&gt; (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PKCE&lt;/td&gt;
&lt;td&gt;Supported, and may be required (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scopes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openid&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; by default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;STROOM_FQDN&lt;/code&gt; is the public facing address of Stroom, which is what you have configured as &lt;code&gt;appConfig.publicUri&lt;/code&gt; and is the address users type into their browser.
If Stroom is behind a load balancer or Nginx, it is that address and not the address of an individual node.&lt;/p&gt;
&lt;h3 id=&#34;the-redirect-uri&#34;&gt;The Redirect URI&lt;/h3&gt;
&lt;p&gt;The redirect URI, sometimes called the callback or reply URL, is where the provider sends the user&amp;rsquo;s browser once they have authenticated.&lt;/p&gt;
&lt;p&gt;Stroom uses a single fixed redirect URI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://STROOM_FQDN/api/auth/flow/v1/signin-oidc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Register that exact value.
It does not vary with the page the user was trying to reach, so there is no need to register a wildcard, and you should not do so.
The page the user came from is remembered separately by Stroom and does not travel through the provider.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    Earlier versions of Stroom sent the user&amp;rsquo;s current page as the redirect URI, which meant registering a wildcard such as &lt;code&gt;https://STROOM_FQDN/*&lt;/code&gt; at the provider.
That is no longer how it works.
If you are upgrading, replace any such wildcard with the single exact URI above, otherwise sign in will be refused by the provider.

&lt;/div&gt;


&lt;p&gt;If Stroom is served under a path prefix, i.e. &lt;code&gt;appConfig.publicUri.pathPrefix&lt;/code&gt; is set, that prefix comes before &lt;code&gt;/api&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;the-post-logout-redirect-uri&#34;&gt;The Post Logout Redirect URI&lt;/h3&gt;
&lt;p&gt;When a user signs out, Stroom sends them to the provider&amp;rsquo;s logout endpoint and asks to be returned to Stroom&amp;rsquo;s public root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://STROOM_FQDN/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stroom appends a &lt;code&gt;state&lt;/code&gt; query parameter to that URI.
Providers that match post logout redirect URIs exactly may need to be told to permit it, so if sign out leaves the user on an error page at the provider, that is the usual cause.&lt;/p&gt;
&lt;p&gt;The name of the parameter Stroom uses to pass this URI is controlled by &lt;code&gt;logoutRedirectParamName&lt;/code&gt;, which may be &lt;code&gt;post_logout_redirect_uri&lt;/code&gt;, the default and current specification, or &lt;code&gt;redirect_uri&lt;/code&gt; for older providers.&lt;/p&gt;
&lt;h3 id=&#34;pkce&#34;&gt;PKCE&lt;/h3&gt;
&lt;p&gt;Stroom always sends a &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7636&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;PKCE (external link to https://datatracker.ietf.org/doc/html/rfc7636)&#34;&gt;
    &lt;span&gt;PKCE&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 &lt;code&gt;code_challenge&lt;/code&gt; using the &lt;code&gt;S256&lt;/code&gt; method, and the matching &lt;code&gt;code_verifier&lt;/code&gt; when it exchanges the authorization code for tokens.&lt;/p&gt;
&lt;p&gt;There is nothing to configure in Stroom for this.
Providers that require PKCE, and anything following OAuth 2.1, will be satisfied, and providers that do not support it ignore the extra parameters.
Where the provider lets you insist on PKCE, as KeyCloak does, you can safely turn that on.&lt;/p&gt;
&lt;h3 id=&#34;claims&#34;&gt;Claims&lt;/h3&gt;
&lt;p&gt;Stroom reads three things about a user from the token.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uniqueIdentityClaim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Links the identity at the provider to a Stroom user. Must be unique at the provider and must never change for a given person.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;userDisplayNameClaim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;preferred_username&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A friendlier name shown in the Stroom UI. Need not be unique and may change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fullNameClaimTemplate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;${name}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Builds the user&amp;rsquo;s full name from claim values, e.g. &lt;code&gt;&#39;${given_name} ${family_name}&#39;&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Not every provider issues &lt;code&gt;preferred_username&lt;/code&gt;, so check the provider page before assuming the defaults will do.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    Do not set &lt;code&gt;uniqueIdentityClaim&lt;/code&gt; to an email address or a username.
Both can be reassigned to a different person at the provider, and whoever holds it next would inherit the Stroom user, along with its permissions.

&lt;/div&gt;


&lt;h2 id=&#34;token-validation&#34;&gt;Token Validation&lt;/h2&gt;
&lt;p&gt;Stroom validates every token it is given, whether that is the &lt;code&gt;id_token&lt;/code&gt; from an interactive sign in or a bearer access token presented to the API.&lt;/p&gt;
&lt;p&gt;The signature must verify against a key from the provider&amp;rsquo;s JWKS, and the algorithm must be one of the RSA, RSA-PSS or ECDSA families.
Unsigned tokens and tokens signed with an HMAC algorithm are refused.&lt;/p&gt;
&lt;p&gt;The issuer must match what the provider advertised, and the audience must match what Stroom expects.
Audience validation is the part most likely to need attention, because providers differ in what they put in the &lt;code&gt;aud&lt;/code&gt; claim of an &lt;strong&gt;access&lt;/strong&gt; token.
See &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/stroom-configuration/#audience-validation&#34;&gt;Audience validation&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    &lt;code&gt;id_token&lt;/code&gt;s carry an &lt;code&gt;aud&lt;/code&gt; claim holding the client id at every provider, so interactive sign in works with the default settings.
It is API authentication with access tokens where providers differ.

&lt;/div&gt;


&lt;h2 id=&#34;users-and-permissions&#34;&gt;Users and Permissions&lt;/h2&gt;
&lt;p&gt;Authentication is handled by the provider.
Authorisation, i.e. what a user may do once they are in, is always handled by Stroom.&lt;/p&gt;
&lt;p&gt;Whenever a user successfully signs in via the provider, Stroom automatically creates an entry for them in its own user table.
That user starts with no permissions and no group memberships, so an administrator must grant those.
This does mean a new user has to sign in once before an administrator can do anything with them.&lt;/p&gt;
&lt;p&gt;The very first administrator is a chicken and egg problem, since there is nobody able to grant permissions yet.
That is solved with the &lt;code&gt;manage_users&lt;/code&gt; command, described on each provider page.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;See &lt;a href=&#34;../../docs/install-guide/setup/create-first-admin/&#34;&gt;Creating the First Administrator&lt;/a&gt; for the full procedure, and &lt;a href=&#34;../../docs/install-guide/setup/open-id/accounts-users/&#34;&gt;Accounts and Users&lt;/a&gt; for how identities at the provider relate to Stroom users.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;


      </description>
    </item>
    
    <item>
      <title>Docs: NGINX, oauth2-proxy and KeyCloak</title>
      <link>/docs/install-guide/setup/open-id/edge-proxy/nginx-oauth2-proxy/</link>
      <pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/edge-proxy/nginx-oauth2-proxy/</guid>
      <description>
        
        
        &lt;p&gt;In this deployment &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://oauth2-proxy.github.io/oauth2-proxy/&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;oauth2-proxy (external link to https://oauth2-proxy.github.io/oauth2-proxy/)&#34;&gt;
    &lt;span&gt;oauth2-proxy&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 is the Open ID Connect Relying Party.
NGINX asks it to authorise each request (&lt;code&gt;auth_request&lt;/code&gt;); oauth2-proxy completes the code flow against the &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, holds its session in &lt;code&gt;_oauth2_proxy&lt;/code&gt; cookies, and hands back the IDP&amp;rsquo;s token, which NGINX forwards to Stroom as an &lt;code&gt;Authorization: Bearer&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;Unlike the ALB, nothing here mints its own token: Stroom receives the &lt;strong&gt;IDP&amp;rsquo;s own token&lt;/strong&gt; and verifies it against the IDP&amp;rsquo;s published keys, exactly as it would verify a token presented by an API client.
That is why this pattern works unchanged with KeyCloak, Cognito or Entra ID behind the proxy; the page is written for KeyCloak, and &lt;a href=&#34;#entra-id-instead-of-keycloak&#34;&gt;Entra ID&lt;/a&gt; at the end lists what differs.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;Read &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/&#34;&gt;Edge Proxy RP&lt;/a&gt; first for the model, the path scoping rules and the trust prerequisites.
The &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/keycloak/&#34;&gt;KeyCloak&lt;/a&gt; page covers setting up the realm and client; here the client belongs to oauth2-proxy rather than to Stroom.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;keycloak-setup&#34;&gt;KeyCloak Setup&lt;/h2&gt;
&lt;p&gt;Create a realm and a confidential client as described on the &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/keycloak/&#34;&gt;KeyCloak&lt;/a&gt; page, with one difference: the client&amp;rsquo;s redirect URI is &lt;strong&gt;oauth2-proxy&amp;rsquo;s&lt;/strong&gt; callback, &lt;code&gt;https://STROOM_FQDN/oauth2/callback&lt;/code&gt;, not Stroom&amp;rsquo;s.&lt;/p&gt;
&lt;p&gt;No second client for Stroom is needed.&lt;/p&gt;
&lt;h2 id=&#34;oauth2-proxy-setup&#34;&gt;Oauth2-proxy Setup&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;provider = &amp;quot;keycloak-oidc&amp;quot;
oidc_issuer_url = &amp;quot;https://IDP_HOST/realms/REALM&amp;quot;
client_id = &amp;quot;stroom-proxy-client&amp;quot;
client_secret = &amp;quot;THE_CLIENT_SECRET&amp;quot;
redirect_url = &amp;quot;https://STROOM_FQDN/oauth2/callback&amp;quot;
cookie_secret = &amp;quot;RANDOM_32_BYTES_BASE64&amp;quot;

# Hand the IDP&#39;s token to NGINX so it can be forwarded to Stroom.
set_authorization_header = true

# Refresh the session before the access token expires, so the forwarded
# token is always live.
cookie_refresh = &amp;quot;4m&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    &lt;code&gt;set_authorization_header&lt;/code&gt; forwards the &lt;strong&gt;ID token&lt;/strong&gt;, not the access token.
Stroom verifies either happily, but this means the &lt;code&gt;requiredAccessTokenType&lt;/code&gt; Stroom setting must be left unset — an ID token does not carry an access token&amp;rsquo;s &lt;code&gt;typ&lt;/code&gt; header and would be rejected.

&lt;/div&gt;


&lt;h2 id=&#34;nginx-setup&#34;&gt;NGINX Setup&lt;/h2&gt;
&lt;p&gt;The essential shape — authenticate the browser paths, forward the machine paths untouched, and &lt;strong&gt;overwrite&lt;/strong&gt; the &lt;code&gt;Authorization&lt;/code&gt; header on everything proxied:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-nginx&#34;&gt;server {
    listen 443 ssl;
    server_name STROOM_FQDN;

    # oauth2-proxy&#39;s own endpoints (sign in, callback, sign out)
    location /oauth2/ {
        proxy_pass       http://oauth2-proxy:4180;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Subrequest endpoint used by auth_request
    location = /oauth2/auth {
        internal;
        proxy_pass              http://oauth2-proxy:4180;
        proxy_set_header        Content-Length &amp;quot;&amp;quot;;
        proxy_pass_request_body off;
    }

    # Browser facing paths - authenticated
    location / {
        auth_request /oauth2/auth;
        error_page 401 = /oauth2/sign_in;

        # Take the token oauth2-proxy returned and forward it to Stroom.
        # proxy_set_header OVERWRITES any client supplied Authorization header,
        # which is one of the trust prerequisites.
        auth_request_set $auth_token $upstream_http_authorization;
        proxy_set_header Authorization $auth_token;

        proxy_pass       https://stroom-backend:8080/;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host  $host;
    }

    # Machine paths - no auth_request, Stroom authenticates these itself
    location /datafeed  { proxy_pass https://stroom-backend:8080/datafeed; }
    location /remoting/ { proxy_pass https://stroom-backend:8080/remoting/; }
    location /status    { proxy_pass https://stroom-backend:8080/status; }

    # Post logout landing page, served by NGINX itself so it needs no session
    location = /signed-out {
        default_type text/html;
        return 200 &#39;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;p&amp;gt;You have been signed out.&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;/&amp;quot;&amp;gt;Sign in again&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&#39;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;stroom-configuration&#34;&gt;Stroom Configuration&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;server:
  applicationConnectors:
    - type: http
      port: 8080
      useForwardedHeaders: true
      maxRequestHeaderSize: 32KiB    # oauth2-proxy chunks its session cookie

appConfig:
  publicUri: &amp;quot;https://STROOM_FQDN&amp;quot;
  security:
    authentication:
      edgeAuthentication:
        enabled: true
        logout:
          cookiesToExpire: [ &amp;quot;_oauth2_proxy&amp;quot; ]
          signOutUrl: &amp;quot;https://STROOM_FQDN/oauth2/sign_out&amp;quot;
      openId:
        identityProviderType: EXTERNAL_IDP
        # The real IDP&#39;s discovery document - Stroom verifies the forwarded token
        # against the keys it advertises.
        openIdConfigurationEndpoint: &amp;quot;https://IDP_HOST/realms/REALM/\
.well-known/openid-configuration&amp;quot;
        # oauth2-proxy&#39;s client - the forwarded token&#39;s audience is this client.
        clientId: &amp;quot;stroom-proxy-client&amp;quot;
        # Leave requiredAccessTokenType unset: oauth2-proxy forwards the ID token.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;clientSecret&lt;/code&gt; is needed; Stroom runs no flow of its own.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    oauth2-proxy can also run in a mode that forwards only plain headers such as &lt;code&gt;X-Forwarded-User&lt;/code&gt; or &lt;code&gt;X-Auth-Request-Email&lt;/code&gt; rather than a token.
Stroom does &lt;strong&gt;not&lt;/strong&gt; support that: there is no signature to verify, so trusting those headers would mean trusting every hop unconditionally.
Always configure &lt;code&gt;set_authorization_header&lt;/code&gt; so a verifiable token reaches Stroom.

&lt;/div&gt;


&lt;h2 id=&#34;logout&#34;&gt;Logout&lt;/h2&gt;
&lt;p&gt;The configuration above expires oauth2-proxy&amp;rsquo;s (chunked) session cookies and sends the browser to &lt;code&gt;/oauth2/sign_out&lt;/code&gt;, which ends the proxy session.
To also end the KeyCloak session, give oauth2-proxy&amp;rsquo;s sign out a redirect to KeyCloak&amp;rsquo;s end session endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;          signOutUrl: &amp;quot;https://STROOM_FQDN/oauth2/sign_out?rd=https%3A%2F%2FIDP_HOST%2Frealms%2FREALM%2Fprotocol%2Fopenid-connect%2Flogout&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(The &lt;code&gt;rd&lt;/code&gt; value must be URL encoded and allowed by oauth2-proxy&amp;rsquo;s &lt;code&gt;whitelist_domains&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;With no return address, KeyCloak shows its own &lt;em&gt;You are logged out&lt;/em&gt; page, which is fine.
To land on the &lt;code&gt;/signed-out&lt;/code&gt; page served by NGINX instead, add a &lt;code&gt;post_logout_redirect_uri&lt;/code&gt; (and &lt;code&gt;client_id&lt;/code&gt;) to KeyCloak&amp;rsquo;s logout URL, URL encoded within the &lt;code&gt;rd&lt;/code&gt; value, and add &lt;code&gt;https://STROOM_FQDN/signed-out&lt;/code&gt; to the client&amp;rsquo;s &lt;em&gt;Valid post logout redirect URIs&lt;/em&gt; in KeyCloak.
Either way the landing page must not be behind &lt;code&gt;auth_request&lt;/code&gt;, or the sign in flow simply restarts.&lt;/p&gt;
&lt;h2 id=&#34;verifying-it-works&#34;&gt;Verifying it Works&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Loading Stroom redirects via oauth2-proxy to KeyCloak; after signing in, the UI loads.&lt;/li&gt;
&lt;li&gt;The request to &lt;code&gt;/api/auth/flow/v1/status&lt;/code&gt; returns &lt;code&gt;200&lt;/code&gt; with &lt;code&gt;&amp;quot;authenticated&amp;quot;: true&lt;/code&gt;, and there is no subsequent navigation to KeyCloak&amp;rsquo;s &lt;code&gt;/auth&lt;/code&gt; endpoint.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl -H &amp;quot;Authorization: Bearer $TOKEN&amp;quot; https://stroom-backend:8080/api/...&lt;/code&gt; from inside the network still works — machine access does not traverse the proxy.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;entra-id-instead-of-keycloak&#34;&gt;Entra ID Instead of KeyCloak&lt;/h2&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;The &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/azure-ad/&#34;&gt;Microsoft Entra ID&lt;/a&gt; page covers the app registration; here it belongs to oauth2-proxy rather than to Stroom.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The NGINX configuration and the Stroom &lt;code&gt;edgeAuthentication&lt;/code&gt; settings are unchanged.
What differs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;App registration&lt;/strong&gt; - create it as on the Entra ID page, but with a Web redirect URI of &lt;code&gt;https://STROOM_FQDN/oauth2/callback&lt;/code&gt; (oauth2-proxy&amp;rsquo;s callback), and &lt;code&gt;https://STROOM_FQDN/signed-out&lt;/code&gt; as a second redirect URI for the post logout return.
The client secret goes in oauth2-proxy&amp;rsquo;s configuration and expires within 24 months; record the date.
The &lt;em&gt;Expose an API&lt;/em&gt; step is not needed for browser sign in, because Stroom receives the ID token, not an access token.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;oauth2-proxy&lt;/strong&gt; (7.8 or later, which added the &lt;code&gt;entra-id&lt;/code&gt; provider and deprecated the old &lt;code&gt;azure&lt;/code&gt; one):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;provider = &amp;quot;entra-id&amp;quot;
oidc_issuer_url = &amp;quot;https://login.microsoftonline.com/TENANT_ID/v2.0&amp;quot;
client_id = &amp;quot;PROXY_CLIENT_ID&amp;quot;
client_secret = &amp;quot;PROXY_CLIENT_SECRET&amp;quot;
# &#39;profile&#39; for the name claims, &#39;offline_access&#39; for a refresh token, without
# which cookie_refresh cannot renew the session.
scope = &amp;quot;openid email profile offline_access&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The remaining settings (&lt;code&gt;redirect_url&lt;/code&gt;, &lt;code&gt;cookie_secret&lt;/code&gt;, &lt;code&gt;set_authorization_header&lt;/code&gt;, &lt;code&gt;cookie_refresh&lt;/code&gt;) are as above.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stroom&lt;/strong&gt; - point &lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt; at &lt;code&gt;https://login.microsoftonline.com/TENANT_ID/v2.0/.well-known/openid-configuration&lt;/code&gt; and set &lt;code&gt;clientId&lt;/code&gt; to &lt;code&gt;PROXY_CLIENT_ID&lt;/code&gt;.
Because Stroom receives the &lt;strong&gt;ID token&lt;/strong&gt;, the claims are the ones the Entra ID page describes: &lt;code&gt;oid&lt;/code&gt;, &lt;code&gt;preferred_username&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; are all present, so &lt;code&gt;uniqueIdentityClaim: &amp;quot;oid&amp;quot;&lt;/code&gt; is available here, unlike behind an &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/aws-alb/#identity-claims&#34;&gt;ALB&lt;/a&gt; where only the user info claims arrive.
Decide on &lt;code&gt;sub&lt;/code&gt; or &lt;code&gt;oid&lt;/code&gt; before the first user signs in.
No &lt;code&gt;clientSecret&lt;/code&gt;, &lt;code&gt;validIssuers&lt;/code&gt;, &lt;code&gt;allowedAudiences&lt;/code&gt; or &lt;code&gt;requestScopes&lt;/code&gt; are needed: Stroom runs no flow, only v2.0 tokens are involved, the ID token&amp;rsquo;s audience is the client id, and the scopes are set on oauth2-proxy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Logout&lt;/strong&gt; - Entra ID&amp;rsquo;s end session endpoint, with the return address registered as above:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;          signOutUrl: &amp;quot;https://STROOM_FQDN/oauth2/sign_out?rd=https%3A%2F%2Flogin.microsoftonline.com%2FTENANT_ID%2Foauth2%2Fv2.0%2Flogout%3Fpost_logout_redirect_uri%3Dhttps%3A%2F%2FSTROOM_FQDN%2Fsigned-out&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;login.microsoftonline.com&lt;/code&gt; must be in oauth2-proxy&amp;rsquo;s &lt;code&gt;whitelist_domains&lt;/code&gt; for the &lt;code&gt;rd&lt;/code&gt; to be honoured.
Without the &lt;code&gt;post_logout_redirect_uri&lt;/code&gt;, Entra ID shows a generic signed out page of its own instead.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Amazon Cognito</title>
      <link>/docs/install-guide/setup/open-id/external-idp/cognito/</link>
      <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/external-idp/cognito/</guid>
      <description>
        
        
        &lt;p&gt;This page covers using an &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;Amazon Cognito (external link to https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html)&#34;&gt;
    &lt;span&gt;Amazon Cognito&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 user pool as Stroom&amp;rsquo;s &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;Read &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;External IDP&lt;/a&gt; first for what Stroom needs from any provider, and &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/stroom-configuration/&#34;&gt;Stroom Configuration&lt;/a&gt; for what each setting does.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;



    &lt;p&gt;This page covers Stroom itself being the OIDC client of the user pool.
If Stroom sits behind an AWS Application Load Balancer whose listener rule does the authentication, the load balancer is the client instead - see &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/aws-alb/#amazon-cognito&#34;&gt;AWS Application Load Balancer&lt;/a&gt;.&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;Cognito differs from a typical OIDC provider in two ways that directly affect the Stroom configuration, so they are worth knowing before you start.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Its &lt;strong&gt;access tokens carry no &lt;code&gt;aud&lt;/code&gt; claim&lt;/strong&gt;, using &lt;code&gt;client_id&lt;/code&gt; instead.
Stroom requires an audience claim by default, so this has to be turned off.&lt;/li&gt;
&lt;li&gt;Its authorization, token and sign out endpoints belong to the &lt;strong&gt;hosted UI domain&lt;/strong&gt;, which is separate from the user pool&amp;rsquo;s issuer host.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;creating-the-user-pool-and-app-client&#34;&gt;Creating the User Pool and App Client&lt;/h2&gt;
&lt;p&gt;In the AWS console, under Cognito:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a user pool, or use an existing one.&lt;/li&gt;
&lt;li&gt;Configure a &lt;strong&gt;domain&lt;/strong&gt; for the pool, either a Cognito prefix domain giving &lt;code&gt;https://YOUR_PREFIX.auth.REGION.amazoncognito.com&lt;/code&gt;, or your own custom domain.
This provides the hosted UI and the OAuth endpoints, and is required.&lt;/li&gt;
&lt;li&gt;Create an &lt;strong&gt;app client&lt;/strong&gt; of the confidential type, i.e. one with a client secret.&lt;/li&gt;
&lt;li&gt;Enable the &lt;strong&gt;Authorization code grant&lt;/strong&gt; for the client.
Do not enable the implicit grant.&lt;/li&gt;
&lt;li&gt;Set the OpenID Connect scopes to at least &lt;code&gt;openid&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Allowed callback URL&lt;/strong&gt; to &lt;code&gt;https://STROOM_FQDN/api/auth/flow/v1/signin-oidc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Allowed sign out URL&lt;/strong&gt; to &lt;code&gt;https://STROOM_FQDN/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Note the app client id and app client secret, and the user pool id.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Where &lt;code&gt;STROOM_FQDN&lt;/code&gt; is the public address of Stroom, i.e. what you have set as &lt;code&gt;appConfig.publicUri&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    Cognito requires callback URLs to use &lt;code&gt;https&lt;/code&gt;, other than for &lt;code&gt;http://localhost&lt;/code&gt;.
It matches them exactly and does not accept wildcards, so register the single URI above rather than anything broader.

&lt;/div&gt;


&lt;p&gt;Cognito supports PKCE, and Stroom always sends an &lt;code&gt;S256&lt;/code&gt; challenge, so there is nothing to configure for it.&lt;/p&gt;
&lt;h2 id=&#34;the-endpoints&#34;&gt;The Endpoints&lt;/h2&gt;
&lt;p&gt;Two different hosts are involved.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Issuer, discovery document, JWKS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://cognito-idp.REGION.amazonaws.com/USER_POOL_ID&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorization, token, sign out&lt;/td&gt;
&lt;td&gt;Your pool&amp;rsquo;s domain, e.g. &lt;code&gt;https://YOUR_PREFIX.auth.REGION.amazoncognito.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The discovery document is at:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://cognito-idp.REGION.amazonaws.com/USER_POOL_ID/.well-known/openid-configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set &lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt; to it, so that Stroom picks up the issuer and the JWKS URI.&lt;/p&gt;
&lt;p&gt;Set &lt;code&gt;authEndpoint&lt;/code&gt; and &lt;code&gt;tokenEndpoint&lt;/code&gt; explicitly to your pool&amp;rsquo;s domain, since those are the endpoints your users and Stroom actually need to reach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://YOUR_PREFIX.auth.REGION.amazoncognito.com/oauth2/authorize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://YOUR_PREFIX.auth.REGION.amazoncognito.com/oauth2/token&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    Compare these against what your pool&amp;rsquo;s discovery document advertises.
Where the two agree you can leave &lt;code&gt;authEndpoint&lt;/code&gt; and &lt;code&gt;tokenEndpoint&lt;/code&gt; unset and let the discovery document supply them; setting them explicitly is the reliable option.

&lt;/div&gt;


&lt;h2 id=&#34;configuring-stroom&#34;&gt;Configuring Stroom&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  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
        openIdConfigurationEndpoint: &amp;quot;https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_ABC123456/.well-known/openid-configuration&amp;quot;
        # The hosted UI endpoints, which are on the pool&#39;s domain rather than the issuer host
        authEndpoint: &amp;quot;https://mydomain.auth.eu-west-2.amazoncognito.com/oauth2/authorize&amp;quot;
        tokenEndpoint: &amp;quot;https://mydomain.auth.eu-west-2.amazoncognito.com/oauth2/token&amp;quot;
        logoutEndpoint: &amp;quot;https://mydomain.auth.eu-west-2.amazoncognito.com/logout&amp;quot;
        # The app client id and secret
        clientId: &amp;quot;1h57kf5cpparlm9m52319hsnrf&amp;quot;
        clientSecret: &amp;quot;THE_APP_CLIENT_SECRET&amp;quot;
        # Cognito requires the token request to be sent as a form. This is the default.
        formTokenRequest: true
        # Cognito access tokens carry no &#39;aud&#39; claim, so an absent one must not be a failure.
        # An &#39;aud&#39; claim that IS present, as on an id_token, is still validated against clientId.
        audienceClaimRequired: false
        # Cognito does not issue &#39;preferred_username&#39;
        userDisplayNameClaim: &amp;quot;cognito:username&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;audience-validation&#34;&gt;Audience Validation&lt;/h3&gt;
&lt;p&gt;This is the setting most likely to catch you out.&lt;/p&gt;
&lt;p&gt;A Cognito &lt;strong&gt;id_token&lt;/strong&gt;, used by interactive sign in, carries &lt;code&gt;aud&lt;/code&gt; set to the app client id, so it validates against &lt;code&gt;clientId&lt;/code&gt; with no further configuration.&lt;/p&gt;
&lt;p&gt;A Cognito &lt;strong&gt;access token&lt;/strong&gt;, used for API calls, carries no &lt;code&gt;aud&lt;/code&gt; claim at all; the equivalent information is in a &lt;code&gt;client_id&lt;/code&gt; claim, which is not something Stroom validates against.
With the default of &lt;code&gt;audienceClaimRequired: true&lt;/code&gt; those tokens are refused, so set it to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    &lt;code&gt;audienceClaimRequired&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;, having previously defaulted to &lt;code&gt;false&lt;/code&gt;.
If you are upgrading an existing Cognito deployment, add &lt;code&gt;audienceClaimRequired: false&lt;/code&gt; before you upgrade, otherwise API calls made with Cognito access tokens will start being refused.
Interactive sign in is unaffected.

&lt;/div&gt;


&lt;p&gt;Leave &lt;code&gt;validateAudience&lt;/code&gt; at its default of &lt;code&gt;true&lt;/code&gt;.
Setting it to &lt;code&gt;false&lt;/code&gt; would switch off audience checking for the &lt;code&gt;id_token&lt;/code&gt; as well, which Cognito populates perfectly well.&lt;/p&gt;
&lt;h3 id=&#34;claims&#34;&gt;Claims&lt;/h3&gt;
&lt;p&gt;Cognito does not issue a &lt;code&gt;preferred_username&lt;/code&gt; claim unless the user pool has been set up with that attribute, so the Stroom default for &lt;code&gt;userDisplayNameClaim&lt;/code&gt; will usually not resolve.
Use &lt;code&gt;cognito:username&lt;/code&gt;, or &lt;code&gt;email&lt;/code&gt; where every user has one.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uniqueIdentityClaim&lt;/code&gt; should be left as &lt;code&gt;sub&lt;/code&gt;, which for Cognito is a UUID that is stable for the life of the user.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    Do not be tempted to use &lt;code&gt;email&lt;/code&gt; or &lt;code&gt;cognito:username&lt;/code&gt; as the &lt;code&gt;uniqueIdentityClaim&lt;/code&gt;.
Both can be changed or reassigned to another person, who would then inherit the Stroom user and its permissions.

&lt;/div&gt;


&lt;p&gt;For &lt;code&gt;fullNameClaimTemplate&lt;/code&gt; to resolve, the corresponding attributes must be populated on the user and included in the token.
Add &lt;code&gt;profile&lt;/code&gt; to &lt;code&gt;requestScopes&lt;/code&gt; if you need &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;given_name&lt;/code&gt; or &lt;code&gt;family_name&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;signing-out&#34;&gt;Signing Out&lt;/h3&gt;
&lt;p&gt;Cognito&amp;rsquo;s sign out endpoint has historically expected the return address in a &lt;code&gt;logout_uri&lt;/code&gt; parameter rather than the &lt;code&gt;post_logout_redirect_uri&lt;/code&gt; that Stroom sends.&lt;/p&gt;
&lt;p&gt;Check whether sign out returns your users to Stroom.
If it leaves them on an error page at Cognito, try:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;        logoutRedirectParamName: &amp;quot;redirect_uri&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those two values are the only ones Stroom accepts.
If neither works with your pool, leave &lt;code&gt;logoutEndpoint&lt;/code&gt; unset; signing out will then end the Stroom session without signing the user out of Cognito, which means their next visit will sign them straight back in without being asked for credentials.&lt;/p&gt;
&lt;h3 id=&#34;access-token-type&#34;&gt;Access Token Type&lt;/h3&gt;
&lt;p&gt;Leave &lt;code&gt;requiredAccessTokenType&lt;/code&gt; unset unless you have decoded the header of a real Cognito access token and confirmed what it contains.
It is a hardening measure, and setting it to a value your provider does not use will refuse every API call.&lt;/p&gt;
&lt;h2 id=&#34;setting-up-the-admin-user-in-stroom&#34;&gt;Setting up the Admin User in Stroom&lt;/h2&gt;
&lt;p&gt;The bootstrap process is the same as for any provider.
Find the &lt;code&gt;sub&lt;/code&gt; of the user who is to be the administrator, which for Cognito is the user&amp;rsquo;s UUID as shown in the console, then run the &lt;code&gt;manage_users&lt;/code&gt; command &lt;strong&gt;before&lt;/strong&gt; starting Stroom for the first time.&lt;/p&gt;

  






&lt;div class=&#34;code-toolbar&#34;&gt;
  &lt;pre 
    class=&#34;command-line language-bash&#34; 
    data-user=&#34;user&#34; 
    data-host=&#34;localhost&#34; 
    data-continuation-str=&#34;\&#34;
    data-filter-output=&#34;(out)&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;subject_id=&amp;#34;XXX&amp;#34;; \
java -jar /absolute/path/to/stroom-app-all.jar \
  manage_users \
  --createUser &amp;#34;${subject_id}&amp;#34; \
  --createGroup Administrators \
  --addToGroup &amp;#34;${subject_id}&amp;#34; Administrators \
  --grantPermission Administrators &amp;#34;Administrator&amp;#34; \
  ../local.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;See &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/keycloak/#setting-up-the-admin-user-in-stroom&#34;&gt;KeyCloak&lt;/a&gt; for a fuller description of what this command does, and &lt;a href=&#34;../../docs/user-guide/tools/command-line/&#34;&gt;Command Line Tools&lt;/a&gt; for its options.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;stroom-proxy-with-cognito&#34;&gt;Stroom-Proxy with Cognito&lt;/h2&gt;
&lt;p&gt;A Stroom-Proxy obtains a token for its own service user using the client credentials grant.&lt;/p&gt;
&lt;p&gt;In Cognito that grant requires a resource server with custom scopes defined on it, and the resulting tokens carry those custom scopes rather than &lt;code&gt;openid&lt;/code&gt;.
Set &lt;code&gt;clientCredentialsScopes&lt;/code&gt; to the custom scopes you have defined:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  security:
    authentication:
      openId:
        identityProviderType: EXTERNAL_IDP
        openIdConfigurationEndpoint: &amp;quot;https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_ABC123456/.well-known/openid-configuration&amp;quot;
        tokenEndpoint: &amp;quot;https://mydomain.auth.eu-west-2.amazoncognito.com/oauth2/token&amp;quot;
        clientId: &amp;quot;THE_PROXY_APP_CLIENT_ID&amp;quot;
        clientSecret: &amp;quot;THE_PROXY_APP_CLIENT_SECRET&amp;quot;
        formTokenRequest: true
        audienceClaimRequired: false
        clientCredentialsScopes:
          - &amp;quot;https://stroom.example.com/api.write&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The destination the proxy forwards to must be configured to accept the tokens this produces, which again means &lt;code&gt;audienceClaimRequired: false&lt;/code&gt; at that end.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Edge Proxy as the Relying Party</title>
      <link>/docs/install-guide/setup/open-id/edge-proxy/</link>
      <pubDate>Wed, 23 Sep 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/edge-proxy/</guid>
      <description>
        
        
        &lt;p&gt;Normally Stroom is its own Open ID Connect client, or Relying Party: it redirects the browser to the &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, exchanges the authorization code for tokens, and holds them in its session.
That is the model described by &lt;a href=&#34;../../docs/install-guide/setup/open-id/internal-idp/&#34;&gt;Internal IDP&lt;/a&gt; and &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;External IDP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some environments put an &lt;strong&gt;authenticating reverse proxy&lt;/strong&gt; in front of Stroom instead.
The proxy completes the OIDC flow before a request ever reaches Stroom, holds the tokens itself, and injects a verified credential into each request it forwards.
Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An AWS &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;Application Load Balancer (external link to https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html)&#34;&gt;
    &lt;span&gt;Application Load Balancer&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 with an &lt;code&gt;authenticate-cognito&lt;/code&gt; or &lt;code&gt;authenticate-oidc&lt;/code&gt; listener rule, which injects a signed &lt;code&gt;x-amzn-oidc-data&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;NGINX with &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://oauth2-proxy.github.io/oauth2-proxy/&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;oauth2-proxy (external link to https://oauth2-proxy.github.io/oauth2-proxy/)&#34;&gt;
    &lt;span&gt;oauth2-proxy&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 (or NGINX Plus&amp;rsquo;s native OIDC support, or &lt;code&gt;lua-resty-openidc&lt;/code&gt;), which relays the IDP&amp;rsquo;s token as an &lt;code&gt;Authorization: Bearer&lt;/code&gt; header.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The proxy can authenticate against any OIDC provider — Amazon Cognito, Microsoft Entra ID, KeyCloak, Google and so on.
The proxy&amp;rsquo;s mechanism determines most of the Stroom configuration; the provider behind it changes only a few settings, listed under &lt;a href=&#34;#what-depends-on-the-idp&#34;&gt;What Depends on the IDP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is common in cloud and government estates where a policy requires that unauthenticated traffic never reaches the application.
Stroom supports it as a first class deployment model.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    This model requires the &lt;code&gt;edgeAuthentication&lt;/code&gt; configuration, available from Stroom 7.13.

&lt;/div&gt;


&lt;h2 id=&#34;the-one-rule-exactly-one-relying-party&#34;&gt;The One Rule: Exactly One Relying Party&lt;/h2&gt;
&lt;p&gt;For any given path, exactly one component runs the OIDC flow — the proxy, or Stroom, never both.&lt;/p&gt;
&lt;p&gt;If Stroom is left in its normal configuration behind an authenticating proxy, both try to be the Relying Party.
The browser is driven through a second, redundant OIDC flow stacked on the one the proxy already completed, which needs a second client registration at the IDP, doubles the cookies on every request, and typically fails with &lt;code&gt;Stroom Loading, Authentication Error: Failed to Fetch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Setting &lt;code&gt;edgeAuthentication.enabled&lt;/code&gt; tells Stroom the proxy owns the flow.
Stroom then:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accepts the proxy&amp;rsquo;s injected credential, verified cryptographically on every request, as the user&amp;rsquo;s identity.
The identity is never held in a Stroom session; it is re-derived from the headers on every request, which is also how the proxy&amp;rsquo;s own token refresh reaches Stroom.&lt;/li&gt;
&lt;li&gt;Never starts an OIDC flow of its own, and disables its OIDC callback endpoint.&lt;/li&gt;
&lt;li&gt;Treats the injected credential as needing Cross-Site Request Forgery (CSRF) protection on browser requests, because the browser attaches the &lt;em&gt;proxy&amp;rsquo;s&lt;/em&gt; session cookie automatically, even to cross site requests.&lt;/li&gt;
&lt;li&gt;Can end the proxy&amp;rsquo;s session on logout, not just its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    With &lt;code&gt;edgeAuthentication.enabled&lt;/code&gt; set, &lt;strong&gt;all browser access must go through the proxy&lt;/strong&gt;.
A browser that reaches Stroom directly (an internal load balancer, a port forward) has no way to sign in, because Stroom will not start a flow.
Direct machine access is unaffected: API keys and bearer tokens work as they always have.

&lt;/div&gt;


&lt;h2 id=&#34;the-flow&#34;&gt;The Flow&lt;/h2&gt;
&lt;p&gt;The proxy completes the whole Open ID Connect flow before Stroom sees anything, then injects a credential that Stroom verifies on each request.&lt;/p&gt;







  
  
  
  
  







  



&lt;div class=&#34;card rounded shadow-stroom p-2 td-post-card mb-4 mt-4&#34; style=&#34;width: fit-content;&#34;&gt;

  &lt;a title=&#34;images/install-guide/open-id/edge-proxy-flow.puml.svg&#34; href=&#34;../../images/install-guide/open-id/edge-proxy-flow.puml.svg&#34;&gt;
    &lt;figure style=&#34;margin-block-end: 0px&#34; &gt;
      
      &lt;img class=&#34;card-img-top&#34; src=&#34;../../images/install-guide/open-id/edge-proxy-flow.puml.svg&#34; style=&#34;max-width: fit-content&#34; alt=&#34;images/install-guide/open-id/edge-proxy-flow.puml.svg&#34;&gt;
      

      
      &lt;div class=&#34;card-body px-0 pt-2 pb-0&#34;&gt;
        &lt;hr style=&#34;border-top: 1px solid #ddd; margin-top: 0px; margin-bottom:4px;&#34;&gt;
        &lt;figcaption class=&#34;card-text&#34; style=&#34;font-size: smaller; text-align: center;&#34;&gt;Edge proxy authentication flow&lt;/figcaption&gt;
      &lt;/div&gt;
      
    &lt;/figure&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;Two things in that sequence are easy to miss, and both matter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stroom &lt;strong&gt;never authenticates from a session&lt;/strong&gt;.
The identity is derived from the injected header on every request, which is also how the proxy&amp;rsquo;s own token refresh reaches Stroom.
A Stroom session is still created, because parts of the application need one, and it appears in the sessions list with no user against it.
Expiring it does not sign the user out, because the next request re-derives them from the header.
Signing out is therefore a matter of ending the &lt;em&gt;proxy&amp;rsquo;s&lt;/em&gt; session, which is what &lt;a href=&#34;#edgeauthenticationlogoutcookiestoexpire&#34;&gt;&lt;code&gt;logout.cookiesToExpire&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;#edgeauthenticationlogoutsignouturl&#34;&gt;&lt;code&gt;logout.signOutUrl&lt;/code&gt;&lt;/a&gt; are for.&lt;/li&gt;
&lt;li&gt;What the proxy injects is not necessarily the IDP&amp;rsquo;s own token.
An ALB signs the claims it fetched from the IDP&amp;rsquo;s user info endpoint, while oauth2-proxy relays the IDP&amp;rsquo;s token itself, and the claims available differ accordingly.
See &lt;a href=&#34;#what-depends-on-the-idp&#34;&gt;What Depends on the IDP&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Compare this with the &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/#the-flow&#34;&gt;External IDP&lt;/a&gt; flow, where Stroom performs every step the proxy performs above.&lt;/p&gt;
&lt;h2 id=&#34;stroom-configuration&#34;&gt;Stroom Configuration&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  security:
    authentication:
      edgeAuthentication:
        enabled: true
        logout:
          cookiesToExpire: [ &amp;quot;PROXY_SESSION_COOKIE_PREFIX&amp;quot; ]
          signOutUrl: &amp;quot;PROXY_OR_IDP_SIGN_OUT_URL&amp;quot;
      openId:
        identityProviderType: EXTERNAL_IDP
        # ... provider settings, see the worked examples ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;edgeauthenticationenabled&#34;&gt;&lt;code&gt;edgeAuthentication.enabled&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Declares that the proxy is the Relying Party, with the effects described above.
Requires &lt;code&gt;identityProviderType: EXTERNAL_IDP&lt;/code&gt;; Stroom will refuse to start otherwise.&lt;/p&gt;
&lt;h3 id=&#34;edgeauthenticationlogoutcookiestoexpire&#34;&gt;&lt;code&gt;edgeAuthentication.logout.cookiesToExpire&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Signing out of Stroom does not end the proxy&amp;rsquo;s session by itself; without help, the very next request would silently sign the user straight back in.
This setting lists the proxy&amp;rsquo;s session cookie &lt;strong&gt;name prefixes&lt;/strong&gt;, which Stroom expires when the user logs out.&lt;/p&gt;
&lt;p&gt;They are prefixes because proxies shard large session cookies: an ALB&amp;rsquo;s &lt;code&gt;AWSELBAuthSessionCookie&lt;/code&gt; arrives as &lt;code&gt;AWSELBAuthSessionCookie-0&lt;/code&gt;, &lt;code&gt;-1&lt;/code&gt; and so on, and oauth2-proxy chunks &lt;code&gt;_oauth2_proxy&lt;/code&gt; the same way.&lt;/p&gt;
&lt;h3 id=&#34;edgeauthenticationlogoutsignouturl&#34;&gt;&lt;code&gt;edgeAuthentication.logout.signOutUrl&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Where to send the browser after logging out of Stroom, normally the proxy&amp;rsquo;s or IDP&amp;rsquo;s own sign out endpoint, so the session ends everywhere.
For an ALB it is the IDP&amp;rsquo;s end session endpoint (Cognito&amp;rsquo;s hosted UI &lt;code&gt;/logout&lt;/code&gt;, Entra ID&amp;rsquo;s &lt;code&gt;oauth2/v2.0/logout&lt;/code&gt;); for oauth2-proxy it is the proxy&amp;rsquo;s own &lt;code&gt;/oauth2/sign_out&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If it is not set, Stroom logs a warning at each logout: the proxy session survives, and the user may be signed straight back in.&lt;/p&gt;
&lt;p&gt;The sign out endpoint will normally return the browser to a &lt;strong&gt;post logout landing page&lt;/strong&gt; given in the URL, and Stroom does not provide one — in edge mode every Stroom page is behind the proxy&amp;rsquo;s authenticate rule, and Stroom cannot show a sign in prompt of its own.
The deployment has to supply it, as a page the proxy serves without authentication.
The worked examples use a &lt;code&gt;/signed-out&lt;/code&gt; page served by the proxy itself.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    The page the user lands on after signing out must be on a path the proxy does &lt;strong&gt;not&lt;/strong&gt; authenticate, otherwise the sign in flow simply restarts and the user never sees that they signed out.
Most IDPs also require the landing page&amp;rsquo;s URL to be registered against the client.

&lt;/div&gt;


&lt;h3 id=&#34;csrfprotectbrowseroriginatedrequests&#34;&gt;&lt;code&gt;csrf.protectBrowserOriginatedRequests&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;On by default, and independent of &lt;code&gt;edgeAuthentication&lt;/code&gt;.
It rejects a state changing request whose token arrived on a request the browser marked as cross site, unless the request carries the &lt;code&gt;X-CSRF&lt;/code&gt; header.
Browsers do not let a cross site page attach an &lt;code&gt;Authorization&lt;/code&gt; header, so such a token can only have been injected by a proxy — this is the safety net for a proxy that nobody declared in the configuration.&lt;/p&gt;
&lt;p&gt;Non browser clients are unaffected, as they send none of the browser fetch metadata this check relies on.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    One visible consequence of edge mode: an &lt;strong&gt;in-browser&lt;/strong&gt; tool that attaches its own bearer token (for example Swagger UI&amp;rsquo;s &lt;em&gt;try it out&lt;/em&gt;) must send an &lt;code&gt;X-CSRF: 1&lt;/code&gt; header on state changing requests.
Scripts, &lt;code&gt;curl&lt;/code&gt; and other non browser automation are unaffected.

&lt;/div&gt;


&lt;h2 id=&#34;what-the-proxy-must-and-must-not-authenticate&#34;&gt;What the Proxy Must and Must Not Authenticate&lt;/h2&gt;
&lt;p&gt;Stroom is not only a web application; it ingests data, serves health checks and its nodes talk to each other.
None of that traffic can complete an interactive sign in, so the proxy&amp;rsquo;s authenticate rule must cover the browser facing paths &lt;strong&gt;only&lt;/strong&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Proxy rule&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;, &lt;code&gt;/stroom/*&lt;/code&gt;, &lt;code&gt;/ui/*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Authenticate&lt;/td&gt;
&lt;td&gt;The UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/api/*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Authenticate&lt;/td&gt;
&lt;td&gt;Browser API calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/datafeed&lt;/code&gt; (and its legacy aliases)&lt;/td&gt;
&lt;td&gt;Bypass&lt;/td&gt;
&lt;td&gt;Data receipt from Stroom-Proxies and clients, authenticated by certificate, token or API key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/remoting/remotefeedservice.rpc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bypass&lt;/td&gt;
&lt;td&gt;Feed status RPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bypass&lt;/td&gt;
&lt;td&gt;Health checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/signed-out&lt;/code&gt; (or similar)&lt;/td&gt;
&lt;td&gt;Serve at the proxy&lt;/td&gt;
&lt;td&gt;The post logout landing page, see &lt;a href=&#34;#edgeauthenticationlogoutsignouturl&#34;&gt;&lt;code&gt;signOutUrl&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin port (&lt;code&gt;/stroomAdmin&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Bypass&lt;/td&gt;
&lt;td&gt;Should not be publicly exposed at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Stroom still authenticates the bypassed paths itself — bypassing the proxy does not bypass Stroom&amp;rsquo;s own checks.&lt;/p&gt;
&lt;p&gt;Node to node traffic inside a cluster does not go through the proxy and needs no special handling.&lt;/p&gt;
&lt;h2 id=&#34;trust-prerequisites&#34;&gt;Trust Prerequisites&lt;/h2&gt;
&lt;p&gt;Stroom verifies the signature of whatever credential the proxy injects, so a forged header does not authenticate.
Two things must still be true of the deployment, and Stroom cannot verify them from the inside:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Stroom is unreachable except through the proxy&lt;/strong&gt; — a security group, firewall rule or network policy allowing traffic to Stroom&amp;rsquo;s application port only from the proxy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The proxy overwrites the headers it injects&lt;/strong&gt;, so a client cannot supply its own.
The ALB does this for its &lt;code&gt;x-amzn-oidc-*&lt;/code&gt; headers; with NGINX make sure &lt;code&gt;proxy_set_header&lt;/code&gt; is used for the &lt;code&gt;Authorization&lt;/code&gt; header, which overwrites, and nothing upstream re-adds it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;request-header-sizes&#34;&gt;Request Header Sizes&lt;/h2&gt;
&lt;p&gt;Authenticating proxies make requests big.
An ALB&amp;rsquo;s session cookie is sharded at 4KB per shard, and the injected token headers come on top, so an ordinary authenticated request can exceed the 8KB per request default that Jetty applies when nothing is configured.
The failure looks like a network error, not an authentication error.&lt;/p&gt;
&lt;p&gt;Set a larger limit on every Stroom node:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;server:
  applicationConnectors:
    - type: http
      port: 8080
      useForwardedHeaders: true
      maxRequestHeaderSize: 32KiB
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;user-accounts-and-permissions&#34;&gt;User Accounts and Permissions&lt;/h2&gt;
&lt;p&gt;Exactly as with any &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;external IDP&lt;/a&gt;, the proxy establishes &lt;em&gt;who the user is&lt;/em&gt;; Stroom still decides &lt;em&gt;what they may do&lt;/em&gt;.
A Stroom user record is created automatically the first time a verified identity is seen, with no permissions.
Anyone the IDP will authenticate can therefore reach an empty Stroom UI, so if that is not wanted, restrict who can authenticate at the IDP or proxy (for example, limit the Cognito app client to a group, or require assignment on the Entra ID enterprise application).&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/accounts-users/&#34;&gt;Accounts and Users&lt;/a&gt; for how identities map to Stroom users and permissions.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;what-depends-on-the-idp&#34;&gt;What Depends on the IDP&lt;/h2&gt;
&lt;p&gt;The proxy decides &lt;em&gt;how&lt;/em&gt; the identity reaches Stroom, and that fixes most of the configuration.
The IDP behind the proxy only affects:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Why it varies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openIdConfigurationEndpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The IDP&amp;rsquo;s discovery document, which supplies the issuer that the injected token is checked against&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clientId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The proxy&amp;rsquo;s client at the IDP, which is the audience of a relayed IDP token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uniqueIdentityClaim&lt;/code&gt;, &lt;code&gt;userDisplayNameClaim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which claims are present depends on the IDP &lt;em&gt;and&lt;/em&gt; on the proxy: an ALB signs the IDP&amp;rsquo;s &lt;strong&gt;user info&lt;/strong&gt; claims, while oauth2-proxy relays the &lt;strong&gt;ID token&lt;/strong&gt;, and the two can differ for the same IDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;logout.signOutUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The IDP&amp;rsquo;s end session endpoint and the name of its return address parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client secret rotation&lt;/td&gt;
&lt;td&gt;The secret lives at the proxy, so an expiring secret (Entra ID&amp;rsquo;s, for example) is renewed there&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;External IDP&lt;/a&gt; page for your provider is still the reference for creating the client, but do not copy its Stroom configuration wholesale: &lt;code&gt;clientSecret&lt;/code&gt;, &lt;code&gt;requestScopes&lt;/code&gt; and any claim advice based on the ID token may not apply.
The worked examples spell out the differences.&lt;/p&gt;
&lt;h2 id=&#34;worked-examples&#34;&gt;Worked Examples&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/aws-alb/&#34;&gt;AWS Application Load Balancer&lt;/a&gt; - the load balancer authenticates against Cognito, Entra ID or any OIDC provider and injects a signed &lt;code&gt;x-amzn-oidc-data&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/nginx-oauth2-proxy/&#34;&gt;NGINX, oauth2-proxy and KeyCloak&lt;/a&gt; - the proxy authenticates against KeyCloak (or Entra ID, Cognito, any OIDC provider) and relays the IDP&amp;rsquo;s token as a bearer header.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;troubleshooting&#34;&gt;Troubleshooting&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Authentication Error: Failed to Fetch&lt;/code&gt; at the loading screen&lt;/td&gt;
&lt;td&gt;&lt;code&gt;edgeAuthentication.enabled&lt;/code&gt; not set, so Stroom started a second flow of its own; or the proxy session lapsed (Stroom reloads the page once to let the proxy re-authenticate, then shows this)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser bounces between Stroom and the IDP forever&lt;/td&gt;
&lt;td&gt;Two Relying Parties: Stroom is running its own flow behind the proxy. Set &lt;code&gt;edgeAuthentication.enabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 403 with &lt;code&gt;Authenticated user is not permitted to use stroom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The proxy&amp;rsquo;s credential verified, but the user is unknown or disabled in Stroom, or the token could not be validated - check the issuer and (for an ALB) &lt;code&gt;expectedSignerPrefixes&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests fail with what looks like a network error&lt;/td&gt;
&lt;td&gt;Header size - set &lt;code&gt;maxRequestHeaderSize&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data feeds or health checks broken&lt;/td&gt;
&lt;td&gt;The proxy&amp;rsquo;s authenticate rule covers a machine path - see the path table above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signing out signs the user straight back in&lt;/td&gt;
&lt;td&gt;&lt;code&gt;logout.cookiesToExpire&lt;/code&gt; / &lt;code&gt;logout.signOutUrl&lt;/code&gt; not set, or the post logout page is behind the proxy&amp;rsquo;s authenticate rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signing out ends at an IDP error page&lt;/td&gt;
&lt;td&gt;The post logout landing URL is not registered against the client at the IDP, or the IDP uses a different parameter name for it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log says &lt;code&gt;Expecting claims to contain configured uniqueIdentityClaim &#39;X&#39; but it is not there&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The proxy forwards a different claim set from the one the External IDP page assumes, e.g. an ALB forwards Entra ID&amp;rsquo;s user info claims, which on the v2.0 endpoints have no &lt;code&gt;oid&lt;/code&gt;. The log line lists the claims that did arrive; use one of those, normally &lt;code&gt;sub&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Users have no display name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;userDisplayNameClaim&lt;/code&gt; names a claim the proxy does not forward; check what actually arrives, see &lt;a href=&#34;#what-depends-on-the-idp&#34;&gt;What Depends on the IDP&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Google</title>
      <link>/docs/install-guide/setup/open-id/external-idp/google/</link>
      <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/external-idp/google/</guid>
      <description>
        
        
        &lt;p&gt;This page covers using &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://developers.google.com/identity/openid-connect/openid-connect&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;Google Identity (external link to https://developers.google.com/identity/openid-connect/openid-connect)&#34;&gt;
    &lt;span&gt;Google Identity&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 as Stroom&amp;rsquo;s &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, whether for consumer Google accounts or for a Google Workspace domain.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;Read &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;External IDP&lt;/a&gt; first for what Stroom needs from any provider, and &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/stroom-configuration/&#34;&gt;Stroom Configuration&lt;/a&gt; for what each setting does.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;


    &lt;p&gt;Google is a more limited choice than KeyCloak or Cognito, in three respects.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Its access tokens are opaque, not JWTs.&lt;/strong&gt;
Stroom cannot validate them, so token authentication for the API and for data receipt does not work with Google.
Use Stroom &lt;span class=&#34;glossary-link&#34;&gt;
&lt;a href=&#34;../../docs/glossary/a/glossary-api-key&#34;&gt;
&lt;span&gt;API Keys&lt;/span&gt;
&lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
&lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
&lt;span class=&#34;glossary-tooltip-title&#34;&gt;API Key&lt;/span&gt;
&lt;span class=&#34;glossary-tooltip-summary&#34;&gt;&lt;em&gt;API Keys&lt;/em&gt; are a form of authentication token that are created within Stroom for use by Stroom-Proxy instances or other clients that want to use Stroom’s &lt;em&gt;API&lt;/em&gt;. It is an encrypted string that contains details of the user and the expiration date of the token. Possession of a valid API Key for a user account means that you can do anything that the user can do in the user interface via the API.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details…&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; instead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It has no OIDC sign out endpoint.&lt;/strong&gt;
Signing out of Stroom cannot sign the user out of Google.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It has no OIDC client credentials grant.&lt;/strong&gt;
A Stroom-Proxy cannot obtain a service user token from Google.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Interactive sign in to the Stroom UI works perfectly well.
It is the machine to machine paths that Google does not serve.&lt;/p&gt;


&lt;/div&gt;


&lt;h2 id=&#34;creating-the-oauth-client&#34;&gt;Creating the OAuth Client&lt;/h2&gt;
&lt;p&gt;In the &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://console.cloud.google.com/&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;Google Cloud Console (external link to https://console.cloud.google.com/)&#34;&gt;
    &lt;span&gt;Google Cloud Console&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Select or create a project.&lt;/li&gt;
&lt;li&gt;Configure the &lt;strong&gt;OAuth consent screen&lt;/strong&gt;.
For a Workspace domain choose the &lt;em&gt;Internal&lt;/em&gt; user type, which restricts sign in to your own domain.
For consumer accounts the only option is &lt;em&gt;External&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Go to &lt;em&gt;APIs &amp;amp; Services&lt;/em&gt; =&amp;gt; &lt;em&gt;Credentials&lt;/em&gt; =&amp;gt; &lt;em&gt;Create Credentials&lt;/em&gt; =&amp;gt; &lt;em&gt;OAuth client ID&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Choose an application type of &lt;strong&gt;Web application&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Authorised redirect URIs&lt;/em&gt;, add &lt;code&gt;https://STROOM_FQDN/api/auth/flow/v1/signin-oidc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create the client and note the &lt;strong&gt;Client ID&lt;/strong&gt; and &lt;strong&gt;Client secret&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Where &lt;code&gt;STROOM_FQDN&lt;/code&gt; is the public address of Stroom, i.e. what you have set as &lt;code&gt;appConfig.publicUri&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    &lt;p&gt;Google matches redirect URIs exactly and &lt;strong&gt;does not accept wildcards&lt;/strong&gt; of any kind.
It also requires &lt;code&gt;https&lt;/code&gt;, other than for &lt;code&gt;http://localhost&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This means that older Stroom guidance to register something like &lt;code&gt;https://STROOM_FQDN/*&lt;/code&gt; could never have worked with Google.
Stroom now uses the single fixed callback URI above, which Google accepts.&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;Google supports PKCE, and Stroom always sends an &lt;code&gt;S256&lt;/code&gt; challenge, so there is nothing to configure for it.&lt;/p&gt;
&lt;p&gt;There is no sign out URL to register, because Google has no OIDC sign out endpoint to register one with.&lt;/p&gt;
&lt;h2 id=&#34;configuring-stroom&#34;&gt;Configuring Stroom&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  security:
    authentication:
      authenticationRequired: true
      openId:
        identityProviderType: EXTERNAL_IDP
        openIdConfigurationEndpoint: &amp;quot;https://accounts.google.com/.well-known/openid-configuration&amp;quot;
        clientId: &amp;quot;123456789012-abcdefghijklmnop.apps.googleusercontent.com&amp;quot;
        clientSecret: &amp;quot;THE_CLIENT_SECRET&amp;quot;
        # Google issues no &#39;preferred_username&#39; claim
        userDisplayNameClaim: &amp;quot;email&amp;quot;
        # &#39;profile&#39; is needed for the &#39;name&#39; claim used by fullNameClaimTemplate
        requestScopes:
          - &amp;quot;openid&amp;quot;
          - &amp;quot;email&amp;quot;
          - &amp;quot;profile&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The discovery document supplies the issuer, &lt;code&gt;https://accounts.google.com&lt;/code&gt;, along with the authorization, token and JWKS endpoints, so none of those need setting by hand.&lt;/p&gt;
&lt;p&gt;Note that &lt;code&gt;logoutEndpoint&lt;/code&gt; is deliberately absent; see &lt;a href=&#34;#signing-out&#34;&gt;Signing out&lt;/a&gt; below.&lt;/p&gt;
&lt;h3 id=&#34;audience-validation&#34;&gt;Audience Validation&lt;/h3&gt;
&lt;p&gt;Nothing to do.
Google&amp;rsquo;s &lt;code&gt;id_token&lt;/code&gt; carries &lt;code&gt;aud&lt;/code&gt; set to your client id, so it validates against &lt;code&gt;clientId&lt;/code&gt; with the default settings.&lt;/p&gt;
&lt;h3 id=&#34;claims&#34;&gt;Claims&lt;/h3&gt;
&lt;p&gt;Google issues &lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;email_verified&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;given_name&lt;/code&gt;, &lt;code&gt;family_name&lt;/code&gt; and &lt;code&gt;picture&lt;/code&gt;, and &lt;code&gt;hd&lt;/code&gt; for a Workspace account.
It does &lt;strong&gt;not&lt;/strong&gt; issue &lt;code&gt;preferred_username&lt;/code&gt;, which is Stroom&amp;rsquo;s default for &lt;code&gt;userDisplayNameClaim&lt;/code&gt;, so that has to be changed.
&lt;code&gt;email&lt;/code&gt; is the natural choice.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;given_name&lt;/code&gt; and &lt;code&gt;family_name&lt;/code&gt; require the &lt;code&gt;profile&lt;/code&gt; scope, which is why it is added to &lt;code&gt;requestScopes&lt;/code&gt; above.
Without it the default &lt;code&gt;fullNameClaimTemplate&lt;/code&gt; of &lt;code&gt;${name}&lt;/code&gt; will not resolve.&lt;/p&gt;
&lt;p&gt;Leave &lt;code&gt;uniqueIdentityClaim&lt;/code&gt; as &lt;code&gt;sub&lt;/code&gt;.
Google&amp;rsquo;s &lt;code&gt;sub&lt;/code&gt; is stable for a given account, unlike the email address.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    Do not set &lt;code&gt;uniqueIdentityClaim&lt;/code&gt; to &lt;code&gt;email&lt;/code&gt;.
A Workspace administrator can reassign an address to a different person, who would then inherit the Stroom user and all of its permissions.
Google&amp;rsquo;s own guidance is to key on &lt;code&gt;sub&lt;/code&gt; for exactly this reason.

&lt;/div&gt;


&lt;h3 id=&#34;signing-out&#34;&gt;Signing Out&lt;/h3&gt;
&lt;p&gt;Google offers no OIDC sign out endpoint, so leave &lt;code&gt;logoutEndpoint&lt;/code&gt; unset.&lt;/p&gt;
&lt;p&gt;Logging out of Stroom then ends the Stroom session but leaves the user signed in to Google.
Their next visit to Stroom will sign them straight back in without being asked for credentials, which is worth being aware of on a shared machine.&lt;/p&gt;
&lt;p&gt;Do not point &lt;code&gt;logoutEndpoint&lt;/code&gt; at a general Google sign out URL, as that would sign the user out of every Google service on that browser, which is unlikely to be what they expect from a Stroom logout.&lt;/p&gt;
&lt;h3 id=&#34;access-token-type&#34;&gt;Access Token Type&lt;/h3&gt;
&lt;p&gt;Leave &lt;code&gt;requiredAccessTokenType&lt;/code&gt; unset.
It applies to JWT bearer tokens on the API, and Google&amp;rsquo;s access tokens are not JWTs.&lt;/p&gt;
&lt;h2 id=&#34;restricting-who-can-sign-in&#34;&gt;Restricting Who Can Sign In&lt;/h2&gt;
&lt;p&gt;Authentication and authorisation are separate.
Anyone Google will authenticate can complete a sign in and have a Stroom user created for them, but that user starts with no permissions and no group memberships, so they can see nothing.&lt;/p&gt;
&lt;p&gt;Even so, you should restrict who can reach the sign in at all:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For a Workspace domain, set the OAuth consent screen to &lt;em&gt;Internal&lt;/em&gt;, so only accounts in your domain can authenticate.&lt;/li&gt;
&lt;li&gt;For consumer accounts there is no equivalent, so any Google account can reach the consent screen.
Consider whether Google is the right provider in that case.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stroom has no configuration to restrict sign in by &lt;code&gt;hd&lt;/code&gt; or email domain, so this has to be done at Google.&lt;/p&gt;
&lt;h2 id=&#34;setting-up-the-admin-user-in-stroom&#34;&gt;Setting up the Admin User in Stroom&lt;/h2&gt;
&lt;p&gt;Find the &lt;code&gt;sub&lt;/code&gt; of the account that is to be the administrator.
Unlike KeyCloak and Cognito, Google does not show this in an admin console; the reliable way to obtain it is to decode an &lt;code&gt;id_token&lt;/code&gt; issued for that account, or read it from Stroom&amp;rsquo;s logs after the person has signed in once.&lt;/p&gt;
&lt;p&gt;The simplest route is therefore:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure Stroom as above and start it.&lt;/li&gt;
&lt;li&gt;Have the intended administrator sign in once.
They will land in Stroom with no permissions.&lt;/li&gt;
&lt;li&gt;Read their &lt;code&gt;sub&lt;/code&gt; from the Stroom logs, or from the 




  &lt;div class=&#34;stroom-theme-dark stroom-menu&#34;&gt;
    
    

      

      
      


      

  
  
  &lt;div class=&#34;stroom-menu-item-background&#34; style=&#34;margin-top: 0px;&#34;&gt;
    
    &lt;div class=&#34;stroom-menu-item &#34; &gt;

      &lt;div class=&#34;stroom-menu-item-text&#34;&gt;Tools
      &lt;/div&gt;
        &lt;div class=&#34;stroom-menu-item-arrow&#34;&gt;
          &lt;span class=&#34;stroom-icon inline-svg-button icon-button  &#34; &gt;
    &lt;span class=&#34;face&#34; title=&#34;Arrow right&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;1427.7665&#34; height=&#34;1427.7665&#34; viewBox=&#34;0 0 1427.7665 1427.7665&#34;&gt;&lt;g style=&#34;stroke:currentColor;stroke-width:167.567&#34;&gt;&lt;path d=&#34;m 415.05387,83.70929 c -39.92654,0.4303 -75.41463,48.3887 -40.0468,81.9121 C 567.552,348.69899 760.75555,531.15431 953.73208,713.82391 760.75555,896.49341 567.552,1078.9486 375.00707,1262.0262 c -46.19471,43.7855 28.48812,112.1966 76.0571,67.1681 195.93558,-185.823 392.95261,-370.82479 587.98693,-557.52749 5.6117,-4.8034 10.4633,-10.2473 14.4855,-16.1231 9.643,-12.4844 13.9638,-27.0796 13.6909,-41.7198 0.2729,-14.6402 -4.0479,-29.2354 -13.6909,-41.7198 -4.0222,-5.8758 -8.8738,-11.3198 -14.4855,-16.1231 C 844.01678,469.27821 646.99975,284.27629 451.06417,98.45319 439.91519,87.89979 427.27628,83.57749 415.05387,83.70929 Z&#34;/&gt;&lt;/g&gt;&lt;/svg&gt; &lt;/span&gt;
  &lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

      

      
      
        
      


      

  
  
  &lt;div class=&#34;stroom-menu-item-background&#34; style=&#34;margin-top: 7px;&#34;&gt;
    
    &lt;div class=&#34;stroom-menu-item stroom-menu-item-leaf&#34; &gt;
        &lt;div class=&#34;stroom-menu-item-icon&#34;&gt;
          &lt;span class=&#34;stroom-icon inline-svg-button icon-button  &#34; &gt;
    &lt;span class=&#34;face&#34; title=&#34;User&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;1792&#34; height=&#34;1792&#34; viewBox=&#34;0 0 1792 1792&#34;&gt;&lt;path style=&#34;fill:currentColor&#34; d=&#34;M1536 1399q0 109-62.5 187t-150.5 78h-854q-88 0-150.5-78t-62.5-187q0-85 8.5-160.5t31.5-152 58.5-131 94-89 134.5-34.5q131 128 313 128t313-128q76 0 134.5 34.5t94 89 58.5 131 31.5 152 8.5 160.5zm-256-887q0 159-112.5 271.5t-271.5 112.5-271.5-112.5-112.5-271.5 112.5-271.5 271.5-112.5 271.5 112.5 112.5 271.5z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/span&gt;
        &lt;/div&gt;

      &lt;div class=&#34;stroom-menu-item-text&#34;&gt;Users
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;/div&gt;


 screen if another administrator is available.&lt;/li&gt;
&lt;li&gt;Run the &lt;code&gt;manage_users&lt;/code&gt; command with that value, then restart Stroom so the permission caches are rebuilt.&lt;/li&gt;
&lt;/ol&gt;

  






&lt;div class=&#34;code-toolbar&#34;&gt;
  &lt;pre 
    class=&#34;command-line language-bash&#34; 
    data-user=&#34;user&#34; 
    data-host=&#34;localhost&#34; 
    data-continuation-str=&#34;\&#34;
    data-filter-output=&#34;(out)&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;subject_id=&amp;#34;XXX&amp;#34;; \
java -jar /absolute/path/to/stroom-app-all.jar \
  manage_users \
  --createUser &amp;#34;${subject_id}&amp;#34; \
  --createGroup Administrators \
  --addToGroup &amp;#34;${subject_id}&amp;#34; Administrators \
  --grantPermission Administrators &amp;#34;Administrator&amp;#34; \
  ../local.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The command is repeatable and will skip anything that already exists, so running it against a user that signed in earlier is fine.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;See &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/keycloak/#setting-up-the-admin-user-in-stroom&#34;&gt;KeyCloak&lt;/a&gt; for a fuller description of what this command does, and &lt;a href=&#34;../../docs/user-guide/tools/command-line/&#34;&gt;Command Line Tools&lt;/a&gt; for its options.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;data-receipt-and-the-api&#34;&gt;Data Receipt and the API&lt;/h2&gt;
&lt;p&gt;Because Google&amp;rsquo;s access tokens are opaque rather than JWTs, Stroom cannot validate them, so this will not work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  receive:
    authenticationRequired: true
    tokenAuthenticationEnabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use Stroom &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/a/glossary-api-key&#34;&gt;
      &lt;span&gt;API Keys&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;API Key&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;&lt;em&gt;API Keys&lt;/em&gt; are a form of authentication token that are created within Stroom for use by Stroom-Proxy instances or other clients that want to use Stroom&amp;rsquo;s &lt;em&gt;API&lt;/em&gt;. It is an encrypted string that contains details of the user and the expiration date of the token. Possession of a valid API Key for a user account means that you can do anything that the user can do in the user interface via the API.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; for API clients and for feed status checks, or client certificates for data receipt.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;See &lt;a href=&#34;../../docs/install-guide/setup/open-id/tokens-for-api/&#34;&gt;Tokens for API use&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;stroom-proxy-with-google&#34;&gt;Stroom-Proxy with Google&lt;/h2&gt;
&lt;p&gt;Google has no OIDC client credentials grant, so a Stroom-Proxy cannot obtain a service user token from it, and &lt;code&gt;addOpenIdAccessToken&lt;/code&gt; on a forward destination has nothing to add.&lt;/p&gt;
&lt;p&gt;Configure the proxy with &lt;code&gt;identityProviderType: NO_IDP&lt;/code&gt; and give it an API key created in Stroom:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  feedStatus:
    apiKey: &amp;quot;AN_API_KEY_CREATED_IN_STROOM&amp;quot;
  security:
    authentication:
      openId:
        identityProviderType: NO_IDP
&lt;/code&gt;&lt;/pre&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Microsoft Entra ID (Azure AD)</title>
      <link>/docs/install-guide/setup/open-id/external-idp/azure-ad/</link>
      <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/external-idp/azure-ad/</guid>
      <description>
        
        
        &lt;p&gt;This page covers using &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;Microsoft Entra ID (external link to https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc)&#34;&gt;
    &lt;span&gt;Microsoft Entra ID&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
 as Stroom&amp;rsquo;s &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.
Entra ID is the current name for what was Azure Active Directory, and much of the tooling and documentation still says Azure AD.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;Read &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;External IDP&lt;/a&gt; first for what Stroom needs from any provider, and &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/stroom-configuration/&#34;&gt;Stroom Configuration&lt;/a&gt; for what each setting does.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;



    &lt;p&gt;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 &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/aws-alb/#microsoft-entra-id&#34;&gt;AWS Application Load Balancer&lt;/a&gt; or &lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/nginx-oauth2-proxy/#entra-id-instead-of-keycloak&#34;&gt;NGINX and oauth2-proxy&lt;/a&gt;.&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;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: &lt;strong&gt;use the v2.0 endpoints throughout&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&#34;creating-the-app-registration&#34;&gt;Creating the App Registration&lt;/h2&gt;
&lt;p&gt;In the &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://entra.microsoft.com/&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;Microsoft Entra admin centre (external link to https://entra.microsoft.com/)&#34;&gt;
    &lt;span&gt;Microsoft Entra admin centre&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
, or the Azure portal under &lt;em&gt;Microsoft Entra ID&lt;/em&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;em&gt;App registrations&lt;/em&gt; =&amp;gt; &lt;em&gt;New registration&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Give it a name, e.g. &lt;code&gt;Stroom&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;em&gt;Supported account types&lt;/em&gt; choose &lt;strong&gt;Accounts in this organizational directory only&lt;/strong&gt;, i.e. single tenant, unless you have a specific reason not to.
This restricts sign in to your own tenant.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Redirect URI&lt;/em&gt; select a platform of &lt;strong&gt;Web&lt;/strong&gt; and enter &lt;code&gt;https://STROOM_FQDN/api/auth/flow/v1/signin-oidc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Register&lt;/em&gt;, then note the &lt;strong&gt;Application (client) ID&lt;/strong&gt; and the &lt;strong&gt;Directory (tenant) ID&lt;/strong&gt; from the overview page.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Where &lt;code&gt;STROOM_FQDN&lt;/code&gt; is the public address of Stroom, i.e. what you have set as &lt;code&gt;appConfig.publicUri&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    &lt;p&gt;Entra ID matches redirect URIs exactly and does not accept wildcards.
It also requires &lt;code&gt;https&lt;/code&gt;, other than for &lt;code&gt;http://localhost&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Register the single exact URI above.
Earlier versions of Stroom sent the user&amp;rsquo;s current page as the redirect URI; if you are upgrading, remove whatever was registered for that.&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;Then, still in the app registration:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Under &lt;em&gt;Authentication&lt;/em&gt;, add a &lt;strong&gt;Front-channel logout URL&lt;/strong&gt; of &lt;code&gt;https://STROOM_FQDN/&lt;/code&gt;, and add the same value under &lt;em&gt;Redirect URIs&lt;/em&gt; if your tenant requires post logout redirect URIs to be registered.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Certificates &amp;amp; secrets&lt;/em&gt; =&amp;gt; &lt;em&gt;Client secrets&lt;/em&gt;, create a new secret and copy its &lt;strong&gt;Value&lt;/strong&gt; immediately, as it is only shown once.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    &lt;p&gt;Entra ID client secrets &lt;strong&gt;expire&lt;/strong&gt;, 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.&lt;/p&gt;
&lt;p&gt;Record the expiry date and plan the rotation, or use certificate credentials instead.&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;Entra ID supports PKCE, and Stroom always sends an &lt;code&gt;S256&lt;/code&gt; challenge, so there is nothing to configure for it.&lt;/p&gt;
&lt;h2 id=&#34;exposing-an-api-for-access-tokens&#34;&gt;Exposing an API for Access Tokens&lt;/h2&gt;
&lt;p&gt;This step is what makes API authentication work, and is the Entra ID equivalent of KeyCloak&amp;rsquo;s audience mapper.&lt;/p&gt;
&lt;p&gt;If Stroom only ever asks for the &lt;code&gt;openid&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;profile&lt;/code&gt; 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 &lt;code&gt;id_token&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To get an access token that Stroom can validate, the app registration has to expose an API of its own:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;em&gt;Expose an API&lt;/em&gt; =&amp;gt; &lt;em&gt;Add&lt;/em&gt; next to &lt;em&gt;Application ID URI&lt;/em&gt;.
Accept the default of &lt;code&gt;api://CLIENT_ID&lt;/code&gt;, or set your own.&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Add a scope&lt;/em&gt;, name it something like &lt;code&gt;user_impersonation&lt;/code&gt;, and choose who can consent.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Manifest&lt;/em&gt;, set &lt;code&gt;accessTokenAcceptedVersion&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;



    &lt;p&gt;&lt;code&gt;accessTokenAcceptedVersion&lt;/code&gt; defaults to &lt;code&gt;null&lt;/code&gt;, which means v1.0.
A v1.0 access token has an issuer of &lt;code&gt;https://sts.windows.net/TENANT_ID/&lt;/code&gt;, which does not match the v2.0 issuer that Stroom obtains from the v2.0 discovery document, so such tokens are refused.&lt;/p&gt;
&lt;p&gt;Setting it to &lt;code&gt;2&lt;/code&gt; is the clean fix.
See &lt;a href=&#34;#issuers&#34;&gt;Issuers&lt;/a&gt; if you have a reason to stay on v1.0.&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;Callers then request that scope, e.g. &lt;code&gt;api://CLIENT_ID/user_impersonation&lt;/code&gt;, and the resulting access token carries an &lt;code&gt;aud&lt;/code&gt; claim that Stroom can be configured to accept.&lt;/p&gt;
&lt;h2 id=&#34;configuring-stroom&#34;&gt;Configuring Stroom&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  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 &#39;/v2.0&#39; path part. Without it you get the v1.0 endpoints and a different issuer.
        openIdConfigurationEndpoint: &amp;quot;https://login.microsoftonline.com/TENANT_ID/v2.0/.well-known/openid-configuration&amp;quot;
        # The Application (client) ID from the app registration overview
        clientId: &amp;quot;CLIENT_ID&amp;quot;
        clientSecret: &amp;quot;CLIENT_SECRET&amp;quot;
        logoutEndpoint: &amp;quot;https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/logout&amp;quot;
        # 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:
          - &amp;quot;CLIENT_ID&amp;quot;
          - &amp;quot;api://CLIENT_ID&amp;quot;
        # Accept tokens from both the v2.0 and the v1.0 endpoints. Setting this replaces
        # Stroom&#39;s default issuer check, so the v2.0 issuer must be listed too.
        validIssuers:
          - &amp;quot;https://login.microsoftonline.com/TENANT_ID/v2.0&amp;quot;
          - &amp;quot;https://sts.windows.net/TENANT_ID/&amp;quot;
        # Entra ID only issues the &#39;name&#39; and &#39;preferred_username&#39; claims when &#39;profile&#39;
        # is requested, and Stroom&#39;s default scopes do not include it.
        requestScopes:
          - &amp;quot;openid&amp;quot;
          - &amp;quot;email&amp;quot;
          - &amp;quot;profile&amp;quot;
        # &#39;oid&#39; is stable across app registrations; the default of &#39;sub&#39; is not.
        uniqueIdentityClaim: &amp;quot;oid&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;TENANT_ID&lt;/code&gt; with the Directory (tenant) ID, &lt;code&gt;CLIENT_ID&lt;/code&gt; with the Application (client) ID, and &lt;code&gt;CLIENT_SECRET&lt;/code&gt; with the value of the client secret, all from the app registration&amp;rsquo;s &lt;em&gt;Overview&lt;/em&gt; and &lt;em&gt;Certificates &amp;amp; secrets&lt;/em&gt; pages.&lt;/p&gt;
&lt;p&gt;Each of &lt;code&gt;validIssuers&lt;/code&gt;, &lt;code&gt;requestScopes&lt;/code&gt; and &lt;code&gt;uniqueIdentityClaim&lt;/code&gt; is explained in the sections that follow.&lt;/p&gt;
&lt;h3 id=&#34;issuers&#34;&gt;Issuers&lt;/h3&gt;
&lt;p&gt;The v2.0 discovery endpoint advertises an issuer of &lt;code&gt;https://login.microsoftonline.com/TENANT_ID/v2.0&lt;/code&gt;, which is a parent path of the discovery endpoint itself, so Stroom&amp;rsquo;s issuer check is satisfied with no extra configuration.&lt;/p&gt;
&lt;p&gt;The v1.0 endpoints are not so tidy.
Their issuer is &lt;code&gt;https://sts.windows.net/TENANT_ID/&lt;/code&gt;, which shares no base URI with the discovery endpoint, and Stroom will refuse to start with:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Issuer &amp;lsquo;X&amp;rsquo; obtained from configuration endpoint Y does not share the same base URI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;        validIssuers:
          - &amp;quot;https://login.microsoftonline.com/TENANT_ID/v2.0&amp;quot;
          - &amp;quot;https://sts.windows.net/TENANT_ID/&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stroom then accepts tokens carrying either issuer, so v1.0 and v2.0 tokens can be in use at the same time.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    &lt;p&gt;When &lt;code&gt;validIssuers&lt;/code&gt; 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:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Issuer &amp;lsquo;X&amp;rsquo; obtained from configuration endpoint Y does not match those in the &amp;lsquo;issuer&amp;rsquo; or &amp;lsquo;validIssuers&amp;rsquo; properties.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Always include the v2.0 issuer alongside the v1.0 one.
The values must match exactly, including the &lt;code&gt;/v2.0&lt;/code&gt; suffix on one and the trailing &lt;code&gt;/&lt;/code&gt; on the other.&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;Using the v2.0 endpoints and &lt;code&gt;accessTokenAcceptedVersion: 2&lt;/code&gt; is much the better answer, in which case &lt;code&gt;validIssuers&lt;/code&gt; can be omitted altogether.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    Do not use the &lt;code&gt;common&lt;/code&gt; or &lt;code&gt;organizations&lt;/code&gt; endpoints in place of a tenant id.
Their discovery documents report an issuer containing a literal &lt;code&gt;{tenantid}&lt;/code&gt; placeholder rather than a real value, and they allow sign in from any tenant, which is unlikely to be what you want.

&lt;/div&gt;


&lt;h3 id=&#34;audience-validation&#34;&gt;Audience Validation&lt;/h3&gt;
&lt;p&gt;An Entra ID &lt;strong&gt;id_token&lt;/strong&gt; carries &lt;code&gt;aud&lt;/code&gt; set to the Application (client) ID, so interactive sign in validates against &lt;code&gt;clientId&lt;/code&gt; with no further configuration.&lt;/p&gt;
&lt;p&gt;An &lt;strong&gt;access token&lt;/strong&gt; for your exposed API carries &lt;code&gt;aud&lt;/code&gt; set to either the Application ID URI or the client id, depending on &lt;code&gt;accessTokenAcceptedVersion&lt;/code&gt; and how the scope was requested.
Listing both in &lt;code&gt;allowedAudiences&lt;/code&gt;, as above, covers either.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    Do not simply set &lt;code&gt;audienceClaimRequired: false&lt;/code&gt; 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.

&lt;/div&gt;


&lt;p&gt;Leave &lt;code&gt;validateAudience&lt;/code&gt; at its default of &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;claims&#34;&gt;Claims&lt;/h3&gt;
&lt;p&gt;Entra ID v2.0 issues &lt;code&gt;preferred_username&lt;/code&gt;, normally the user principal name, which Stroom uses as the display name by default, and &lt;code&gt;name&lt;/code&gt;, which satisfies the default &lt;code&gt;fullNameClaimTemplate&lt;/code&gt; of &lt;code&gt;${name}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Both of those claims are only issued when the &lt;strong&gt;&lt;code&gt;profile&lt;/code&gt; scope&lt;/strong&gt; is requested.
Stroom&amp;rsquo;s default &lt;code&gt;requestScopes&lt;/code&gt; is &lt;code&gt;openid&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;, which does not include it, so without the &lt;code&gt;requestScopes&lt;/code&gt; shown in the example above users will sign in with no display name or full name.
Setting &lt;code&gt;requestScopes&lt;/code&gt; replaces the defaults rather than adding to them, so keep &lt;code&gt;openid&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; in the list.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;uniqueIdentityClaim&lt;/code&gt; you have a choice:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;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&amp;rsquo;s &lt;code&gt;sub&lt;/code&gt; changes, orphaning their Stroom user.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The user&amp;rsquo;s object id in the directory. Stable across applications and across app registrations, so it survives a re-registration. Unique within a tenant.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;oid&lt;/code&gt; is the more robust choice for a single tenant deployment, and is what Microsoft&amp;rsquo;s own guidance points to as the durable identifier.
&lt;code&gt;sub&lt;/code&gt; is fine if you are confident the app registration will not be recreated.
The example above uses &lt;code&gt;oid&lt;/code&gt;; remove that line to keep the default of &lt;code&gt;sub&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;alert alert-warning&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Warning&lt;/h4&gt;

    &lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Do not use &lt;code&gt;preferred_username&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt; or &lt;code&gt;upn&lt;/code&gt;; all can be reassigned to a different person, who would then inherit the Stroom user.&lt;/p&gt;


&lt;/div&gt;


&lt;h3 id=&#34;group-and-role-claims&#34;&gt;Group and Role Claims&lt;/h3&gt;
&lt;p&gt;Entra ID can be configured to emit &lt;code&gt;groups&lt;/code&gt; and &lt;code&gt;roles&lt;/code&gt; claims.
Stroom does not consume them.
All authorisation is done with Stroom&amp;rsquo;s own users, groups and permissions, so directory group membership has no effect on what a user can do in Stroom.&lt;/p&gt;
&lt;h3 id=&#34;access-token-type&#34;&gt;Access Token Type&lt;/h3&gt;
&lt;p&gt;Leave &lt;code&gt;requiredAccessTokenType&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;setting-up-the-admin-user-in-stroom&#34;&gt;Setting up the Admin User in Stroom&lt;/h2&gt;
&lt;p&gt;Find the identifier of the account that is to be the administrator, matching whatever you set &lt;code&gt;uniqueIdentityClaim&lt;/code&gt; to.&lt;/p&gt;
&lt;p&gt;If you are using &lt;code&gt;oid&lt;/code&gt;, it is shown as the &lt;strong&gt;Object ID&lt;/strong&gt; on the user&amp;rsquo;s page under &lt;em&gt;Users&lt;/em&gt; in the Entra admin centre.
If you are using &lt;code&gt;sub&lt;/code&gt;, it is pairwise and not shown anywhere in the portal, so you will need to decode an &lt;code&gt;id_token&lt;/code&gt; issued for that user, or have them sign in once and read it from the Stroom logs.&lt;/p&gt;
&lt;p&gt;Then run the following, ideally &lt;strong&gt;before&lt;/strong&gt; Stroom has been started for the first time:&lt;/p&gt;

  






&lt;div class=&#34;code-toolbar&#34;&gt;
  &lt;pre 
    class=&#34;command-line language-bash&#34; 
    data-user=&#34;user&#34; 
    data-host=&#34;localhost&#34; 
    data-continuation-str=&#34;\&#34;
    data-filter-output=&#34;(out)&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;subject_id=&amp;#34;XXX&amp;#34;; \
java -jar /absolute/path/to/stroom-app-all.jar \
  manage_users \
  --createUser &amp;#34;${subject_id}&amp;#34; \
  --createGroup Administrators \
  --addToGroup &amp;#34;${subject_id}&amp;#34; Administrators \
  --grantPermission Administrators &amp;#34;Administrator&amp;#34; \
  ../local.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;See &lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/keycloak/#setting-up-the-admin-user-in-stroom&#34;&gt;KeyCloak&lt;/a&gt; for a fuller description of what this command does, and &lt;a href=&#34;../../docs/user-guide/tools/command-line/&#34;&gt;Command Line Tools&lt;/a&gt; for its options.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;stroom-proxy-with-entra-id&#34;&gt;Stroom-Proxy with Entra ID&lt;/h2&gt;
&lt;p&gt;A Stroom-Proxy obtains a token for its own service user using the client credentials grant.&lt;/p&gt;
&lt;p&gt;Create a second app registration for the proxy, then grant it access to the API exposed by the Stroom app registration:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In the proxy&amp;rsquo;s app registration, go to &lt;em&gt;API permissions&lt;/em&gt; =&amp;gt; &lt;em&gt;Add a permission&lt;/em&gt; =&amp;gt; &lt;em&gt;My APIs&lt;/em&gt; and select the Stroom app registration.&lt;/li&gt;
&lt;li&gt;Choose &lt;em&gt;Application permissions&lt;/em&gt;, which is the client credentials case, rather than delegated permissions.&lt;/li&gt;
&lt;li&gt;Have a directory administrator &lt;strong&gt;grant admin consent&lt;/strong&gt;, without which the grant will fail.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Entra ID&amp;rsquo;s client credentials flow uses the &lt;code&gt;.default&lt;/code&gt; scope of the target API:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  security:
    authentication:
      openId:
        identityProviderType: EXTERNAL_IDP
        openIdConfigurationEndpoint: &amp;quot;https://login.microsoftonline.com/TENANT_ID/v2.0/.well-known/openid-configuration&amp;quot;
        # The proxy&#39;s own app registration
        clientId: &amp;quot;PROXY_CLIENT_ID&amp;quot;
        clientSecret: &amp;quot;PROXY_CLIENT_SECRET&amp;quot;
        # The Application ID URI of the Stroom app registration, not the proxy&#39;s
        clientCredentialsScopes:
          - &amp;quot;api://CLIENT_ID/.default&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;Note&lt;/h4&gt;


    Stroom&amp;rsquo;s default for &lt;code&gt;clientCredentialsScopes&lt;/code&gt; is &lt;code&gt;openid&lt;/code&gt;, and its configuration description suggests setting &lt;code&gt;openid&lt;/code&gt; alongside the &lt;code&gt;.default&lt;/code&gt; scope.
Entra ID&amp;rsquo;s v2.0 client credentials flow generally accepts a &lt;code&gt;.default&lt;/code&gt; scope on its own and rejects it being combined with others, so start with just the &lt;code&gt;.default&lt;/code&gt; scope as above and add &lt;code&gt;openid&lt;/code&gt; only if your tenant requires it.

&lt;/div&gt;


&lt;p&gt;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&amp;rsquo;s &lt;code&gt;allowedAudiences&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;obtaining-a-token-to-send-data&#34;&gt;Obtaining a Token to Send Data&lt;/h2&gt;
&lt;p&gt;When a Stroom-Proxy, or Stroom itself, is configured with Entra ID as above and has &lt;code&gt;receive.authenticationRequired&lt;/code&gt; and &lt;code&gt;receive.tokenAuthenticationEnabled&lt;/code&gt; both set to &lt;code&gt;true&lt;/code&gt;, anything sending data to &lt;code&gt;/datafeed&lt;/code&gt; must present an Entra ID access token, or a certificate, with each request.
See &lt;a href=&#34;../../docs/sending-data/token-authentication/&#34;&gt;Token Authentication&lt;/a&gt; for how the token is attached.&lt;/p&gt;
&lt;p&gt;This section covers how a sending system gets that token.
It is the mirror image of &lt;a href=&#34;#stroom-proxy-with-entra-id&#34;&gt;Stroom-Proxy with Entra ID&lt;/a&gt;: 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.&lt;/p&gt;
&lt;h3 id=&#34;registering-the-sender&#34;&gt;Registering the Sender&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an app registration for the sender, e.g. &lt;code&gt;Stroom Sender - Widget Service&lt;/code&gt;.
No redirect URI is needed as it will never sign in interactively.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Certificates &amp;amp; secrets&lt;/em&gt;, create a client secret and record its value.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;API permissions&lt;/em&gt; =&amp;gt; &lt;em&gt;Add a permission&lt;/em&gt; =&amp;gt; &lt;em&gt;My APIs&lt;/em&gt;, select the Stroom app registration.&lt;/li&gt;
&lt;li&gt;Choose &lt;em&gt;Application permissions&lt;/em&gt;, not delegated permissions.&lt;/li&gt;
&lt;li&gt;Have a directory administrator &lt;strong&gt;grant admin consent&lt;/strong&gt;, without which the token request will fail.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Stroom app registration must already &lt;a href=&#34;#exposing-an-api-for-access-tokens&#34;&gt;expose an API&lt;/a&gt; with &lt;code&gt;accessTokenAcceptedVersion&lt;/code&gt; set to &lt;code&gt;2&lt;/code&gt;.
If it does not, the token comes back as a v1.0 token with an issuer of &lt;code&gt;https://sts.windows.net/TENANT_ID/&lt;/code&gt; and is refused unless that issuer is listed in &lt;code&gt;validIssuers&lt;/code&gt;, see &lt;a href=&#34;#issuers&#34;&gt;Issuers&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;requesting-the-token&#34;&gt;Requesting the Token&lt;/h3&gt;
&lt;p&gt;The sender asks the v2.0 token endpoint for a token using the client credentials grant.
The &lt;code&gt;scope&lt;/code&gt; is the &lt;code&gt;.default&lt;/code&gt; scope of the Stroom app registration&amp;rsquo;s Application ID URI, &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;openid&lt;/code&gt; or &lt;code&gt;email&lt;/code&gt;; asking for those returns a token for Microsoft Graph that Stroom cannot validate.&lt;/p&gt;

  






&lt;div class=&#34;code-toolbar&#34;&gt;
  &lt;pre 
    class=&#34;command-line language-bash&#34; 
    data-user=&#34;user&#34; 
    data-host=&#34;localhost&#34; 
    data-continuation-str=&#34;\&#34;
    data-filter-output=&#34;(out)&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;TENANT_ID=&amp;#34;&amp;lt;TENANT_ID&amp;gt;&amp;#34;; \
SENDER_CLIENT_ID=&amp;#34;&amp;lt;SENDER_CLIENT_ID&amp;gt;&amp;#34;; \
SENDER_CLIENT_SECRET=&amp;#34;&amp;lt;SENDER_CLIENT_SECRET&amp;gt;&amp;#34;; \
STROOM_APP_ID_URI=&amp;#34;api://&amp;lt;CLIENT_ID&amp;gt;&amp;#34;; \
TOKEN=&amp;#34;$( \
  curl \
    --silent \
    --request POST \
    --header &amp;#34;Content-Type: application/x-www-form-urlencoded&amp;#34; \
    --data &amp;#34;grant_type=client_credentials&amp;#34; \
    --data &amp;#34;client_id=${SENDER_CLIENT_ID}&amp;#34; \
    --data &amp;#34;client_secret=${SENDER_CLIENT_SECRET}&amp;#34; \
    --data &amp;#34;scope=${STROOM_APP_ID_URI}/.default&amp;#34; \
    &amp;#34;https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token&amp;#34; \
  | jq -r &amp;#39;.access_token&amp;#39; \
)&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TENANT_ID&lt;/code&gt; is the Directory (tenant) ID.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SENDER_CLIENT_ID&lt;/code&gt; and &lt;code&gt;SENDER_CLIENT_SECRET&lt;/code&gt; are from the sender&amp;rsquo;s app registration.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;STROOM_APP_ID_URI&lt;/code&gt; is the Application ID URI of the Stroom app registration, as shown under &lt;em&gt;Expose an API&lt;/em&gt;.
The default form is &lt;code&gt;api://&amp;lt;CLIENT_ID&amp;gt;&lt;/code&gt;, where &lt;code&gt;CLIENT_ID&lt;/code&gt; is the Stroom app registration&amp;rsquo;s client id, not the sender&amp;rsquo;s, and it is the same value used in the proxy&amp;rsquo;s &lt;code&gt;clientCredentialsScopes&lt;/code&gt; above.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The response is a JSON document containing &lt;code&gt;access_token&lt;/code&gt;, &lt;code&gt;token_type&lt;/code&gt; and &lt;code&gt;expires_in&lt;/code&gt;; the command above extracts just the token.
A &lt;code&gt;400&lt;/code&gt; response instead carries &lt;code&gt;error&lt;/code&gt; and &lt;code&gt;error_description&lt;/code&gt; fields, the latter with an &lt;code&gt;AADSTS&lt;/code&gt; code and a message that says what is wrong.
The usual causes are a wrong or expired client secret, a &lt;code&gt;scope&lt;/code&gt; that is not the &lt;code&gt;.default&lt;/code&gt; scope of an API the Stroom app registration actually exposes, or admin consent not having been granted.&lt;/p&gt;
&lt;h3 id=&#34;sending-data-with-the-token&#34;&gt;Sending Data with the Token&lt;/h3&gt;
&lt;p&gt;The token goes in the &lt;code&gt;Authorization&lt;/code&gt; header as a bearer token, alongside the usual &lt;a href=&#34;../../docs/sending-data/header-arguments/&#34;&gt;header arguments&lt;/a&gt;:&lt;/p&gt;

  






&lt;div class=&#34;code-toolbar&#34;&gt;
  &lt;pre 
    class=&#34;command-line language-bash&#34; 
    data-user=&#34;user&#34; 
    data-host=&#34;localhost&#34; 
    data-continuation-str=&#34;\&#34;
    data-filter-output=&#34;(out)&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;curl \
  --silent \
  --request POST \
  --header &amp;#34;Authorization: Bearer ${TOKEN}&amp;#34; \
  --header &amp;#34;Feed: &amp;lt;FEED_NAME&amp;gt;&amp;#34; \
  --header &amp;#34;System: &amp;lt;SYSTEM_NAME&amp;gt;&amp;#34; \
  --header &amp;#34;Environment: &amp;lt;ENVIRONMENT&amp;gt;&amp;#34; \
  --data-binary @events.log \
  &amp;#34;https://stroom-proxy.example.com/stroom/datafeed&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Tokens are short lived, typically around an hour, so a sender must request a fresh token when &lt;code&gt;expires_in&lt;/code&gt; has elapsed or a request is refused with a &lt;code&gt;401&lt;/code&gt;, rather than caching one indefinitely.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;See &lt;a href=&#34;../../docs/sending-data/example-clients/curl-linux/&#34;&gt;curl (Linux)&lt;/a&gt; for more on sending data with curl.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&#34;checking-the-token&#34;&gt;Checking the Token&lt;/h3&gt;
&lt;p&gt;If the proxy rejects the token, decode its payload before assuming the proxy is at fault:&lt;/p&gt;

  






&lt;div class=&#34;code-toolbar&#34;&gt;
  &lt;pre 
    class=&#34;command-line language-bash&#34; 
    data-user=&#34;user&#34; 
    data-host=&#34;localhost&#34; 
    data-continuation-str=&#34;\&#34;
    data-filter-output=&#34;(out)&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;echo &amp;#34;${TOKEN}&amp;#34; \
  | cut -d &amp;#39;.&amp;#39; -f 2 \
  | tr &amp;#39;_-&amp;#39; &amp;#39;/&amp;#43;&amp;#39; \
  | base64 --decode 2&amp;gt;/dev/null \
  | jq &amp;#39;.&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Check that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;iss&lt;/code&gt; is &lt;code&gt;https://login.microsoftonline.com/TENANT_ID/v2.0&lt;/code&gt;.
If it is &lt;code&gt;https://sts.windows.net/TENANT_ID/&lt;/code&gt; the Stroom app registration is still issuing v1.0 tokens, see &lt;a href=&#34;#exposing-an-api-for-access-tokens&#34;&gt;Exposing an API for Access Tokens&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aud&lt;/code&gt; is a value that appears in the proxy&amp;rsquo;s &lt;code&gt;allowedAudiences&lt;/code&gt;.
It will be the Application ID URI or the client id of the Stroom app registration.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exp&lt;/code&gt; is in the future.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once accepted, the sender&amp;rsquo;s identity is recorded against the received data in the &lt;code&gt;UploadUserId&lt;/code&gt; meta attribute, taken from the claim named in &lt;code&gt;uniqueIdentityClaim&lt;/code&gt;.
For a client credentials token that is the &lt;code&gt;oid&lt;/code&gt; of the sender&amp;rsquo;s service principal, so it is that value, not the app registration&amp;rsquo;s display name, that you match on in &lt;a href=&#34;../../docs/user-guide/data-receipt/data-receipt-rules/&#34;&gt;Data Receipt Rules&lt;/a&gt;.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Setting up Stroom with an Open ID Connect IDP</title>
      <link>/docs/install-guide/setup/open-id/</link>
      <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
      
      <guid>/docs/install-guide/setup/open-id/</guid>
      <description>
        
        
        &lt;p&gt;Stroom authenticates its users against an &lt;span class=&#34;glossary-link&#34;&gt;
    &lt;a href=&#34;../../docs/glossary/i/glossary-idp&#34;&gt;
      &lt;span&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;i class=&#34;glossary-link-icon fas fa-book fa-sm text-primary&#34;&gt;&lt;/i&gt;
    &lt;/a&gt;&lt;span class=&#34;glossary-tooltip&#34;&gt;
      &lt;span class=&#34;glossary-tooltip-title&#34;&gt;Identity Provider (IDP)&lt;/span&gt;
      &lt;span class=&#34;glossary-tooltip-summary&#34;&gt;An &lt;strong&gt;Id&lt;/strong&gt;entity &lt;strong&gt;P&lt;/strong&gt;rovider is a system or service that can authenticate a user and assert their identity. &lt;em&gt;Identity providers&lt;/em&gt; can support single sign on (SSO), which allows the user to sign in once to the &lt;em&gt;Identity Provider&lt;/em&gt; so they are then authenticated to all systems using that IDP.&lt;/span&gt;&lt;span class=&#34;glossary-tooltip-truncated&#34;&gt;Click to see more details...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; using &lt;span class=&#34;external-link&#34;&gt;
  &lt;a href=&#34;https://openid.net/connect/&#34; target=&#34;_blank&#34; class=&#34;external-link&#34; title=&#34;Open ID Connect (external link to https://openid.net/connect/)&#34;&gt;
    &lt;span&gt;Open ID Connect&lt;/span&gt;
    &lt;i class=&#34;external-link-icon fas fa-external-link-alt fa-sm text-secondary&#34;&gt;&lt;/i&gt;
  &lt;/a&gt;
&lt;/span&gt;
.
There are three deployment models, distinguished by &lt;em&gt;where the user accounts live&lt;/em&gt; and &lt;em&gt;which component performs the sign in&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/internal-idp/&#34;&gt;Internal IDP&lt;/a&gt; - Stroom acts as its own identity provider and manages the user accounts itself.
This is the default.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;External IDP&lt;/a&gt; - A 3rd party identity provider, such as KeyCloak, Cognito or Google, holds the accounts; &lt;strong&gt;Stroom performs the sign in&lt;/strong&gt; by redirecting the browser to it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/&#34;&gt;Edge Proxy RP&lt;/a&gt; - A 3rd party identity provider holds the accounts, but &lt;strong&gt;an authenticating reverse proxy in front of Stroom performs the sign in&lt;/strong&gt; (an AWS Application Load Balancer, NGINX with oauth2-proxy, etc., against Cognito, Entra ID, KeyCloak or any other provider) and passes Stroom a verified identity with each request.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not sure which you have?&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your situation&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No existing identity provider, or Stroom should manage its own accounts&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/internal-idp/&#34;&gt;Internal IDP&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An existing IDP (KeyCloak, Cognito, Google, Entra ID) and browsers reach Stroom directly, or through a proxy that only routes&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/external-idp/&#34;&gt;External IDP&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A load balancer or proxy in front of Stroom signs users in before traffic reaches it, e.g. an ALB &lt;code&gt;authenticate-cognito&lt;/code&gt; or &lt;code&gt;authenticate-oidc&lt;/code&gt; rule, oauth2-proxy, or a policy that unauthenticated traffic must not reach the application&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;../../docs/install-guide/setup/open-id/edge-proxy/&#34;&gt;Edge Proxy RP&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Whichever you use, authorisation is always handled by Stroom.
The provider establishes who a user is; Stroom decides what they are allowed to do.&lt;/p&gt;

&lt;div class=&#34;alert alert-primary&#34; role=&#34;alert&#34;&gt;
&lt;h4 class=&#34;alert-heading&#34;&gt;See Also&lt;/h4&gt;
&lt;div class=&#34;alert-content&#34;&gt;
  
  &lt;p&gt;See &lt;a href=&#34;../../docs/install-guide/setup/open-id/accounts-users/&#34;&gt;Accounts and Users&lt;/a&gt; for how identities at the provider relate to Stroom users, and &lt;a href=&#34;../../docs/install-guide/setup/open-id/tokens-for-api/&#34;&gt;Tokens for API use&lt;/a&gt; for authenticating machine to machine.&lt;/p&gt;

&lt;/div&gt;
&lt;/div&gt;


      </description>
    </item>
    
  </channel>
</rss>
