ASP.NET Identity with Sustainsys Saml2 - How to persist ExternalLoginInfo Claims?

956 views Asked by At

I have an ASP.NET Core app, targeting netcoreapp3.1, set up with ASP.NET Identity and the Sustainsys.Saml2.AspNetCore2 package.

IDP-initiated SAML authentication is working fine, but I can't retrieve custom attributes/claims from the signed-in user after the authentication redirect.

In my ExternalLogin.cshtml.cs class, the custom claims are present on the ExternalLoginInfo.Principal (as var info in the code below), but they are not retrievable from Context.User.Claims after the redirect.

That is, _logger.LogInformation($"PatientId: {info.Principal.FindFirst("PatientId")}"); prints the value passed in the custom PatientId SAML attribute, but @Context.User.FindFirst("PatientId"); is null after the redirect.

public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            if (remoteError != null)
            {
                ErrorMessage = $"Error from external provider: {remoteError}";
                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
            }
            var info = await _CustomSignInManager.GetExternalLoginInfoAsync();
            if (info == null)
            {
                ErrorMessage = "Error loading external login information.";
                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
            }

            // Sign in the user with this external login provider if the user already has a login.
            var result = await _CustomSignInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
            if (result.Succeeded)
            {
                _logger.LogInformation($"{info.Principal.Identity.Name} logged in with {info.LoginProvider} provider.");
                _logger.LogInformation($"PatientId: {info.Principal.FindFirst("PatientId")}");
                return LocalRedirect(returnUrl);
            }

    ...
}

My conclusion from this answer is that these claims should still be available. Do I need to somehow pass the ExternalLoginInfo.Principal (rather just the LoginProvider and ProviderKey) to the ExternalLoginSignInAsync method?

I will say that I only want to persist the claims for the length of the session, not add them as AspNetUserClaims the database. They will be different on each login.

1

There are 1 answers

0
ChiefMcFrank On

I figured out something that works. My question at the end there, "Do I need to somehow pass the ExternalLoginInfo.Principal (rather just the LoginProvider and ProviderKey) to the ExternalLoginSignInAsync method?" was on the right track.

As you can see above, the ExternalLoginSignInAsync method is in the CustomSignInManager class (it's a custom implementation for reasons not relevant to this question, but that method was unchanged from the default ASP.NET Identity SignInManager).

Tracing the request path to the "bottom," ExternalLoginSignInAsync calls SignInOrTwoFactorAsync, which calls SignInAsync. In SignInAsync, a new ClaimsIdentity is created and returned. So I simply had to pass them from the ExternalLoginInfo.Principal instance through all of those methods such that I could set them on the new principal.

Potentially helpful minutiae: I chose to create a CustomClaimSet class to store the custom attributes, and then I had to update all the method signatures to accept a CustomClaimSet as an argument.