Unable to access claims or saml attributes via Kentor.AuthServices.Owin in MVC

927 Views Asked by At

I am building an .net 4.5 MVC application which connects to a Shibboleth-based SAML IdP, to provide single-sign-on functionality. To do this I am using the Kentor.AuthServices.Owin middleware.

The IdP service in question requires use of encrypted assertions, which the latest build of Kentor.AuthServices doesn't support. Instead I had to use the Raschmann-fork of it here https://github.com/Raschmann/authservices/tree/78EncryptedAssertion (v0.8.1), then tried ..Raschmann/authservices/tree/Release (v0.10.1).

(Using ..Raschmann/authservices/tree/master (v0.12.1) - or indeed any of the KentorIT Kentor.AuthServices builds - results in loginInfo being null within ExternalLoginCallback.)

Using the above allows me to login/register on the application via the IdP. However, when ExternalLoginCallback is called, the ExternalClaims or Claims objects within loginInfo.ExternalIdentity don't contain any claims.

I have captured and decrypted the SAML response from the IdP and have confirmed that it is sending information (such as firstname, lastname, DoB, etc.) back to my application once I have logged in.

How can I access the SAML data that is being returned?

ConfigureAuth within Startup.Auth.vb:

Public Sub ConfigureAuth(app As IAppBuilder)
        ' Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(AddressOf ApplicationDbContext.Create)
        app.CreatePerOwinContext(Of ApplicationUserManager)(AddressOf ApplicationUserManager.Create)
        app.CreatePerOwinContext(Of ApplicationSignInManager)(AddressOf ApplicationSignInManager.Create)

        app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
            .AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            .Provider = New CookieAuthenticationProvider() With {
                .OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUser)(
                    validateInterval:=TimeSpan.FromMinutes(30),
                    regenerateIdentity:=Function(manager, user) user.GenerateUserIdentityAsync(manager))},
            .LoginPath = New PathString("/Account/Login")})



app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie)
            app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5))
            app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie)

            app.UseKentorAuthServicesAuthentication(New KentorAuthServicesAuthenticationOptions(True))
            AntiForgeryConfig.UniqueClaimTypeIdentifier = Global.System.IdentityModel.Claims.ClaimTypes.NameIdentifier

 End Sub

ExternalLoginCallback within AccountController.vb:

<AllowAnonymous>
    Public Async Function ExternalLoginCallback(returnUrl As String) As Task(Of ActionResult)

        Dim loginInfo = Await AuthenticationManager.GetExternalLoginInfoAsync()

        If loginInfo Is Nothing Then
            Return RedirectToAction("Login")
        End If

        Dim externalIdentity = Await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie)

        ' Sign in the user with this external login provider if the user already has a login
        Dim result = Await SignInManager.ExternalSignInAsync(loginInfo, isPersistent:=False)
        Select Case result
            Case SignInStatus.Success
                Dim user = Await UserManager.FindAsync(loginInfo.Login)
                If user IsNot Nothing Then
                    'user.FirstName = loginInfo.ExternalIdentity.FindFirst(ClaimTypes.Name).Value
                    'user.Email = loginInfo.ExternalIdentity.FindFirst(ClaimTypes.Email).Value
                    Await UserManager.UpdateAsync(user)
                End If
                Return RedirectToLocal(returnUrl)
            Case SignInStatus.LockedOut
                Return View("Lockout")
            Case SignInStatus.RequiresVerification
                Return RedirectToAction("SendCode", New With {
                    .ReturnUrl = returnUrl,
                    .RememberMe = False
                })
            Case Else
                ' If the user does not have an account, then prompt the user to create an account
                ViewBag.ReturnUrl = returnUrl
                ViewBag.LoginProvider = loginInfo.Login.LoginProvider
                Return View("ExternalLoginConfirmation", New ExternalLoginConfirmationViewModel() With {
                    .Email = loginInfo.Email
                })
        End Select
    End Function
1

There are 1 best solutions below

3
On

The owin pipeline is quite complex. To debug this I'd suggest that you insert a small breakpoint middleware immediately before the UseKentorAuthServicesAuthentication() call.

app.Use(async (context, next) =>
{
  await next.Invoke();
});

Sorry for using C#, but I assume you can find the equivalent VB syntax.

Run the application and authenticate. Right before you trigger the Idp to post the response back, put a breakpoint on the closing bracket of the code snippet above. Then investigate the content of the context.Authentication.AuthenticationResponseGrant. That's the actual output form Kentor.AuthServices. Are the claims present there?

If they're not, then there's a bug in AuthServices. Please report it as an issue on the GitHub Issue tracker and I'll have a look.

If the claims are indeed present at that point, but lost later, you may be a victim of the Owin Cookie Monster.