Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sustainsys SAML2 Sample for ASP.NET Core WebAPI without Identity

Does anyone have a working sample for Sustainsys Saml2 library for ASP.NET Core WebAPI only project (no Mvc) and what's more important without ASP Identity? The sample provided on github strongly relies on MVC and SignInManager which I do not need nor want to use.

I added Saml2 authentication and at first it worked fine with my IdP (I also checked the StubIdP provided by Sustainsys) for first few steps so:

  • IdP metadata get properly loaded
  • My API properly redirects to sign-in page
  • Sign-in page redirects to /Saml2/Acs page, and I see in the logs that it parses the result successfully

However I don't know how to move forward from there and extract user login and additional claims (my IdP provided also an e-mail, and it is included in SAML response which I confirmed in the logs).

Following some samples found on the web and modyfing a little bit the MVC Sample from GitHub I did the following:

In Startup.cs:

...
.AddSaml2(Saml2Defaults.Scheme,
                       options =>
                       {
                           options.SPOptions.EntityId = new EntityId("...");
                           options.SPOptions.ServiceCertificates.Add(...));
                           options.SPOptions.Logger = new SerilogSaml2Adapter();
                           options.SPOptions.ReturnUrl = new Uri(Culture.Invariant($"https://localhost:44364/Account/Callback?returnUrl=%2F"));

                           var idp =
                               new IdentityProvider(new EntityId("..."), options.SPOptions)
                               {
                                   LoadMetadata = true,
                                   AllowUnsolicitedAuthnResponse = true, // At first /Saml2/Acs page throwed an exception that response was unsolicited so I set it to true
                                   MetadataLocation = "...",
                                   SingleSignOnServiceUrl = new Uri("...") // I need to set it explicitly because my IdP returns different url in the metadata
                               };
                           options.IdentityProviders.Add(idp);
                       });

In AccountContoller.cs (I tried to follow a somewhat similar situation described at how to implement google login in .net core without an entityframework provider):

[Route("[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
    private readonly ILog _log;

    public AccountController(ILog log)
    {
        _log = log;
    }

    [HttpGet("Login")]
    [AllowAnonymous]
    public IActionResult Login(string returnUrl)
    {
        return new ChallengeResult(
            Saml2Defaults.Scheme,
            new AuthenticationProperties
            {
                // It looks like this parameter is ignored, so I set ReturnUrl in Startup.cs
                RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
            });
    }

    [HttpGet("Callback")]
    [AllowAnonymous]
    public async Task<IActionResult> LoginCallback(string returnUrl)
    {

        var authenticateResult = await HttpContext.AuthenticateAsync(Constants.Auth.Schema.External);

        _log.Information("Authenticate result: {@authenticateResult}", authenticateResult);

// I get false here and no information on claims etc.
        if (!authenticateResult.Succeeded)
        {
            return Unauthorized();
        }

// HttpContext.User does not contain any data either


// code below is not executed
        var claimsIdentity = new ClaimsIdentity(Constants.Auth.Schema.Application);
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));

        _log.Information("Logged in user with following claims: {@Claims}", authenticateResult.Principal.Claims);           

        await HttpContext.SignInAsync(Constants.Auth.Schema.Application, new ClaimsPrincipal(claimsIdentity));

        return LocalRedirect(returnUrl);
    }

TLDR: Configuration for SAML in my ASP.NET Core WebApi project looks fine, and I get success response with proper claims which I checked in the logs. I do not know how to extract this data (either return url is wrong or my callback method should work differently). Also, it is puzzling why successfuly redirect from SSO Sign-In page is treated as "unsolicited", maybe this is the problem?

Thanks for any assistance

like image 343
LizardErrtu Avatar asked Mar 06 '19 14:03

LizardErrtu


2 Answers

For anyone who still needs assistance on this issue, I pushed a full working example to github which uses a .Net Core WebAPI for backend and an Angular client using the WebAPI. you can find the example from here:

https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample

like image 197
antistar Avatar answered Nov 14 '22 08:11

antistar


As it turned out, the various errors I've been getting were due to my solution being hosted inside docker container. This caused a little malfunction in internal aspnet keychain. More details can be found here (docker is mentioned almost at the end of the article):

https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?tabs=aspnetcore2x&view=aspnetcore-2.2

Long story short, for the code to be working I had to add only these lines:

services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo("/some/volume/outside/docker")); // it needs to be outside container, even better if it's in redis or other common resource

It fixed everything, which includes:

  • Sign-in action to external cookie
  • Unsolicited SSO calls
  • Exceptions with data protection key chain

So it was very difficult to find, since exceptions thrown by the code didn't point out what's going on (and the unsolicited SSO calls made me think that the SSO provider was wrongly configured). It was only when I disassembled the Saml2 package and tried various code pieces one by one I finally encoutered proper exception (about the key chain) which in turned led me to an article about aspnet data protection.

I provide this answer so that maybe it will help someone, and I added docker tag for proper audience.

like image 29
LizardErrtu Avatar answered Nov 14 '22 06:11

LizardErrtu