1

I'm trying to implement single logout using redirect in my ASP.Core 3.1.10 aplication. I've used code from https://www.itfoxtec.com/identitysaml2. Logout POST request result is 303
(User.Identity.IsAuthenticated becomes false), but the next GET request result is 400, redirection is not happened (LoggedOut, SingleLogout methods are not executed) . Could you please help to solve this issue?

This is a failed GET SAMLRequest:

<saml2p:LogoutRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_40c55084-a748-42be-ba8f-c5bb70010109" Version="2.0" IssueInstant="2021-07-12T20:55:39.5289450Z" Destination="http://myserver:5000/auth/realms/mycompany/protocol/saml" NotOnOrAfter="2021-07-12T21:05:39.5289589Z"><saml2:Issuer>mycompany-dev</saml2:Issuer><saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">user1234</saml2:NameID><saml2p:SessionIndex>c8c8b602-34d0-4eb1-aed4-71ae7a06a4b5::0e88e5c4-ce48-44a4-916c-5587a7fdb636</saml2p:SessionIndex></saml2p:LogoutRequest>

SAML2 configuration:

 saml2Configuration.SignAuthnRequest = true;
        saml2Configuration.SigningCertificate = CertificateUtil.Load(AppEnvironment.MapToPhysicalFilePath(Configuration["Saml2:SigningCertificateFile"]), Configuration["Saml2:SigningCertificatePassword"]);

        saml2Configuration.AllowedAudienceUris.Add(saml2Configuration.Issuer);

        var entityDescriptor = new EntityDescriptor();
        entityDescriptor.ReadIdPSsoDescriptorFromFile(Configuration["Saml2:IdPMetadataFile"]);
        if (entityDescriptor.IdPSsoDescriptor != null)
        {
            saml2Configuration.AllowedIssuer = entityDescriptor.EntityId;
            saml2Configuration.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.First().Location;
            if (AppEnvironment.IsDevelopment() || AppEnvironment.IsStaging()) // todo test
            {
                saml2Configuration.SingleLogoutDestination = entityDescriptor.IdPSsoDescriptor.SingleLogoutServices.First().Location;
            }
            saml2Configuration.SignatureValidationCertificates.AddRange(entityDescriptor.IdPSsoDescriptor.SigningCertificates);
        }
        else
        {
            throw new Exception("IdPSsoDescriptor not loaded from metadata.");
        }

AuthController Login:

[Route("Login")]
    [HttpGet]
    public IActionResult Login(string returnUrl = null)
    {
        var binding = new Saml2RedirectBinding();
        binding.SetRelayStateQuery(new Dictionary<string, string> { { relayStateReturnUrlKey, returnUrl ?? Url.Content("~/") } });

        var assertionConsumerServiceUrl = new Uri(GetBaseUri(false, false), Url.Content("~/Auth/AssertionConsumerService"));

        return binding.Bind(new Saml2AuthnRequest(config)
        {
            //Subject = new Subject { NameID = new NameID { ID = "username" } },
            AssertionConsumerServiceUrl = Startup.AppEnvironment.IsDevelopment() ? assertionConsumerServiceUrl : null
        }).ToActionResult();
    }enter code here

AssertionConsumerService:

 [Route("AssertionConsumerService")]
    [HttpPost]
    public async Task<IActionResult> AssertionConsumerService()
    {
        var binding = new Saml2PostBinding();
        var saml2AuthnResponse = new Saml2AuthnResponse(config);

        binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
        if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
        {
            throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
        }
        binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
        ClaimsTransform.InitUserService(_userService);
        await saml2AuthnResponse.CreateSession(HttpContext, new TimeSpan(24,0,0), true, claimsTransform: (claimsPrincipal) => ClaimsTransform.Transform(claimsPrincipal).Result);

        var relayStateQuery = binding.GetRelayStateQuery();
        var returnUrl = relayStateQuery.ContainsKey(relayStateReturnUrlKey) ? relayStateQuery[relayStateReturnUrlKey] : Url.Content("~/");
        return Redirect(returnUrl);
    }

Logout:

[HttpPost("Logout")]       
    public async Task<IActionResult> Logout()
    {
        if (!User.Identity.IsAuthenticated)
        {
            return Redirect(Url.Content("~/"));
        }

        var binding = new Saml2RedirectBinding();
        var saml2LogoutRequest = await new Saml2LogoutRequest(config, User).DeleteSession(HttpContext);
        return binding.Bind(saml2LogoutRequest).ToActionResult();
    } 

LoggedOut:

 [Route("LoggedOut")]       
    public IActionResult LoggedOut()
    {
        var binding = new Saml2PostBinding();
        binding.Unbind(Request.ToGenericHttpRequest(), new Saml2LogoutResponse(config));

        return Redirect(Url.Content("~/"));
    }

SingleLogout:

[Route("SingleLogout")]       
    public async Task<IActionResult> SingleLogout()
    {
        Saml2StatusCodes status;
        var requestBinding = new Saml2RedirectBinding();
        var logoutRequest = new Saml2LogoutRequest(config, User);
        try
        {
            requestBinding.Unbind(Request.ToGenericHttpRequest(), logoutRequest);
            status = Saml2StatusCodes.Success;
            await logoutRequest.DeleteSession(HttpContext);
        }
        catch (Exception exc)
        {               
            Debug.WriteLine("SingleLogout error: " + exc.ToString());
            status = Saml2StatusCodes.RequestDenied;
        }

        var responsebinding = new Saml2RedirectBinding();
        responsebinding.RelayState = requestBinding.RelayState;
        var saml2LogoutResponse = new Saml2LogoutResponse(config)
        {
            InResponseToAsString = logoutRequest.IdAsString, 
            Status = status,
        };
        return responsebinding.Bind(saml2LogoutResponse).ToActionResult();
    }

1 Answers1

0

Maybe this is a cookie issue. Where the .NET auth cookie is not send to the server by the browser when you do redirect between domains. You can check if the .NET auth cookie is send by tracing (Chrome F12) the calls in the browser.

Maybe this can help .NetCore Authentication cookie not persistent across all request [Inermittent issue]

Anders Revsgaard
  • 3,636
  • 1
  • 9
  • 25