Table of Contents

SAML 2.0 SP-initiated single sign-on

This server is also a SAML 2.0 identity provider. A service provider sends the browser to it with an AuthnRequest (HTTP-Redirect binding); the user signs in; and the server answers with a signed Response carrying an assertion about the user, in a form the browser posts to the service provider's assertion consumer service (HTTP-POST binding). The service provider is the demo host's https://sp.demo.example.com/saml, which is registered to send unsigned requests; it learns the server's signing certificate from the metadata.

Follows: SAML 2.0 Core, SAML 2.0 Bindings, SAML 2.0 Web Browser SSO Profile.

Recorded from the demo host (src/Fdyay.IdentityServer.Host) by the SamlSsoFlow test on every CI run. A value in braces, such as {access_token_1}, is one that differs on every run; the same placeholder is the same value wherever it appears. See how these pages are made.

sequenceDiagram
    participant Browser
    participant ServiceProvider as Service Provider
    participant AuthorizationServer as Authorization Server
    ServiceProvider->>AuthorizationServer: GET /saml/metadata
    AuthorizationServer-->>ServiceProvider: 200 OK
    Browser->>AuthorizationServer: GET /saml/sso
    AuthorizationServer-->>Browser: 302 Found to /account/login
    Note over Browser,AuthorizationServer: The user signs in on the sign-in page, as in the<br/>authorization code flow.
    Browser->>AuthorizationServer: GET /saml/sso/callback
    AuthorizationServer-->>Browser: 200 OK
    Note over Browser,ServiceProvider: The browser posts the form to the service provider,<br/>which checks the signature against the metadata's<br/>certificate and signs the user in.

1. The service provider reads the identity provider's metadata

The entity id, the single sign-on and logout endpoints for each binding, and the certificate the server signs with.

Service Provider → Authorization Server

GET /saml/metadata HTTP/1.1

Authorization Server → Service Provider

HTTP/1.1 200 OK
Content-Type: application/samlmetadata+xml; charset=UTF-8

<md:EntityDescriptor xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://localhost:5001/saml" ID="{saml_id_1}" cacheDuration="PT12H" validUntil="{timestamp}" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
  <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" WantAuthnRequestsSigned="true">
    <md:KeyDescriptor use="signing">
      <ds:KeyInfo>
        <ds:X509Data>
          <ds:X509Certificate>{certificate_1}</ds:X509Certificate>
        </ds:X509Data>
      </ds:KeyInfo>
    </md:KeyDescriptor>
    <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost/saml/slo" />
    <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost/saml/slo" />
    <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
    <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
    <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost/saml/sso" />
    <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost/saml/sso" />
  </md:IDPSSODescriptor>
</md:EntityDescriptor>

2. The service provider sends the browser with an AuthnRequest

Nobody is signed in, so the server sends the browser to its sign-in page.

Browser → Authorization Server

GET /saml/sso?SAMLRequest={saml_request_1}
    &RelayState=relay-7d1c HTTP/1.1

{saml_request_1} decoded (DEFLATE, then Base64, as the redirect binding sends it):

<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_documented-authn-request" Version="2.0" IssueInstant="{timestamp}" Destination="http://localhost/saml/sso" AssertionConsumerServiceURL="https://sp.demo.example.com/acs" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
  <saml:Issuer>https://sp.demo.example.com/saml</saml:Issuer>
</samlp:AuthnRequest>

Authorization Server → Browser

HTTP/1.1 302 Found
Location: /account/login?returnUrl=%2Fsaml%2Fsso%2Fcallback%3FsamlStateId%3D{saml_state_id_1}

The user signs in on the sign-in page, as in the authorization code flow.

3. The browser comes back signed in, and is given the response

A page whose form posts the signed SAMLResponse, and the service provider's RelayState, to the assertion consumer service.

Browser → Authorization Server

GET /saml/sso/callback?samlStateId={saml_state_id_1} HTTP/1.1
Cookie: .AspNetCore.Antiforgery.{suffix}={antiforgery_cookie_1}; idsrv={authentication_cookie_1}

Authorization Server → Browser

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Cache-Control: no-cache,no-store
Pragma: no-cache
Set-Cookie: idsrv={authentication_cookie_2}; path=/; samesite=none; httponly

(an HTML page, not shown, that posts this form to https://sp.demo.example.com/acs as it loads:)
SAMLResponse={saml_response_1}
RelayState=relay-7d1c

{saml_response_1} decoded (Base64, as the POST binding sends it):

<samlp:Response xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{saml_id_2}" Version="2.0" IssueInstant="{timestamp}" InResponseTo="_documented-authn-request" Destination="https://sp.demo.example.com/acs" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
  <saml:Issuer>https://localhost:5001/saml</saml:Issuer>
  <samlp:Status>
    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
  </samlp:Status>
  <saml:Assertion ID="{saml_id_3}" Version="2.0" IssueInstant="{timestamp}">
    <saml:Issuer>https://localhost:5001/saml</saml:Issuer>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
      <SignedInfo>
        <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
        <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
        <Reference URI="#{saml_id_3}">
          <Transforms>
            <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
            <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
          </Transforms>
          <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
          <DigestValue>{digest_1}</DigestValue>
        </Reference>
      </SignedInfo>
      <SignatureValue>{signature_1}</SignatureValue>
      <KeyInfo>
        <X509Data>
          <X509Certificate>{certificate_1}</X509Certificate>
        </X509Data>
      </KeyInfo>
    </Signature>
    <saml:Subject>
      <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">alice</saml:NameID>
      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData InResponseTo="_documented-authn-request" Recipient="https://sp.demo.example.com/acs" NotOnOrAfter="{timestamp}" />
      </saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Conditions NotBefore="{timestamp}" NotOnOrAfter="{timestamp}">
      <saml:AudienceRestriction>
        <saml:Audience>https://sp.demo.example.com/saml</saml:Audience>
      </saml:AudienceRestriction>
    </saml:Conditions>
    <saml:AuthnStatement AuthnInstant="{timestamp}" SessionIndex="{saml_session_index_1}">
      <saml:AuthnContext>
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
      </saml:AuthnContext>
    </saml:AuthnStatement>
    <saml:AttributeStatement>
      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
        <saml:AttributeValue>Alice Smith</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
        <saml:AttributeValue>alice@example.com</saml:AttributeValue>
      </saml:Attribute>
    </saml:AttributeStatement>
  </saml:Assertion>
</samlp:Response>

The browser posts the form to the service provider, which checks the signature against the metadata's certificate and signs the user in.