spring-authentication-server

Logo

The SAML Identity Provider

License


This page describes how the SAML Identity Provider handles an authentication request from start to end: how the request is received and turned into the protocol-neutral authentication requirements, how the user is authenticated, how single sign-on is decided, how the released attributes are chosen, what the response holds, which failures are answered to the Service Provider, and how to replace the parts of the processing. The properties are described in Configuration.

Source links in this guide point to the main branch of the spring-authentication-server repository.

How a request flows

  1. The Service Provider sends an AuthnRequest to one of the authentication endpoints. The request is decoded, validated and turned into authentication requirements, see Receiving a request.

  2. The requirements are handed to the authentication providers, together with the authentication in the session that may be reused. The first provider that can serve the request handles it, see Authenticating the user.

  3. Either the provider answers at once, from single sign-on or with a new authentication, or it sends the user to the pages of its module. In the second case, the flow continues when the user comes back to the resume path, see Modules with pages of their own.

  4. The post-authentication processors run on the result, the released attributes are chosen, and the assertion and the response are built, see The response.

  5. The authentication is kept in the session for single sign-on, and the response is posted to the assertion consumer service of the Service Provider.

A failure after the assertion consumer service has been established is answered to the Service Provider with an error response, see Failures.

Receiving a request

Authentication requests are received on the authentication endpoints under the SAML path, see URL layout:

The processing is done by Saml2AuthnRequestAuthenticationConverter, which decodes the request and finds the Service Provider, and Saml2AuthnRequestAuthenticationProvider, which validates it and builds the authentication requirements. In this order:

  1. The message is decoded, and must be an AuthnRequest of SAML version 2 with an ID and an Issuer.
  2. The Destination of the message must be the endpoint it was received on.
  3. The message must not be older than the maximum message age, allowing for the clock skew.
  4. The Service Provider is looked up in the client registry.
  5. The replay check: a request with an ID that has already been seen is rejected.
  6. The assertion consumer service is established: the one the request asks for, by URL or by index, or the default one of the Service Provider metadata. It must be in the metadata.
  7. The signature is validated against the signing keys of the Service Provider metadata. An unsigned request is rejected if the Identity Provider requires signed requests, or if the metadata states AuthnRequestsSigned.
  8. When assertions are encrypted, the Service Provider metadata must have a key to encrypt them for.
  9. The requester acceptance check decides whether the Service Provider may use the Identity Provider.
  10. The NameIDPolicy is checked by the NameID generator factory, see Attributes.
  11. The authentication requirements are built.

What the requirements hold

The result is a SamlAuthenticationRequirements, described in Writing an authentication module:

The requirements are handed on together with the SAML data that the response is built from: Saml2AuthnRequestData, holding the request, where to send the response, whether the request came on a Holder-of-key endpoint, and the NameID generator.

Authenticating the user

The processed request is handed to the installed authentication providers, in the order they were installed. With Spring Boot, every UserAuthenticationProvider bean is installed, in Spring’s order.

Choosing the provider

A provider that cannot deliver any of the requested authentication contexts returns null, and the next one is asked. The first provider that returns something handles the request. When no provider handles it, the Service Provider gets Requester / NoAuthnContext.

A request without a RequestedAuthnContext can be served by any provider, so the first one handles it. Install the providers in the order they should be preferred.

Single sign-on

The session holds one authentication for the whole server, whichever protocol it was made for, since single sign-on is shared between SAML and OpenID Connect. It is kept in the Spring Security SecurityContext of the session. When a request comes in, that authentication is given to the provider, and the provider decides whether it may be reused, as described in Writing an authentication module.

The single sign-on policy of a provider, when it has one, wins over the SAML policy under authn-server.saml.sso, which wins over the shared policy under authn-server.sso. With the default policy, an authentication is reused for 60 minutes, and only for the Service Provider it was made for.

When an authentication is reused, it is answered with the requirements of the new request. The attributes that are released are therefore what the new request asks for, not what the original request asked for.

After a successful response, the authentication is saved in the session. An authentication that may not be reused, such as one where a sign message was displayed, is not saved, and it also removes any earlier authentication from the session. What happens to the session authentication otherwise is described in Writing an authentication module.

Modules with pages of their own

A provider that authenticates the user on pages of its own, see Writing an authentication module, sends the user to its authentication path. The server’s filter chain serves both paths of every such provider:

A request to the resume path that has no matching authentication in progress, one that has expired or has already been resumed, and one for a protocol that is not enabled, is the unrecoverable invalid session error, and the user is shown an error page. An error that the module reports, including a cancel, is answered to the Service Provider.

Producers, voters and processors

Four kinds of components take part in the authentication and the response:

Each kind is a shared list on the AuthnServerConfigurer, for entries that apply to all protocols, plus a list on each protocol configurer. For a SAML request the SAML entries come first, followed by the shared ones. The order matters, since the first producer to release an attribute wins and the voters are asked in order. The single sign-on voters and the post-authentication processors of a provider itself run before both lists.

The defaults are:

List SAML Shared
Attribute producers SwedenConnectAttributeProducer DefaultAttributeProducer
Attribute release voters SwedenConnectAttributeReleaseVoter IncludeAllAttributeReleaseVoter
Single sign-on voters - -
Post-authentication processors - SwedenConnectPostAuthenticationProcessor

Together they give the rules of the Swedish eID Framework: the requested attributes are released, with the sign message digest and the SAD for a signature service, a coordination number is only released to a Service Provider that accepts it, and a sign message that had to be displayed but was not fails the request. This is the same result as the defaults of saml-identity-provider. The shared DefaultAttributeProducer releases the requested attributes for every protocol; for SAML it adds nothing, since SwedenConnectAttributeProducer runs first and releases the same attributes, and a later producer releasing an attribute that has already been released is ignored. The SADFactory of the producer defaults to one that signs with the signing credential of the Identity Provider.

Add entries in an adapter. An entry that applies to all protocols goes in the shared list:

@Bean
AuthnServerConfigurerAdapter releaseRules() {
  return (http, configurer) -> configurer
      .attributeReleaseVoters(voters -> voters.addFirst(new AllowedAttributesVoter()))
      .postAuthenticationProcessors(processors -> processors.add(new MyChecks()))
      .protocol(Saml2IdpConfigurer.class, saml -> saml
          .attributeProducers(producers -> producers.addFirst(new MySamlOnlyProducer()))
          .ssoVoters(voters -> voters.add(new MySamlOnlySsoVoter())));
}

The lists are read when the filter chain is built, so changes after that have no effect. The single sign-on voters and post-authentication processors of the lists reach the providers that extend AbstractUserAuthenticationProvider.

The response

A successful authentication is answered with a signed Response holding one assertion, built by Saml2AssertionBuilder:

The assertion is signed when the Service Provider metadata states WantAssertionsSigned, and always for Holder-of-key. It is encrypted for the Service Provider when authn-server.saml.assertions.encrypt is true, which is the default, with a key and the algorithms of the Service Provider metadata. The response is always signed.

The response is posted to the assertion consumer service by the response page, together with the relay state. The default page is a form that the browser submits by itself.

Failures

Until the Service Provider is known and the assertion consumer service has been established, there is nowhere to send a response. Failures up to that point end at the Identity Provider as an UnrecoverableErrorException, with one of the errors of SamlUnrecoverableError. After that point, a failure is answered to the Service Provider with a signed error response, posted to the assertion consumer service together with the relay state.

Failure Outcome
The message cannot be decoded, is not an AuthnRequest, or lacks version, ID or issuer Unrecoverable
The Destination does not match the endpoint Unrecoverable
The message is too old Unrecoverable
The Service Provider is not known Unrecoverable (UNKNOWN_PEER)
The client registry fails when the Service Provider is looked up Unrecoverable (PEER_LOOKUP_FAILED)
The request has already been received Unrecoverable
The assertion consumer service is not in the metadata Unrecoverable
The signature is missing or invalid Unrecoverable
The Service Provider has no key for encrypted assertions Requester / RequestDenied
The Service Provider is not accepted Responder / RequestDenied
The NameIDPolicy asks for a format that is not supported Requester / InvalidNameIDPolicy
ForceAuthn and IsPassive are both set Requester / RequestUnsupported
The requested authentication context cannot be resolved Requester / RequestUnsupported
The SignMessage cannot be decrypted or has nothing to display Requester / RequestUnsupported
The UserMessage has a MIME type that is not supported, and user messages are supported Requester / RequestUnsupported
The SADRequest is invalid Requester / RequestUnsupported
A request on a Holder-of-key endpoint has no client certificate Responder / AuthnFailed
No provider can deliver a requested authentication context Requester / NoAuthnContext
The authentication fails, or the module reports an error The status of the error, see Errors
A sign message had to be displayed, but was not Responder / AuthnFailed
The resume path is reached without a matching authentication in progress Unrecoverable (INVALID_SESSION)

An unknown Service Provider and a registry that fails are logged differently: the first at INFO, since it is a normal outcome, and the second at ERROR, since a dependency is not working.

The status message of an error response is resolved from the message code of the error against a MessageSource, when one is given, and is otherwise the description of the error.

Replacing parts of the processing

The components have defaults built from the configuration. Replace them in an adapter through the Saml2AuthnRequestProcessorConfigurer:

@Bean
AuthnServerConfigurerAdapter samlProcessing(final MessageSource messageSource) {
  return (http, configurer) -> configurer.protocol(Saml2IdpConfigurer.class, saml -> saml
      .authnRequestProcessor(processor -> processor
          .messageSource(messageSource)
          .responsePage(myResponsePage)));
}

The replay checker, the requested attribute resolver, the sign message extractor, the assertion consumer service validator, the response page, the message source, and customizers for assertions and responses can be replaced this way. The assertion customizer gets the assertion before it is signed and encrypted.


Copyright © 2026, Sweden Connect. Licensed under version 2.0 of the Apache License.