Fix Keycloak SAML Audience Errors

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package podman-5.8.2-5.el10_2.x86_64
quay.io/keycloak/keycloak:26.8.0
libxml2-2.12.5-10.el10_2.2.x86_64
Applies to Any Linux host with Podman or Docker
Privilege Normal user (rootless Podman). Realm setup uses the Keycloak bootstrap admin
Scope Why a service provider rejects a Keycloak SAML assertion when AudienceRestriction does not contain the expected entity ID, how that value differs from the ACS URL and Destination, and when a SAML audience mapper should add a second audience. Does not cover OpenID Connect access-token audience or ACS and destination mismatches.
Related guides Keycloak SAML metadata
Keycloak SP-Initiated vs IdP-Initiated SAML SSO
Keycloak SAML NameID
Keycloak SAML attribute mapper
Map Keycloak roles and groups into SAML assertions
Short on time?Beta Get the gist of this 13-minute read in seconds, then ask follow-up questions

A service provider rejects a Keycloak assertion when AudienceRestriction does not contain the service provider entity ID it expects. Keep these three values separate:

  • SAML client ID: normally the service provider entity ID on the Keycloak identity provider
  • ACS URL: where the browser delivers the response
  • OIDC aud: an access-token claim outside this SAML article

The test uses the two-realm login from Keycloak SAML SSO:

  • Realm company on 127.0.0.1:18109 signs the assertion
  • Realm apps on 127.0.0.1:18110 is the broker that checks it
  • The client ID must match the service provider entity ID before an audience mapper can help

A mapper adds an audience. It does not repair a client ID that the identity provider cannot find.


Quick Answer

In a normal Keycloak SAML setup:

  • The assertion's Audience should contain the service provider entity ID. On the Keycloak identity provider, that normally means the SAML client ID must match the service provider entity ID.

An Entity ID is the SAML identifier for the service provider. It often looks like a URL, but it is primarily an identifier, not necessarily a page you open in a browser.

  • Do not replace that value with the ACS URL just because both look like URLs. The ACS is where the response is delivered. The audience identifies which service provider the assertion is meant for.
  • Use an Audience mapper only when the assertion needs an additional audience after the main service provider entity ID is already correct.

What the SAML Audience Means

AudienceRestriction limits which service provider is allowed to accept the assertion. Inside it, each Audience value is an identifier for an intended receiver.

text
Assertion
└── Conditions
    └── AudienceRestriction
        └── Audience = SP Entity ID

In this Keycloak SAML client configuration, the identity provider uses the client's Client ID as the primary SAML audience.

On a matching login the audience was the apps realm URL:

xml
<saml:Conditions NotBefore="2026-10-03T20:12:17.566Z" NotOnOrAfter="2026-10-03T20:13:17.566Z">
  <saml:AudienceRestriction>
    <saml:Audience>http://127.0.0.1:18110/realms/apps</saml:Audience>
  </saml:AudienceRestriction>
</saml:Conditions>

That string is the client ID and the service provider entity ID. The assertion consumer service URL is a different string, shown in the next section. The assertion can contain additional audiences alongside the main service provider entity ID. For portable SAML interoperability, keep the service provider entity ID in the audience list. In this Keycloak 26.8.0 broker test, the validator also accepted the response Destination, but other service providers may not.


Audience vs Entity ID vs ACS vs Destination

These four fields are easy to paste into the wrong box. In the login above they had two values, not four.

Field Where you see or configure it in this lab Value in the matching login
Audience Audience inside the assertion http://127.0.0.1:18110/realms/apps
Service provider entity ID apps realm, Identity Providers, company-idp, Service Provider Entity ID. Empty means the apps realm URL, which is also the AuthnRequest Issuer http://127.0.0.1:18110/realms/apps
ACS URL Broker endpoint, and the SAML client's redirect and ACS settings http://127.0.0.1:18110/realms/apps/broker/company-idp/endpoint
Destination Destination attribute on the SAML Response http://127.0.0.1:18110/realms/apps/broker/company-idp/endpoint

The audience identifies the service provider, also called the relying party, that the assertion is intended for. The ACS URL is where the browser posts the response.

The Keycloak 26.8.0 broker test showed three important details:

  • The validator's allowed-audience list contained the configured service provider entity ID and the response Destination
  • That result is Keycloak broker behavior observed in this test, not a reason to publish the ACS URL as the audience for other service providers
  • Copying the ACS URL into the client ID is not the fix for an audience error

An ACS URL mismatch is a different failure from this page.


Inspect AudienceRestriction in the Assertion

Save the POST binding response as response.xml and the AuthnRequest as request.xml. Read the audience from the assertion first:

bash
xmllint --xpath 'string(//*[local-name()="Audience"])' response.xml
output
http://127.0.0.1:18110/realms/apps

That is the client ID. The response destination is the broker endpoint, not that client ID:

bash
xmllint --xpath 'string(//*[local-name()="Response"]/@Destination)' response.xml
output
http://127.0.0.1:18110/realms/apps/broker/company-idp/endpoint

The AuthnRequest issuer is what the identity provider looks up as a client. It matched the audience:

bash
xmllint --xpath 'string(//*[local-name()="Issuer"])' request.xml
output
http://127.0.0.1:18110/realms/apps

The ACS URL on that request is the destination you already saw, not the audience:

bash
xmllint --xpath 'string(//*[local-name()="AuthnRequest"]/@AssertionConsumerServiceURL)' request.xml
output
http://127.0.0.1:18110/realms/apps/broker/company-idp/endpoint

If the assertion does not contain the service provider entity ID that matches the AuthnRequest Issuer and the client registration, fix that client and entity-ID relationship before adding another audience mapper. Extra audiences can sit beside that entity ID. If the entity ID is already present and the service provider still rejects the assertion, the receiver expects some other string.


Make the SAML Client ID Match the SP Entity ID

Service Provider Entity ID is the issuer of the AuthnRequest. The company realm accepts the request only when a SAML client exists with that exact client ID.

         apps broker
              ↓
  Service Provider Entity ID
              ↓
appears as AuthnRequest Issuer
              ↓
         company IdP
              ↓
        SAML Client ID

For the identity provider to recognize the request, the service provider entity ID must equal the identity-provider-side SAML client ID. These three places should describe the same SAML service provider. If one uses a different identifier, Keycloak may fail before it creates an assertion. The normal assertion then contains Audience equal to that same service provider entity ID.

I set Service Provider Entity ID to https://sp.example.com/apps and left the client ID as http://127.0.0.1:18110/realms/apps.

On the apps realm, that value is Service provider entity ID on the company-idp provider. Identity provider entity ID stays the company realm URL:

Apps realm identity provider with Service provider entity ID set to https://sp.example.com/apps

The company realm still has one SAML client, and its client ID is the apps realm URL:

Company realm SAML client whose client ID is still the apps realm URL

Compare the two values on those screens:

  • AuthnRequest Issuer: https://sp.example.com/apps
  • IdP-side client ID: http://127.0.0.1:18110/realms/apps

Those strings do not match, so the company page stopped before a login form:

Company realm error card reading Invalid Request

This is not yet an audience-validation failure. The identity provider first has to map the AuthnRequest Issuer to a SAML client. Because no client had ID https://sp.example.com/apps, Keycloak stopped with client_not_found before creating a SAML assertion at all. The company realm log shows that it could not find a SAML client matching the incoming Issuer. Filter that moment:

bash
podman logs keycloak-saml-aud-idp 2>&1 | grep '2026-10-03 20:13:53'
output
2026-10-03 20:13:53,367 WARN  [org.keycloak.events] (executor-thread-13) type="LOGIN_ERROR", realmId="8cc42ce3-5e12-48fa-9192-c70f97465eab", realmName="company", clientId="null", userId="null", ipAddress="10.88.0.1", error="client_not_found", reason="Cannot_match_source_hash"

client_not_found means the issuer https://sp.example.com/apps was not a client ID. I set the client ID to that same URI and left the ACS URL on the client. The next response carried the new audience:

bash
xmllint --xpath 'string(//*[local-name()="Audience"])' response-aligned.xml
output
https://sp.example.com/apps

The corrected login proves that the fields have separate jobs:

  • The broker accepted the assertion and continued to its first-login profile step
  • The ACS URL remained the broker endpoint
  • Changing the client ID changed the audience, not where the browser posts the response

Add a SAML Audience Mapper When Required

Add an Audience mapper only when the integration explicitly requires an additional audience beyond the main service provider entity ID. Keycloak presents two similarly named mapper types:

  • Audience, saml-audience-mapper: explicitly adds the client or custom audience configured below
  • Audience Resolve: can add audiences automatically from client-role relationships. That is a different workflow and is not used here.

The two audience fields on that mapper do different jobs:

  • Included Client Audience lets you select an existing Keycloak client. Keycloak adds that client's Client ID string as the extra audience. It does not add the client's internal database UUID.
  • Use Included Custom Audience when the receiving system expects a literal audience value that is not represented by an existing Keycloak client.
  • If both fields are filled, Included Client Audience wins. Keycloak adds that selected client's Client ID and ignores the custom audience value for this mapper.

I left the client ID at https://sp.example.com/apps and set only Included Custom Audience to https://billing.example.com. Leave Included Client Audience empty. Otherwise it takes precedence over the custom value:

Audience mapper with Included Custom Audience set and Included Client Audience empty

The following assertion contains both:

bash
xmllint --xpath '//*[local-name()="Audience"]/text()' response-audiences.xml
output
https://sp.example.com/apps
https://billing.example.com

The result confirms three points:

  • The assertion passed audience validation, and the broker continued the login
  • One audience remained the service provider entity ID
  • https://billing.example.com was additional; it did not replace the main service provider entity ID

Custom Audience vs Client Audience

I then set Included Client Audience to a second SAML client, https://reports.example.com, and left the custom billing URI in place:

Audience mapper with Included Client Audience set to https://reports.example.com and the custom billing URI still filled in

Keycloak kept the service provider audience and added the client, not the custom URI:

bash
xmllint --xpath '//*[local-name()="Audience"]/text()' response-client.xml
output
https://sp.example.com/apps
https://reports.example.com

Choose the mapper field from the value the receiver expects:

  • Use Included Client Audience when the extra audience is the Client ID of a client that already exists in the realm
  • Use Included Custom Audience for a literal value that is not one of those clients
  • Clear Included Client Audience when you want Keycloak to write the custom value
  • Neither setting removes the primary audience that comes from the service provider's SAML client ID

Verify the Correct Audience

After the client ID matches the service provider entity ID, sign in again and check the new response:

  • Audience contains the documented service provider entity ID
  • Extra mapper values appear beside that primary audience, not instead of it
  • The first-login profile step on the apps realm means this broker accepted the assertion

Other service providers may not accept the ACS or Destination value that this Keycloak broker accepted. A successful Keycloak broker login does not prove that an outside product will accept the ACS URL as the audience.


Common Audience Misconfigurations

The next test isolates audience validation in the local lab:

  • It modifies the Audience in a captured response
  • It disables signature validation so the edited response reaches the audience check
  • Local lab only: this test disables signature validation so the audience check can be isolated. Do not use that configuration in a real deployment.

I posted a copy of a valid response after changing only Audience to https://other.example.com/sp. Signature validation was off on the broker, so this was not the invalid signature failure. The browser error is misleading here. The assertion had not expired. The server-side validator rejected its audience. When the page says Login timeout, check the Keycloak logs before assuming the clock or the assertion lifetime is the cause.

The apps realm showed this page:

Apps realm error card reading Login timeout. Please sign in again.

The assertion was still inside its allowed time window. NotOnOrAfter was later than the log line:

bash
xmllint --xpath 'string(//*[local-name()="Conditions"]/@NotOnOrAfter)' response-window.xml
output
2026-10-03T20:13:53.756Z

The apps container, started with ConditionsValidator at DEBUG, recorded the real reason. The log contains both the specific audience failure and the broader Assertion expired error emitted by the conditions-validation path:

bash
podman logs keycloak-saml-aud-sp 2>&1 \
  | grep -E 'not addressed to this SP|Allowed audiences are|Assertion expired'
output
2026-10-03 20:12:56,242 INFO  [org.keycloak.saml.validators.ConditionsValidator] (executor-thread-13) Assertion ID_c0e2e4ab-21c8-4231-a571-242959fd8809 is not addressed to this SP.
2026-10-03 20:12:56,242 DEBUG [org.keycloak.saml.validators.ConditionsValidator] (executor-thread-13) Allowed audiences are: [http://127.0.0.1:18110/realms/apps, http://127.0.0.1:18110/realms/apps/broker/company-idp/endpoint]
2026-10-03 20:12:56,242 ERROR [org.keycloak.broker.saml.SAMLEndpoint] (executor-thread-13) Assertion expired.

The log entry is at 20:12:56, while NotOnOrAfter is 20:13:53. The assertion was therefore still valid by time, so Assertion expired is not the useful diagnosis here. The earlier "not addressed to this SP" line identifies the audience failure. The allowed list in this 26.8.0 broker test is the service provider entity ID and the ACS URL. https://other.example.com/sp is in neither. Putting the ACS URL into Audience was accepted on this broker, because that URL was the second allowed value in this test. That is not a reason to publish the ACS URL as the audience for other service providers.

The failure happens at one of these stages:

       AuthnRequest
            ↓
    IdP client lookup
            ↓
    assertion created
            ↓
broker audience validation
Symptom Stage Meaning What to change
Invalid Request and client_not_found IdP processing AuthnRequest The AuthnRequest Issuer does not match an IdP-side SAML client ID Set the SAML client ID to that entity ID
Login timeout, log says not addressed to this SP Broker validating assertion Audience does not match what this broker accepts Put the entity ID in the client ID. Add a mapper only for an extra audience
Login works in Keycloak, another product still rejects the audience Broker validating assertion That product does not accept the ACS or Destination value this broker allowed Set the audience to the service provider entity ID
Custom audience never appears Mapper configuration Included Client Audience is also set, so it wins Clear the client audience, or keep the client value Keycloak writes

Summary

In this Keycloak SAML client configuration, the identity provider uses the client's Client ID as the primary Audience. In the 26.8.0 broker test, the allowed list also contained the response Destination. Use the service provider entity ID as the audience you publish. The ACS URL and the entity ID were different on this login, and the audience matched the entity ID.

Setting Service Provider Entity ID to https://sp.example.com/apps without a client of that ID stopped the company realm with Invalid Request and client_not_found, before any assertion existed. Setting the client ID to the same URI made the next audience https://sp.example.com/apps, and the broker continued to the first-login profile step. An audience of https://other.example.com/sp produced Login timeout even though NotOnOrAfter had not passed. The relevant log line was not addressed to this SP, which identifies the audience mismatch.

An audience mapper is for an additional audience, not a replacement for the entity ID. With only Included Custom Audience set, the assertion contained the entity ID and https://billing.example.com. Filling Included Client Audience as well replaced the billing URI with https://reports.example.com and left the entity ID in place. Match the client ID first, then add a mapper only when the integration requires another audience.


References


Frequently Asked Questions

What should the SAML audience contain?

The service provider entity ID. On Keycloak that is normally the SAML client ID, and it is the issuer of the AuthnRequest when Service Provider Entity ID is set. In this 26.8.0 broker test the allowed list also contained the response Destination, which is usually the ACS URL. Other products may require only their documented entity ID, so keep that value in the assertion even if you add more audiences.

Is Audience the same as ACS URL?

No. In the matching login the audience was http://127.0.0.1:18110/realms/apps. The ACS URL and the response destination were http://127.0.0.1:18110/realms/apps/broker/company-idp/endpoint. The audience identifies the service provider the assertion is intended for. The ACS URL is where the browser posts it.

How do I add a custom SAML audience in Keycloak?

On the SAML client, add a protocol mapper of type Audience. Set Included Custom Audience to the URI, and leave Included Client Audience empty. Keycloak still writes the service provider client ID as an audience. The custom URI is added beside it. If Included Client Audience is set, Keycloak uses that client ID and does not write the custom URI.

Why does an SP reject Keycloak AudienceRestriction?

The audience list does not contain the identifier that service provider expects. On this broker the page said Login timeout. Please sign in again. and the server log said the assertion is not addressed to this SP. That happened while the assertion time window was still open. Put the service provider entity ID in the SAML client ID, or add it with an audience mapper without removing the entity ID that is already required.

Can one assertion contain multiple audiences?

Yes. One response contained https://sp.example.com/apps and https://billing.example.com, and the broker accepted it because the service provider entity ID was still present. An additional audience can be named with an audience mapper. Keep the service provider's documented Entity ID in the audience list unless that service provider explicitly documents another requirement. Additional audiences can be added beside it.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)