
OAuth With Unsolicited SAML Responses
On this page
The SAML protocol allows identity providers (IdP) to redirect an already authenticated user to an application — a service provider, or SP, in SAML terms — without initiating the flow on the service provider's part. This approach is called an unsolicited SAML response, or an IdP-initiated SSO. More often, users will use solicited SAML, which means that it is the SP that initiates the SAML flow.
Here is an example that illustrates the difference:
- A solicited SAML flow is when a user navigates to a website and clicks a button to log in. The user is then redirected to the IdP to authenticate and redirected back to the website.
- An unsolicited SAML flow is when a user opens a portal provided by the IdP, logs in, and then selects a website from a list. The user is redirected to the website and is already authenticated.
The unsolicited SAML response, even though it's not recommended by OWASP, is used by some identity providers, and companies need to be able to properly handle it.
Using Unsolicited SAML with OAuth
Sometimes, a company will want to use an OAuth-protected application, but enable users to log in with SAML. This might then mean that the application and the OAuth authorization server need to be prepared to handle an unsolicited SAML response as part of an OAuth flow. The following diagram shows the differences in the authorization flow when the IdP uses an unsolicited SAML response or not.
A flow initiated by a SAML IdP is not an issue for a confidential client — a client that is able to authenticate at the authorization server. Such a client will be able to exchange the authorization code for a set of tokens as usual. A problem arises when dealing with a public client. A public client is not able to authenticate at the authorization server and uses the OAuth security extension Proof Key for Code Exchange (PKCE) to secure the flow. PKCE requires the client to generate a secure, random value, hash it, and send it to the authorization server with the initial authorization request. That value is later checked by the server when the client exchanges the authorization code for tokens.
When using an unsolicited SAML response, the client does not send the initial authorization request, so the client is not able to generate the PKCE challenge or pass it to the server. During the call to the token endpoint, the server has no PKCE challenge and will thus always reject the request. This renders public clients unable to securely obtain tokens when dealing with an unsolicited SAML response.
Confidential clients might also run into the same issue should the authorization server require PKCE for all types of clients. For example, this might happen when an authorization server starts following the OAuth 2.1 recommendations.
Ways to Handle the PKCE Issue
At Curity, we think that a client that needs to use PKCE in such a scenario can use one of these two approaches:
- An external source can provide the application with a PKCE verifier.
- The OAuth client can start a new authorization flow when it detects that the PKCE verifier is missing.
As with any non-standard solutions, there are some limitations and security implications to these approaches. Below, the solutions are described in detail.
Provide the Application With a PKCE Verifier
One option is to ensure the OAuth client receives a proper code verifier out-of-band. For example, an application might use an OAuth library, and the library could read the verifier value from the browser's session storage. In such a case, ensure that session storage contains the code verifier value before the library calls the token endpoint.
The tricky part is to get the proper value for the code verifier, and there are two options for this:
- use a static value,
- use a dynamic value generated by the SAML IdP.
Using a static value is easier to implement, but it has security implications — anyone can read the client's code and obtain the PKCE parameter, which might eliminate the security enhancements that PKCE gives. A more secure way is to use a dynamic value for PKCE. There are a few requirements for such an approach:
- The SAML IdP must be able to generate the code verifier on its own.
- That IdP must then properly set the OAuth parameters that it will send with the unsolicited SAML response (
code_challenge
andcode_challenge_method
). - Both the SAML IdP and the OAuth client must be able to share the code verifier. For example, both applications are browser apps that run on the same domain and can share local storage.
In such a setup, the solution will have pretty much the same security attributes as a regular OAuth code flow with PKCE.
Restart the Authorization Flow
Another approach to solving the PKCE issue is to monitor it in the OAuth client directly and restart the OAuth authorization flow when needed. When the OAuth client receives the authorization code through a redirect to the redirect URI, it can discover that it now has the code but is missing the PKCE code verifier. Then, the client can start a new OAuth authorization flow by redirecting the user to the authorization server.
As the user has just authenticated with SAML, the authorization server should instantly redirect them to the client with a new authorization code. Now, the OAuth client will be able to properly obtain tokens, as it will have all the required parameters. The user will see the page redirect a few times but will otherwise have no additional interaction.
When implementing this approach, adding a circuit breaker might be necessary. In a rare case, the browser might have issues with persisting the PKCE code verifier, which will cause the client to fall into an endless redirect loop. If that becomes an issue, another option is for the client to present a separate page that asks the user to log in, rather than abruptly redirecting them.
The implementation might also get a bit trickier, if the original SAML response contains information required by the OAuth flow, like particular scopes. In such a case, that information needs to pass through the authorization server back to the OAuth client, so the client can use it when restarting the flow. For example, it might require the authorization server to catch the parameters at the authorization endpoint and add them to the response that sends the authorization code back to the client.
Implementing Authorization Code Flow with PKCE for Unsolicited SAML
The following sections demonstrate techniques to implement the previously described approaches. The code snippets use the OAuth Assistant library, but you could implement equivalent techniques in any other OAuth library.
Provide the Client With a Verifier
In this example, the client's OAuth library reads the PKCE code verifier from the browser's session storage. Add the verifier to the session before initializing the assistant, with code similar to this:
if (window.location.search) { // This indicates that there are parameters, most probably sent by the authorization server// get the `code` parameter from the query stringconst args = new URLSearchParams(window.location.search);const code = args.get('code');// read the PKCE code verifier from the session storageconst codeVerifier = window.sessionStorage.getItem('code_verifier');// If there is an authorization code, but no code verifier in the session, then set a new code verifierif (code && !codeVerifier) {const newCodeVerifier = ...; // Either use a hard-coded value or obtain the code verifier from a different sourcewindow.sessionStorage.setItem('code_verifier', newCodeVerifier);}}...assistant.init();
This code should be added to the page that handles the redirect response from the authorization server.
The authorization request sent to the Curity Identity Server, which contains the unsolicited SAML response, should also contain these parameters:
code_challenge_method=S256
code_challenge
with a SHA-256 hash of the PKCE code verifier.
Restart the Authorization Flow
When you want to implement the approach that restarts the OAuth flow, on the page that handles the redirect response from the authorization server, add code similar to the following. The code should be added after initializing the assistant.
assistant.init();...if (window.location.search) { // This indicates that there are parameters, most probably sent by the authorization server// get the `code` parameter from the query stringconst args = new URLSearchParams(window.location.search);const code = args.get('code');// read the PKCE code verifier from the session storageconst codeVerifier = window.sessionStorage.getItem("code_verifier");if (code && !codeVerifier) {// Restart the authorization flowassistant.authorize(params);}}
The params
object contains parameters used by the authorization request, like scopes or state. These can either be hard-coded in the application or can come from the initial request sent to the Curity Identity Server. In the latter case, make the following modifications to the Curity Identity Server's configuration to properly pass the parameters back to the OAuth client.
In the admin UI, in Token Service → Endpoints → oauth-authorize endpoints add a procedure to the Authorization Code flow. Create a new procedure, and update the default implementation to have the responseData
object return the additional parameters. For example:
var responseData = {code: issuedAuthorizationCode,state: context.providedState,iss: context.issuer,scope: context.scope,myParameter: 'someValue'};
In case you also need to pass some non-standard parameters included in the initial request, then also create a script action for the SAML authenticator. The script should be similar to this:
function result(context) {var attributes = context.attributeMap;attributes['myattribute'] = context.getOriginalQueryParameters['myattribute']return attributes;}
Once this script is added, the procedure on the authorization endpoint will have access to the additional attributes in its context
.
Recommended Solution
If you use a system that provides unsolicited SAML logins to multiple web applications, consider updating those systems with application URLs. Doing so is the simplest way to follow current recommended security best practices. You might consider it to be a more modern user experience, since you might design the OAuth client to inform and guide the user before login, rather than issuing abrupt redirects.
If that is not possible, we recommend that the client receives the SAML response and then restarts the application flow, since that is simpler to implement than sharing a runtime PKCE verifier between the legacy system and the OAuth client. Avoid solutions where you disable PKCE or use a static PKCE code verifier, since they reduce the security of the OAuth client.

Michał Trojanowski
Product Marketing Engineer at Curity
Join our Newsletter
Get the latest on identity management, API Security and authentication straight to your inbox.
Start Free Trial
Try the Curity Identity Server for Free. Get up and running in 10 minutes.
Start Free Trial