Search code examples
asp.net-coresaml-2.0component-space

IdP Proxy - SLO SP initiated


I'm trying to construct an IdP-Proxy based on component-space SAML solution.

Till now i was able to handle single sign on in the following manner:

  1. Add a SingleSignOnService method that's receiving the AUTHN requrest from the SP and initiates a SSO to a partner IdP (SP-initiated SSO).
  2. Add an AssertionConsumerService that's receiving a SSO result and cheks IsInResponseTo flag. Based on this flag i identified if I'm in SP-initiated SSO or in IdP-initiated SSO flow and finalized the flow accordingly.

I'm trying to handle single log out in the same manner using the following example flow: Single SAMLv2 Identity Provider Proxy Logout

In theory, for a SP-initiated log out, i need to achieve the following: 1. Receive a single logout request 2. Check if it's not a response 3. Identify IdP 4. Send a Slo request to IdP identified at step 3 5. Respond to the SP-initiated SLO indicating successful logout.

public async Task<IActionResult> SingleLogoutService()
{
    // Receive the single logout request or response.
    // If a request is received then single logout is being initiated by a partner service provider.
    // If a response is received then this is in response to single logout having been initiated by the identity provider.
    var sloResult = await _samlIdentityProvider.ReceiveSloAsync();

    if (sloResult.IsResponse)
    {     
    }
    else
    {
      // Figure out IdP Partner Name 
      var idpPartnerName = _configuration["IdPPartnerName"];

      // Send logout request to idp partner
      await _samlServiceProvider.InitiateSloAsync(idpPartnerName, sloResult.LogoutReason, sloResult.RelayState);

      // Respond to the SP-initiated SLO request indicating successful logout.
      await _samlIdentityProvider.SendSloAsync();
    }

    return new EmptyResult();
  }

I'm able to destroy the session at SP's end, but I'm not able to remove the IdP session (i'm thinking that

await _samlServiceProvider.InitiateSloAsync(idpPartnerName, sloResult.LogoutReason, sloResult.RelayState);

needs to trigger IdP session removal, being the 3rd step in attached proxy process).

Is something that I'm missing "protocol-wise"?

Annex (InitiateSloAsync method):

public async Task InitiateSloAsync(string partnerName, string logoutReason, string relayState)
    {
      int num;
      if ((uint) num <= 5U)
        ;
      try
      {
        this.logger.LogDebug("Initiating SLO to the partner identity provider" + (string.IsNullOrEmpty(partnerName) ? "." : string.Format(" {0}.", (object) partnerName)), Array.Empty<object>());
        await this.LoadSamlStateAsync();
        this.LogSessionState();
        await this.GetLocalSpConfigurationAsync();
        if (this.SamlState.ServiceProviderSessionState.PendingResponseState != null)
          this.logger.LogDebug(string.Format("The pending SAML action {0} is being overridden.", (object) this.SamlState.ServiceProviderSessionState.PendingResponseState.Action), Array.Empty<object>());
        if (string.IsNullOrEmpty(partnerName) && this.SamlState.ServiceProviderSessionState.SsoSessions.Count == 1)
        {
          IEnumerator<SsoSessionState> enumerator = this.SamlState.ServiceProviderSessionState.SsoSessions.Values.GetEnumerator();
          enumerator.MoveNext();
          partnerName = enumerator.Current.PartnerName;
          enumerator = (IEnumerator<SsoSessionState>) null;
        }
        await this.GetPartnerIdpConfigurationAsync(partnerName);
        if (this.partnerIdentityProviderConfiguration.DisableOutboundLogout)
          throw new SamlProtocolException(string.Format("Logout to the partner identity provider {0} is disabled.", (object) partnerName));
        XmlElement xmlElement = await this.CreateLogoutRequestAsync(logoutReason);
        XmlElement logoutRequestElement = xmlElement;
        xmlElement = (XmlElement) null;
        await this.SendLogoutRequestAsync(logoutRequestElement, relayState);
        this.SamlState.ServiceProviderSessionState.SsoSessions.Remove(this.partnerIdentityProviderConfiguration.Name);
        SamlSubject.OnLogoutRequestSent(partnerName, logoutRequestElement, relayState);
        await this.SaveSamlStateAsync();
        this.LogSessionState();
        this.logger.LogDebug(string.Format("Initiation of SLO to the partner identity provider {0} has completed successfully.", (object) partnerName), Array.Empty<object>());
        logoutRequestElement = (XmlElement) null;
      }
      catch (Exception ex)
      {
        this.logger.LogError((EventId) 101, ex, string.Format("Initiation of SLO to the partner identity provider {0} has failed.", (object) partnerName), Array.Empty<object>());
        throw;
      }
    }

Solution

  • As per ComponentSpace response listed here: https://www.componentspace.com/Forums/8806/?Update=1#bm8813 the problem is related to not waiting a response from IdP.

    As per current implementation InitiateSloAsync will only send a SLO request to IdP, but won't wait for a response.

    // Send logout request to idp partner
    await _samlServiceProvider.InitiateSloAsync(idpPartnerName, sloResult.LogoutReason, sloResult.RelayState);
    
    // Respond to the SP-initiated SLO request indicating successful logout.
    await _samlIdentityProvider.SendSloAsync();
    

    The process is the following:

    1. Receive logout request from SP.
    2. Identify IdP.
    3. Send logout request to IdP.
    4. Receive logout response from IdP.
    5. Send logout response to SP.

    Important: It might make sense to have different single logout service endpoints when acting as the identity provider versus service provider.

    When you act as an IdP:

    public async Task<IActionResult> SingleLogoutService()
    {
      // Receive the single logout request or response.
      // If a request is received then single logout is being initiated by a partner service provider.
      // If a response is received then this is in response to single logout having been initiated by the identity provider.
      var sloResult = await _samlIdentityProvider.ReceiveSloAsync();
    
      if (sloResult.IsResponse)
      {   
      }
      else
      {
        // Figure out IdP Partner Name 
        var idpPartnerName = _configuration["IdPPartnerName"];
    
        // Send logout request to idp partner
        await _samlServiceProvider.InitiateSloAsync(idpPartnerName, sloResult.LogoutReason, sloResult.RelayState);
      }
    
      return new EmptyResult();
    }
    

    When you act as a SP:

    public async Task<IActionResult> SingleLogoutService()
    {
      // Receive the single logout request or response.
      // If a request is received then single logout is being initiated by the identity provider.
      // If a response is received then this is in response to single logout having been initiated by the service provider.
      var sloResult = await _samlServiceProvider.ReceiveSloAsync();
    
      if (sloResult.IsResponse)
      {
        // Respond to the SP-initiated SLO request indicating successful logout.
        await _samlIdentityProvider.SendSloAsync();  
      }
      else
      {
      }
    
      return new EmptyResult();
    }
    

    P.S: Don't forget to update your SingleLogoutServiceUrl properties if you end up creating two different endpoints for handling logout.