| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64quay.io/keycloak/keycloak:26.7.3libxml2-2.12.5-10.el10_2.2.x86_64 |
| Applies to | Any Linux host with Podman or Docker |
| Browser | Chrome, Edge, or Firefox with access to Network request payloads |
| Privilege | Normal user (rootless Podman). Realm setup uses the Keycloak bootstrap admin |
| Lab environment | Two Keycloak realms on loopback (18113 = upstream IdP, 18114 = SP/broker). Examples use HTTP on 127.0.0.1; in production, register the application's HTTPS/public ACS URL. The same checks apply when the upstream IdP is Entra ID, Okta, ADFS, or another product. |
| Scope | Why Keycloak acting as a SAML service provider or broker returns invalid destination on an inbound SAMLResponse, how SAMLResponse Destination differs from AuthnRequest Destination, and how to align the upstream IdP ACS URL with the broker endpoint and public hostname. Does not cover outbound SAML client Valid redirect URI errors (Keycloak SAML ACS URL), audience restriction (Keycloak SAML audience error), or full reverse-proxy setup (Keycloak reverse proxy). |
| Related guides | Keycloak SAML SSO Keycloak SAML ACS URL Decode Keycloak SAML assertions Keycloak reverse proxy Configure a SAML client in Keycloak |
When Keycloak uses another identity provider for SAML login, Keycloak acts as the service provider (SP). After the user signs in, the upstream IdP (the provider Keycloak sends the user to for authentication) returns a SAMLResponse—the XML message that carries the authentication result.
That response normally returns to Keycloak's ACS (Assertion Consumer Service) endpoint—the URL on the SP where the browser posts the SAML message. If the response includes a Destination URL that does not match the endpoint Keycloak expects, login can fail with Invalid SAML response / invalid destination.
This article shows how to compare those URLs and fix the mismatch. The examples use two Keycloak realms on one host; the same method applies when the upstream IdP is not Keycloak.
User
|
| opens app / Keycloak login
v
Keycloak apps realm
(SAML SP / broker)
|
| AuthnRequest
| Destination = company IdP SSO URL
v
Keycloak company realm
(SAML IdP)
|
| user authenticates
|
| SAMLResponse
| Destination = apps broker ACS URL
v
Keycloak apps realm
/broker/company-idp/endpointIdentity brokering means Keycloak delegates authentication to another IdP. Toward that upstream IdP, Keycloak is the SAML SP. Keycloak's SAML role depends on the relationship: the company realm acts as the IdP, while the apps realm acts as the SP toward company. The broker ACS is the assertion consumer URL Keycloak exposes for that identity provider alias (here company-idp).
This article is about the second Destination—on the returning SAMLResponse—not the Destination on the outgoing AuthnRequest.
Do not confuse the two Destination values. On the AuthnRequest, Destination points at the IdP SSO URL. On the SAMLResponse, Destination points back at the SP's ACS endpoint.
AuthnRequest: apps SP --------> company IdP
Destination = company SSO URL
SAMLResponse: apps SP <-------- company IdP
Destination = apps ACS URLQuick answer
When Keycloak rejects a SAML response for destination mismatch:
- Capture the
SAMLResponsePOST in browser Developer tools (see below). - Decode the XML and read
Destinationon<samlp:Response>. - Compare that value with the broker ACS endpoint Keycloak exposes for this identity provider (alias
company-idpin the examples). - Compare
Destinationwith the Request URL of the ACS POST in Developer tools. Scheme, hostname, port, path, and any proxy prefix should match the URL Keycloak expects. - On the upstream IdP, update the SP's registered ACS/callback URL to exactly match the Keycloak broker endpoint. In this Keycloak-to-Keycloak lab, that means correcting Valid redirect URIs and the relevant ACS fields on the
companyrealm SAML client. In Entra ID, Okta, or ADFS, look for Reply URL, ACS URL, or Assertion Consumer Service URL. - If a reverse proxy or TLS hostname sits in front of Keycloak, make sure Keycloak, the browser, and the upstream IdP all use the same public ACS URL. See Keycloak reverse proxy for hostname and
--proxy-headersdetail.
Do not try to bypass destination validation. Find which URL differs and correct the configuration that generated it.
What does Invalid Destination mean in Keycloak?
A SAML response can include a Destination attribute naming the endpoint for which the response was created. When Destination is present, Keycloak validates it against the ACS endpoint where it expects to receive the response. A different URL causes destination validation to fail.
Treat the endpoint as an exact URL. A different scheme (http vs https), hostname, port, context path, realm name, or broker alias can produce a mismatch. Even a small URL difference can fail validation.
Keycloak's role as the SAML broker
In the examples, realm apps on http://127.0.0.1:18114 is the SP/broker. Realm company on http://127.0.0.1:18113 is the upstream IdP. A working broker ACS is:
http://127.0.0.1:18114/realms/apps/broker/company-idp/endpoint
SP metadata is the XML document that lists the SP entity ID and ACS endpoints. An external IdP can import it instead of typing URLs by hand:
http://127.0.0.1:18114/realms/apps/broker/company-idp/endpoint/descriptor
AuthnRequest Destination vs SAMLResponse Destination
| Message | Destination points to |
Example in this lab |
|---|---|---|
| AuthnRequest | IdP SSO service | http://127.0.0.1:18113/realms/company/protocol/saml |
| SAMLResponse | SP ACS / broker endpoint | http://127.0.0.1:18114/realms/apps/broker/company-idp/endpoint |
An ACS allow-list error on the upstream IdP (Invalid redirect uri) happens before any SAMLResponse exists. Invalid destination on apps happens when a response arrives but Keycloak rejects its Destination.
Compare the three URLs
When debugging this error, compare three URLs:
| URL | What it is |
|---|---|
Response Destination |
Destination attribute inside the SAML XML |
| Browser POST request URL | Request URL of the ACS POST in Developer tools |
| Broker ACS URL | Broker ACS endpoint Keycloak expects for identity provider company-idp |
On a matching login, all three equal http://127.0.0.1:18114/realms/apps/broker/company-idp/endpoint.
Destination vs ACS URL
The ACS URL is the endpoint where the SP receives the SAML response. When the response includes a Destination attribute, that value should identify the ACS endpoint intended to receive the response.
| Field | Typical value in this broker lab |
|---|---|
| ACS URL / broker endpoint | http://127.0.0.1:18114/realms/apps/broker/company-idp/endpoint |
Response Destination |
Same string when registration matches |
AuthnRequest Destination |
http://127.0.0.1:18113/realms/company/protocol/saml (IdP SSO URL) |
Read Destination from the failed SAMLResponse
Capture SAMLResponse in browser Developer Tools
- Open browser Developer tools and select Network.
- Reproduce the failed login.
- Select the POST request to
/broker/company-idp/endpoint(or the ACS path your IdP used). - Open Payload or Form Data (browser wording varies).
- Copy only the value of the
SAMLResponsefield—not the field name and notSAMLResponse=. - Save that Base64 text alone to
response.b64.
Copy the parsed form value, not the raw request body. The raw body may still contain URL encoding (%2B, %2F, %3D).
Decode and inspect Destination
The browser sends SAMLResponse as a Base64-encoded XML document. Decode it before you read attributes such as Destination:
base64 -d response.b64 > response.xml
xmllint --xpath 'string(//*[local-name()="Response"]/@Destination)' response.xmlhttp://127.0.0.1:18114/realms/apps/broker/company-idp/endpointFor more fields (Audience, InResponseTo, signatures), see Decode Keycloak SAML assertions.
Find the broker ACS URL Keycloak expects
On realm apps, open Identity providers → company-idp. Inbound SAML responses for that broker use:
/realms/apps/broker/company-idp/endpoint
The full ACS URL is the Keycloak base URL plus this broker path. For example:
- Keycloak base URL:
http://127.0.0.1:18114 - Broker path:
/realms/apps/broker/company-idp/endpoint - Full ACS:
http://127.0.0.1:18114/realms/apps/broker/company-idp/endpoint
Register that full string on the upstream IdP. If you change the broker alias, the path changes (/broker/{alias}/endpoint); re-import SP metadata after any alias or hostname change.
Fix the ACS URL on the upstream IdP
The exact setting names depend on your identity provider. Look for the SP's ACS URL, Reply URL, SAML callback URL, or Assertion Consumer Service URL. All of these refer to the endpoint where that IdP should return the SAML response.
Register this value on the upstream IdP:
http://127.0.0.1:18114/realms/apps/broker/company-idp/endpoint
Prefer exact URLs over trailing wildcards. A wildcard can let the IdP emit a Destination your broker does not serve (ACS URL article).
Keycloak as the upstream IdP
In this lab the upstream IdP is realm company. Open the SAML client whose client ID is http://127.0.0.1:18114/realms/apps. Because this lab uses one POST ACS endpoint, set these fields to the same broker URL:
- Valid redirect URIs — allow-list when the AuthnRequest carries
AssertionConsumerServiceURL - Assertion Consumer Service POST Binding URL — fallback ACS when the request omits one
- Master SAML Processing URL — general processing endpoint when no finer ACS is selected
A real SP may publish different endpoints per binding; follow its metadata rather than assuming every ACS-related field must always be identical.
Fix hostname and reverse-proxy mismatches
Destination errors behind nginx, HAProxy, or OpenShift routes often mean Keycloak, the browser, and the upstream IdP disagree on the public ACS URL.
Typical pattern:
- The IdP and users see
https://login.example.com/realms/apps/broker/company-idp/endpoint. - Keycloak still generates or validates the SAML endpoint as
http://127.0.0.1:18114/...instead of the externalhttps://login.example.com/...URL. Destination, the POST URL, and the registered ACS no longer line up.
Keycloak may still listen on an internal HTTP port behind the proxy. The failure appears when externally visible SAML URLs do not match what Keycloak generates or validates.
Make sure Keycloak generates SAML endpoints with the same public scheme, host, port, and context path the IdP uses—for example https://login.example.com, not an internal container URL. Set --hostname (or KC_HOSTNAME) accordingly. Configure Keycloak's --proxy-headers setting to match the headers your proxy sends (Forwarded or X-Forwarded-*) per Keycloak reverse proxy. Re-import the current SP metadata on the upstream IdP after you change hostname or the broker endpoint.
For this error, the important proxy check is whether all three sides use the same public ACS URL. The exact proxy configuration is outside this SAML troubleshooting flow.
Verify the corrected login
Repeat the login after you fix ACS registration and any hostname or proxy settings.
Capture the new ACS POST in Developer tools and overwrite response.b64 with the new SAMLResponse value so you are not inspecting the old failed response.
Decode the new file and confirm Destination matches the broker ACS:
xmllint --xpath 'string(//*[local-name()="Response"]/@Destination)' response.xmlhttp://127.0.0.1:18114/realms/apps/broker/company-idp/endpointA successful run reaches Update Account Information or the account console without a destination error. If Destination is correct but login still fails, check signature (Keycloak SAML invalid signature) and audience (Keycloak SAML audience error) next.
Optional: Reproduce an Invalid Destination error
You do not need to reproduce the failure if you already have a failing login. Use this section only to see how Destination is chosen.
Adding a wrong URL to Valid redirect URIs alone does not change where Keycloak sends the response. The incoming AuthnRequest must request that URL as its AssertionConsumerServiceURL.
Test environment (examples)
| Role | Realm | Base URL |
|---|---|---|
| Upstream SAML IdP | company |
http://127.0.0.1:18113 |
| Keycloak SP/broker | apps |
http://127.0.0.1:18114 |
| Broker alias | company-idp |
— |
| Broker ACS | — | http://127.0.0.1:18114/realms/apps/broker/company-idp/endpoint |
Start from a known-good broker login: open http://127.0.0.1:18114/realms/apps/account, choose Company IdP, and sign in as alice / Alice!2026. A successful login gives you a known-good baseline before you introduce the mismatch.
Wrong ACS not allowed
|
v
IdP rejects AuthnRequest
"Invalid redirect uri"
Wrong ACS allowed
|
v
IdP creates SAMLResponse
Destination = /wrong
|
v
Browser POST + broker validation
destination / routing failureOn realm company, add a second Valid redirect URI:
http://127.0.0.1:18114/realms/apps/broker/company-idp/wrong
Send an AuthnRequest whose ACS is that URL. A normal account-console login still uses the correct broker ACS, so it will not exercise /wrong by itself.
If you are not running the optional two-realm lab, skip this reproduction—you do not need it to diagnose or fix an existing Invalid Destination error. Otherwise, build a redirect-binding AuthnRequest as the apps SP toward the company IdP (the company SAML client must allow unsigned AuthnRequests):
#!/usr/bin/env python3
import base64, datetime, uuid, urllib.parse, zlib
IDP_SSO = "http://127.0.0.1:18113/realms/company/protocol/saml"
ACS = "http://127.0.0.1:18114/realms/apps/broker/company-idp/wrong"
ISSUER = "http://127.0.0.1:18114/realms/apps"
def redirect_url():
rid = f"_{uuid.uuid4()}"
instant = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
xml = (
f'<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" '
f'xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{rid}" Version="2.0" '
f'IssueInstant="{instant}" Destination="{IDP_SSO}" '
f'AssertionConsumerServiceURL="{ACS}" '
f'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST">'
f"<saml:Issuer>{ISSUER}</saml:Issuer></samlp:AuthnRequest>"
)
enc = base64.b64encode(zlib.compress(xml.encode())[2:-4]).decode()
return f"{IDP_SSO}?SAMLRequest={urllib.parse.quote_plus(enc)}"
if __name__ == "__main__":
print(redirect_url())Save as build-authnrequest-broker.py, run python3 build-authnrequest-broker.py, and open the printed URL in a browser. Sign in as alice / Alice!2026. The company IdP can create a SAMLResponse with:
Destination="http://127.0.0.1:18114/realms/apps/broker/company-idp/wrong"The browser posts to /wrong. In a test run on Keycloak 26.7.3, the apps realm answered Page not found while the decoded SAMLResponse still carried Destination="http://127.0.0.1:18114/realms/apps/broker/company-idp/wrong". Decode SAMLResponse from the POST either way—the Destination attribute shows what the IdP emitted.
Remove the /wrong redirect URI and restore a single exact broker ACS when you finish testing.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Invalid destination on apps broker | Response Destination ≠ broker ACS URL |
Register the broker ACS on the upstream IdP; re-import current SP metadata |
| Page not found after IdP shows success | Browser posted to a wrong ACS path | Register only real ACS URLs; avoid wildcard redirect URIs (ACS URL) |
Response Destination uses one hostname, ACS POST uses another |
Hostname or proxy mismatch | Align public URL, --hostname, --proxy-headers, and IdP ACS (reverse proxy) |
| Invalid redirect uri on upstream IdP | AuthnRequest ACS not allow-listed | Fix ACS allow-list—not a destination error on the broker |
| Error after changing broker alias | Stale ACS on external IdP | Update alias in apps and re-import descriptor; path includes /broker/{alias}/endpoint |
Summary
Invalid destination means Keycloak refused an inbound SAMLResponse because Destination did not match the broker ACS endpoint handling the POST. Compare three values: the response Destination, the browser's ACS POST URL, and the broker ACS URL Keycloak expects.
Fix the ACS URL on the upstream IdP (Reply URL, ACS, or Keycloak Valid redirect URIs in the lab). You can see the same error when Keycloak and the IdP use different public or internal URLs behind a proxy.
Use Keycloak SAML ACS URL when the upstream IdP rejects the AuthnRequest before a response exists. Use this page when a response exists but the broker rejects it for destination. Decode Keycloak SAML assertions helps you inspect Destination with Audience and InResponseTo.
References
- Keycloak 26.7.3 Server Administration: SAML v2.0 identity providers
- Keycloak 26.7.3 Server Administration: SAML clients
- Keycloak 26.7.3 reverse proxy — hostname and
--proxy-headers - OASIS SAML 2.0 Core — optional
Destinationon protocol messages
Frequently Asked Questions
Why does Keycloak say Invalid Destination?
The SAMLResponse Destination attribute does not match the ACS URL Keycloak expects for that broker endpoint. Compare the decoded XML destination to the broker ACS URL on the SAML identity provider in the apps realm.
Is Destination the same as the ACS URL?
When Destination is present on an HTTP-POST SAML response, it should identify the ACS endpoint intended to receive that response. Keycloak validates that value against the expected endpoint.
Why does this happen behind a reverse proxy?
The upstream IdP often registers a public hostname while Keycloak validates against another URL, or the opposite. Align the public ACS URL, Keycloak hostname settings, and --proxy-headers with what the IdP was given.
Which URL should the upstream IdP use for ACS?
The broker endpoint for that identity provider alias—for example /realms/apps/broker/company-idp/endpoint on the apps Keycloak base URL. Re-import the current SP metadata after changing hostname or broker endpoint.
Can I disable destination validation?
No. Register the correct ACS on the upstream IdP and fix hostname or proxy settings instead of bypassing validation.

