Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to create custom Identity with WIF in a MVC application?

Tags:

wif

Prior to WIF, Application_PostAcquireRequestState was a good place to create a custom Identity, but required a lot of framework to make sure each type of authentication you were doing was mapped appropriately. By custom Identity, I mean a class inheriting from Identity such as the below SomeIdentity, so that we could have a specific property we might require all authenticated users to have.

PostAcquirerequestState is still available, but there's alot of new ways you can hook into authentication with. Additionally, old methods become complex when supporting multiple authentication methods.

I'd like to know if there's a better way than the below to accomplish this now in WIF. Primarily I'd like to separate out the code that handles mapping claims to the Identity. The idea being that code would be different for other authentication types/providers, as the way it retrieves that property value may not be from a claim such as with SAML, but from somewhere else for other types of authentication methods. I am using Kentor.AuthServices for SAML support currently. While there might be different code for mapping those values depending on the provider, the end result would be that a SomeIdentity instance had been created and it's SomeProperty and other properties would be set. This way the rest of the application can always depend/assume those have been handled.

My MVC project came with a AccountController that had a ExternalLoginCallback which the name implied might be a good hook for when an external authentication completed(which to me SAML is an "external" authentication). However, it does not seem to be hit at any point during/after a SAML authentication.

It may be the answer is that we still need to piece this together ourselves the old way, but I was hoping WIF had some better framework hooks to make this easier.

public sealed class SomeIdentity : Identity
{
  ...
  // Some custom properties
  public string SomeProperty { get;set;}
}

protected void Application_PostAcquireRequestState(object sender, EventArgs e)
{
  ...
  identity = new SomeIdentity(id, userId);
  // map a claim to a specific property
  identity.SomeProperty = ...Claims[IdpSomePropertyKey];
  ///...

  GenericPrincipal newPrincipal = new GenericPrincipal(identity , null);
  HttpContext.Current.User = newPrincipal;
  System.Threading.Thread.CurrentPrincipal = newPrincipal;
}

Now that I'm using WIF, where should I be putting code that is specific to a particular authentication type(i.e. Kentor.AuthServices SAML) which creates a custom SomeIdentity?

The idea being that SomeIdentity would be the identity class used everywhere in my application, but the code to populate it's properties will need to be written specifically for each authentication type, such as with SAML to pull claims and use their values to set the proeprties. I.e. it is where the mapping occurs.

like image 552
AaronLS Avatar asked Feb 23 '16 22:02

AaronLS


1 Answers

Ok I'm going to take a stab at this, and hopefully I'm understanding your question and not making too many assumptions about your code. The assumptions I am going to make is this is a brand new MVC app created using Visual Studio ASP.NET 4.6 Template with the "No Authentication" option (judging by what I understand about your situation, you aren't looking to store this data as much as just read it in from the claims).

Add a class called Started to the root of your project

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(WebApplication1.Startup))]

namespace WebApplication1
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }
    }
}

Now this is going to run, as you probably can guess, on "startup" just like a global.asax.cs file does.

Now we need to actually make the ConfigureAuth() method. This is usually done in the App_Start folder with a file called "Startup.Auth.cs". This file won't currently exist, so go ahead and create it with this template

using Owin;
using Kentor.AuthServices.Owin;

namespace WebApplication1
{
    public partial class Startup
    {
        private void ConfigureAuth(IAppBuilder app)
        {
        }
    }
}

This is where we are going to do our authentication logic / settings. OWIN comes with quite a few out of the box, authentication strategies, and there are some libraries with even more. I recommend taking a look at OwinOAuthProviders if you are going to be writing your own.

Go ahead and install the NuGet package Kentor.AuthServices.Owin package and dependencies. This should fix any compile errors you have at the moment as well. You'll also need to add a reference to System.IdentityModel to your project for later.

Now, in your ConfigureAuth method inside Startup.Auth.cs, you can add Kentor into your app by doing the following.

Public void ConfigureAuth(IAppBuilder app)
{
    var kentorOptions = new KentorAuthServicesAuthenticationOptions(true);
}

Now your variable kentorOptions since I passed it true, will read in your settings from your WebConfig. You could also configure them here manually though.

The part we are interested in is the kentorOptions.SPOptions.SystemIdentityModelIdentityConfiguration.ClaimsAuthenticationManager property. We want to create a custom ClaimsAuthenticationManager to provide the identity based on the incoming claims.

Make a new class that inherits from ClaimsAuthenticationManager

using System.Security.Claims;

namespace WebApplication5 {
    public class CustomClaimsAuthManager : ClaimsAuthenticationManager {
        public override ClaimsPrincipal Authenticate( string resourceName, ClaimsPrincipal incomingPrincipal ) {
            ClaimsIdentity ident = (ClaimsIdentity) incomingPrincipal.Identity;
            //Use incomingPrincipal.Identity.AuthenticationType to determine how they got auth'd
            //Use incomingPrincipal.Identity.IsAuthenticated to make sure they are authenticated.
            //Use ident.AddClaim to add a new claim to the user
             ...
            identity = new SomeIdentity( id, userId );
            // map a claim to a specific property
            identity.SomeProperty = ...Claims[IdpSomePropertyKey];
            ///...

            GenericPrincipal newPrincipal = new GenericPrincipal( identity, null );
            return newPrincipal;
        }
    }
}

That's where you have your identity code. Now finally we need to set that as the ClaimsAuthenticationManager to actually use, and tell your app to use Kentor in the OWIN pipeline. This is all back in the Startup.Auth.cs file.

private void ConfigureAuth( IAppBuilder app ) {
            var kentorOptions = new KentorAuthServicesAuthenticationOptions(true);
            kentorOptions.SPOptions.SystemIdentityModelIdentityConfiguration.ClaimsAuthenticationManager = new WebApplication5.CustomClaimsAuthManager();
            app.UseKentorAuthServicesAuthentication( kentorOptions );
        }

Annnndd that should hopefully do it! For other Auth providers, like the ones from OwinOAuthProviders, you can also manipulate things by overriding the options.Provider methods to do things on different events, like so:

var cookieOptions = new CookieAuthenticationOptions {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString( "/Auth/Login" ),
                CookieName = "cooooookiees",
                ExpireTimeSpan = new TimeSpan( 10000, 0, 0, 0, 0 ),
                Provider = new CookieAuthenticationProvider {
                    OnException = context => {
                        var x = context;
                    },
                    OnValidateIdentity = async context => {
                        var invalidateBySecurityStamp = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes( 15 ),
                        regenerateIdentity: ( manager, user ) => user.GenerateUserIdentityAsync( manager ) );
                        await invalidateBySecurityStamp.Invoke( context );

                        if ( context.Identity == null || !context.Identity.IsAuthenticated ) {
                            return;
                        }
                        var newResponseGrant = context.OwinContext.Authentication.AuthenticationResponseGrant;
                        if ( newResponseGrant != null ) {
                            newResponseGrant.Properties.IsPersistent = true;
                        }

                    }
                }
            };
            app.UseCookieAuthentication( cookieOptions );
like image 185
Matti Price Avatar answered Jan 04 '23 06:01

Matti Price