Fix Keycloak SAML ACS URL 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 Keycloak rejects an AssertionConsumerServiceURL with Invalid redirect uri, and which client fields allow that ACS. The client ID, the ACS callback, and the identity provider SSO endpoint are three different addresses. Does not cover audience restriction, ACS index selection in metadata, or a reverse proxy that rewrites Destination.
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 14-minute read in seconds, then ask follow-up questions

Fix Invalid redirect uri by registering the service provider ACS as an exact Valid Redirect URI. After authentication, the identity provider creates a SAML response. In this POST-binding flow, the browser carries that response to the service provider's ACS URL in an HTTP POST. The ACS belongs to the service provider, not the identity provider.

Realm company on 127.0.0.1:18111 is the identity provider. Realm apps on 127.0.0.1:18112 is the broker that receives the response. The two-realm login is the one in Keycloak SAML SSO. In this lab, the working ACS is the apps broker endpoint. Register that exact callback instead of widening the redirect allow-list to a wildcard. Keycloak can accept a wildcard-matched URL even when nothing is listening on that path.


Quick Answer

For a SAML client, Keycloak validates the AssertionConsumerServiceURL from the incoming AuthnRequest against Valid Redirect URIs. Register the real ACS callback there as an exact URL. If the request does not contain an ACS, Keycloak can fall back to the configured Assertion Consumer Service POST Binding URL and then the Master SAML Processing URL, depending on the request, the binding, and the client configuration.

Do not fix the error by widening the redirect URI to /*. That can allow Keycloak to post a valid SAML response to a path your service provider does not actually serve.


What the SAML ACS URL Is

The Assertion Consumer Service URL is the callback that receives the SAML response. The service provider puts it on the AuthnRequest as AssertionConsumerServiceURL. Keycloak copies an allowed value into the response Destination.

This is the path the browser follows in the POST binding used here:

                      SP
                       ↓
AuthnRequest, AssertionConsumerServiceURL = ACS
                       ↓
                 Keycloak IdP
                       ↓
  authenticate the user, create SAMLResponse
                       ↓
                    Browser
                       ↓
               POST SAMLResponse
                       ↓
                 ACS on the SP

On the matching login the apps broker sent this request to the company realm. The opening tag carries both the callback and the SSO endpoint:

xml
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" AssertionConsumerServiceURL="http://127.0.0.1:18112/realms/apps/broker/company-idp/endpoint" Destination="http://127.0.0.1:18111/realms/company/protocol/saml" ForceAuthn="false" ID="ID_3bee7c6d-ea34-48d9-8a94-8f9ab62c74da" IssueInstant="2026-10-03T20:28:57.884Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0">

Read the callback on its own:

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

That path is the broker endpoint for the identity provider alias company-idp. It is the ACS for this client. The login that used it reached Update Account Information on the apps realm, which means the broker accepted the response.


ACS URL vs Entity ID vs Keycloak SSO URL

Three addresses show up in the same login, and only one of them is the ACS.

                  SP entity ID and client ID
              http://127.0.0.1:18112/realms/apps
                              ↓
                ACS URL, the browser callback
http://127.0.0.1:18112/realms/apps/broker/company-idp/endpoint
                              ↓
            IdP SSO URL, AuthnRequest Destination
     http://127.0.0.1:18111/realms/company/protocol/saml
Address What it names Value on this login
Client ID The service provider Entity ID. In this flow it also identifies the SAML client whose ACS settings Keycloak validates http://127.0.0.1:18112/realms/apps
ACS URL Where the browser posts the response http://127.0.0.1:18112/realms/apps/broker/company-idp/endpoint
SSO URL Where the AuthnRequest is sent. It is the company realm SAML endpoint http://127.0.0.1:18111/realms/company/protocol/saml

The client ID is the Issuer of the AuthnRequest. An audience failure is a different problem from a rejected ACS. Audience restriction is the page for that mismatch.

Read the issuer from the same request:

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

The issuer is the apps realm URL. It does not include /broker/company-idp/endpoint.

The Destination on the AuthnRequest is the company SSO endpoint, not the ACS:

bash
xmllint --xpath 'string(//*[local-name()="AuthnRequest"]/@Destination)' request.xml
output
http://127.0.0.1:18111/realms/company/protocol/saml

Keep those three strings apart when you copy metadata. The client ID identifies the service provider. The ACS is the callback. The SSO URL is where the request goes.


Configure the SP ACS in a Keycloak SAML Client

The company realm is the identity provider, so the ACS is configured on the SAML client there. The client ID is the apps realm URL. The callback belongs in three places that should agree:

  • Valid Redirect URIs: the allow-list for an ACS supplied on the AuthnRequest
  • Assertion Consumer Service POST Binding URL: the callback used when the request omits an ACS and this login is POST
  • Master SAML Processing URL: the client's general SAML processing endpoint, used when the request omits an ACS and the POST binding URL is empty

I set all three to the broker endpoint. Read them back from the client:

bash
podman exec keycloak-saml-acs-idp /opt/keycloak/bin/kcadm.sh get clients -r company \
  -q clientId=http://127.0.0.1:18112/realms/apps | grep -E 'clientId|adminUrl|redirectUris|saml_assertion_consumer'
output
  "clientId" : "http://127.0.0.1:18112/realms/apps",
  "adminUrl" : "http://127.0.0.1:18112/realms/apps/broker/company-idp/endpoint",
  "redirectUris" : [ "http://127.0.0.1:18112/realms/apps/broker/company-idp/endpoint" ],
    "saml_assertion_consumer_url_post" : "http://127.0.0.1:18112/realms/apps/broker/company-idp/endpoint",

redirectUris is Valid Redirect URIs. saml_assertion_consumer_url_post is the POST binding ACS. In the Admin REST representation, Keycloak stores Master SAML Processing URL in adminUrl. Despite that internal field name, this is not an Admin Console URL. For a SAML client it is the client's general SAML processing endpoint. The redirect-binding attribute was not set.

In the Admin Console, open the client in the company realm and stay on the Settings tab. Valid redirect URIs and Master SAML Processing URL sit in the Access settings block:

Keycloak SAML client Settings tab with Valid redirect URIs and Master SAML Processing URL set to the broker endpoint

The POST binding ACS is on a different tab. Open Advanced and look under Fine Grain SAML Endpoint Configuration:

Keycloak SAML client Advanced tab with Assertion Consumer Service POST Binding URL set to the broker endpoint

All three fields show the same broker endpoint, and the redirect-binding field is empty. Click Save on each tab after you edit it, because the two tabs save separately.


How AuthnRequest ACS Overrides Work

In this Keycloak 26.8.0 POST-binding test, an AssertionConsumerServiceURL on the AuthnRequest took precedence over the configured endpoints. When the attribute was missing, the fallback order was the POST binding URL, then the Master SAML Processing URL. The administration guide says a request ACS takes precedence over Master SAML Processing URL, and it documents POST, then Master, then Redirect selection for IdP-initiated login. The SP-initiated requests on 26.8.0 followed that POST-then-Master order.

Does the AuthnRequest contain AssertionConsumerServiceURL?
                         │
               ┌─────────┴─────────┐
              Yes                  No
               │                    │
               ▼                    ▼
     Check it against       POST Binding ACS
   Valid Redirect URIs             │
               │                empty?
               │                    │
               ▼                    ▼
   Use the request ACS       Master SAML
     when it matches       Processing URL

Valid Redirect URIs is an allow-list. It does not choose the callback when the request has no ACS. The POST binding URL, and then the Master SAML Processing URL, supply that callback.

I added http://127.0.0.1:18112/realms/apps/broker/company-idp/alt as a second exact redirect URI and left the POST binding URL on the broker endpoint. I then changed only the request ACS to that /alt path. The request carried the new callback:

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

Because /alt was registered as a valid redirect URI, Keycloak used the request's ACS and set the response Destination to /alt. The POST binding URL was still the broker endpoint:

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

The status was urn:oasis:names:tc:SAML:2.0:status:Success. The company realm accepted the ACS because it was on the allow-list. The apps realm has no broker listener on /alt, so the browser stopped on Page not found. An allowed ACS is not the same thing as a callback the service provider serves.

I then removed AssertionConsumerServiceURL from the request and left the POST binding URL on the broker endpoint. Confirm the attribute is gone:

bash
xmllint --xpath 'string(//*[local-name()="AuthnRequest"]/@AssertionConsumerServiceURL)' request-no-acs.xml

The command prints nothing. The request did not name a callback, so Keycloak used the POST binding URL. The response destination was the broker endpoint, and the apps realm reached Update Account Information:

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

I next pointed the POST binding URL at /alt and again sent a request with no ACS. The Master URL was still the broker endpoint. Destination followed the POST binding URL:

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

I cleared the POST binding URL and sent one more request with no ACS. Destination fell back to the Master SAML Processing URL, and the login reached Update Account Information again:

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

When the service provider includes AssertionConsumerServiceURL, register that exact callback in Valid Redirect URIs. The configured POST and Master URLs matter when the request does not supply an ACS. Set those two fields to the same real callback so a request that omits the ACS still returns to the broker.


Valid Redirect URI Validation

Keycloak checks a request ACS against Valid Redirect URIs before it shows a login form. I put the broker endpoint back as the only redirect URI and changed the request ACS to a path that was not registered:

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

The company realm stopped on the error card. There was no login form and no SAML response:

Company realm error card reading Invalid redirect uri

The event on the identity provider is invalid_redirect_uri. Filter that moment:

bash
podman logs keycloak-saml-acs-idp 2>&1 | grep '2026-10-03 20:29:25'
output
2026-10-03 20:29:25,331 WARN  [org.keycloak.events] (executor-thread-3) type="LOGIN_ERROR", realmId="a33ea812-7c25-43ed-83c0-bb04dc1aaa9c", realmName="company", clientId="null", userId="null", ipAddress="10.88.0.1", error="invalid_redirect_uri"

Invalid redirect uri means the ACS on the request did not match a Valid Redirect URI. A missing client logs client_not_found. In this failed request, the event logged clientId="null" even though the AuthnRequest issuer matched the configured client. The useful diagnostic here is error="invalid_redirect_uri". In this controlled test, the same issuer worked once the ACS was corrected, so the failure was isolated to redirect and ACS validation rather than client lookup.

A trailing wildcard would have hidden that mistake. I set Valid Redirect URIs to http://127.0.0.1:18112/* and sent the same /wrong ACS. On Keycloak 26.8.0 the company realm accepted it. The response destination was the path the service provider does not serve:

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

I also tried http://127.0.0.1*. On Keycloak 26.8.0 that pattern still allowed the /wrong callback. The SAML client settings allow a wildcard only at the end of the URL, and the documented example is a path pattern such as http://host.com/*. Keep http://127.0.0.1* as a result from this 26.8.0 test, not as a pattern to copy. Register the exact callback the service provider sends.


Fix Wrong or Rejected ACS URLs

Prefer registering the real ACS as an exact Valid Redirect URI. Keycloak supports trailing wildcards, but a broad pattern is usually the wrong fix for an ACS mismatch, because it can allow a callback path the service provider does not serve. If the service provider omits the attribute, set the POST binding URL and the Master SAML Processing URL to that same callback. I restored all three to http://127.0.0.1:18112/realms/apps/broker/company-idp/endpoint. The next request that carried that ACS reached Update Account Information.

Do not clear the error by widening the allow-list. On Keycloak 26.8.0, http://127.0.0.1:18112/* made the /wrong ACS legal, and the browser then posted a success response to a path the apps realm does not serve. Registering /alt did the same thing for a path I had added on purpose. The fix is the callback the broker actually listens on.

What you see What is wrong What to change
Invalid redirect uri and invalid_redirect_uri The request ACS is not a Valid Redirect URI Add that exact URL, when it is the real callback
Page not found, response status Success Destination is allowed, but nothing listens there Point the request ACS, the POST URL, and the Master URL at the broker endpoint
Destination is the Master URL, and the service provider expected another ACS The request omitted AssertionConsumerServiceURL Set the POST binding URL to the callback the service provider serves
Invalid Request and client_not_found The issuer is not a client ID Fix the client ID. That is not an ACS change

Multiple ACS Endpoints and Bindings

One SAML client can list more than one Valid Redirect URI. Each extra URI is another ACS the AuthnRequest is allowed to ask for. The /alt login above used this list, with the broker endpoint first and /alt added as a second row under Valid redirect URIs:

Keycloak SAML client Valid redirect URIs listing the broker endpoint and a second alt ACS

With both rows saved, the company realm returned Success and Destination was the second URI. The apps realm answered with Page not found, because /alt is not the broker endpoint. That generic page shows no realm name, so read Destination from the response to tell it apart from a real broker error. Remove the extra row once you are done testing.

Add a second URI only when a second listener exists. A spare path on the same host is not a second ACS. SAML metadata can also describe multiple ACS endpoints and indexes. This article focuses on URL validation for AssertionConsumerServiceURL, not ACS index selection.

This login used the POST binding. The request says so:

bash
xmllint --xpath 'string(//*[local-name()="AuthnRequest"]/@ProtocolBinding)' request.xml
output
urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST

The client also had saml.force.post.binding set to true, and saml_assertion_consumer_url_redirect was unset. POST and Redirect are different ways of transporting SAML protocol messages, so Keycloak can store a separate ACS endpoint for each binding when a service provider exposes different endpoints. This lab used HTTP-POST, so the POST Binding URL is the relevant one. The redirect binding ACS is a separate field, Assertion Consumer Service Redirect Binding URL. I did not send a redirect-binding AuthnRequest, so that field stayed empty. If you use redirect binding, set that URL to the callback the service provider exposes for it, and keep the same exact string in Valid Redirect URIs. An ACS on the AuthnRequest still has to match the allow-list, whichever binding the request names.


Verify the Response Destination

After the client fields match the callback the service provider sends, sign in again and read Destination from the response. On the restored client it was the broker endpoint:

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

That is the same string as AssertionConsumerServiceURL on request.xml. The status was success:

bash
xmllint --xpath 'string(//*[local-name()="StatusCode"]/@Value)' response.xml
output
urn:oasis:names:tc:SAML:2.0:status:Success

Update Account Information on the apps realm means the broker received that POST. If Destination matches and the service provider still rejects the assertion, compare the client ID next. The ACS is already the address you meant to use. If Keycloak generates the intended ACS but the service provider still reports a Destination mismatch because a proxy or public hostname rewrote the URL, that is a different problem from the allow-list error covered here.


Summary

The ACS URL is the callback on AssertionConsumerServiceURL. Keycloak copies an allowed value into the response Destination. The client ID and the identity provider SSO URL are different addresses. On this login the client ID was the apps realm URL, the ACS was the broker endpoint, and the SSO URL was the company realm /protocol/saml path.

On Keycloak 26.8.0, a request ACS overrode the POST binding URL and the Master SAML Processing URL, but only when it matched a Valid Redirect URI. /wrong produced Invalid redirect uri and invalid_redirect_uri while the only registered URI was the broker endpoint. Removing the attribute made Destination follow the POST binding URL, and clearing that field made it follow the Master URL.

On that same version, http://127.0.0.1:18112/* and http://127.0.0.1* both let the wrong path through, and the apps realm then returned Page not found. Prefer the exact broker endpoint in Valid Redirect URIs. Set the POST binding URL and the Master SAML Processing URL to that same callback when the service provider sometimes omits the ACS. The host-star result stays tied to this 26.8.0 test.


References


Frequently Asked Questions

Where do I set ACS URL in Keycloak?

On the SAML client in the identity provider realm. Put the callback in Valid Redirect URIs. Set Master SAML Processing URL and Assertion Consumer Service POST Binding URL to the same callback when the AuthnRequest may omit AssertionConsumerServiceURL. The POST binding field is the one this login used.

Is ACS URL the same as Client ID?

No. The client ID is the service provider entity ID and the Issuer of the AuthnRequest. The ACS URL is the broker endpoint the browser posts the response to. On this login those were http://127.0.0.1:18112/realms/apps and http://127.0.0.1:18112/realms/apps/broker/company-idp/endpoint.

Why is Keycloak rejecting AssertionConsumerServiceURL?

The URL on the AuthnRequest is not a Valid Redirect URI for that client. The company realm shows Invalid redirect uri, and the event is invalid_redirect_uri. Prefer the exact callback. Keycloak allows a trailing wildcard, and on 26.8.0 both http://127.0.0.1:18112/* and http://127.0.0.1* accepted a path the service provider does not serve.

What is Master SAML Processing URL?

Master SAML Processing URL is the client's general SAML processing endpoint in Keycloak. It can act as the fallback ACS when the AuthnRequest does not provide AssertionConsumerServiceURL and a more specific binding URL is not configured. In Keycloak's Admin REST representation, the same value appears as adminUrl. That field name does not make it an Admin Console URL.

Can one SAML client use multiple ACS URLs?

Yes. Each extra Valid Redirect URI is another ACS the AuthnRequest may name. Keycloak sets Destination to the ACS on that request. The service provider still has to listen on that URL. A second path that returns Page not found is not a working ACS.

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)