Question

I'm having problems trying to decide on a route to take on a project I have.

I've been reading up on OWIN specs and Katana implementation within .NET. The reason why I'd like to go with the Katana route is because of the owin components associated with ADFS and Token/Cookie generation.

I have two projects, one for MVC 5 website, and one for Web API. They may rest of two separate servers in the future, but for now they are on the same.

I know I will be using IIS, so the Owin pipeline isn't necessary for me to investigate.

The requirements I have is that there will be users that will be logging in using ADFS, and other users who will be logging in using Token/Cookie generation, with Role/Membership providers. Based on who is authenticated, certain sections of my web page will be exposed. The webpage enginer is done in razor.

Does anyone have any material that I can read through to help explain a design flow I can take? Or anyone has done a project similar to what I'm going through that can add any advice? There's a lot of disparate documentations that describe specific things that I need, but not the big picture; like only talking about WebAPI and ADFS, or WebAPI and windows azure, etc etc.

My theory is to implement authentication/authorization on the MVC5 website project, authorization on the Web API (somehow communication between the two needs to exist). I then maybe create a copy of the project for ADFS and another copy for Token/cookie authentication? Or maybe I'd have to make 4 different kinds of authentications: 2 for adfs where I authenticate against the MVC5 website and Web API, and again another 2 for token/cookie generation.

Any suggestions would be helpful as I'm not very familiar with this kind of technology.

Was it helpful?

Solution

I can offer that the WsFederation option in OWIN is nice but requires cookies...and they're a different kind of cookie than local auth with cookies. ADFS 2.0/WsFederation uses AuthenticationType="Cookies", and local auth uses AuthenticationType="ApplicationCookie". They are apparently incompatible as far as I can tell. I think you'll have to use token auth for ADFS but I believe that requires ADFS 3.0 on 2012R2. For that use OWIN OAuth.

UPDATE: after working on this for a while, I've figured out how to get these two authentication types to coexist peacefully in the same web application. Using OWIN, set up to call UseCookieAuthentication TWICE, once to enable the new WsFederationAuthentication middleware, and again to enable local cookie authentication. It's not intuitive but behind the scenes, specifying different authentication types for each sets them up as different auth "engines". Here's how it looks in my Startup:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
        {
            OnResponseSignIn = ctx =>
            {
                ctx.Identity = TransformClaims(ctx, app);
            }
        }
});

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    Provider = new CookieAuthenticationProvider
    {
        OnResponseSignIn = ctx =>
        {
            ctx.Identity = TransformClaims(ctx, app);
        }
    }
});

app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
    Wtrealm = Realm,
    MetadataAddress = Metadata,
    Caption = "Active Directory",
    SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});

This is successfully allowing users to authenticate to either local SQL tables or to ADFS 2.0. The TransformClaims callout is allowing me to normalize my claims between these two providers so they are consistent.

EDIT: Here's a very rudimentary TransformClaims. You can do a lot of things inside this: get the user from your DB, setup claims for navigation, custom permissions, role collections, whatever. I just built this slimmed-down version from a much larger implementation so I've not run it but hopefully you get the idea of how to leverage the OnResponseSignIn event.

private static ClaimsIdentity TransformClaims(CookieResponseSignInContext ctx, IAppBuilder app)
{
    var ident = ctx.Identity;

    var claimEmail = ident.Claims.SingleOrDefault(c => c.Type == ClaimTypes.Email);
    var claimName = ident.Claims.SingleOrDefault(c => c.Type == ClaimTypes.Name);

    //normalize my string identifier
    var loginString = (claimEmail != null) ? claimEmail.Value : (claimName != null) ? claimName.Value : null;
    var efctx = ctx.OwinContext.Get<DBEntities>();

    var user = UserBL.GetActiveUserByEmailOrName(efctx, loginString);
    if (user == null)
    {
        //user was auth'd by ADFS but hasn't been auth'd by this app
        ident.AddClaim(new Claim(ClaimTypesCustom.Unauthorized, "true"));
        return ident;
    }

    if (ident.Claims.First().Issuer == "LOCAL AUTHORITY")
    {
        //Local
        //local already has claim type "Name"
        //local didn't have claim type "Email" - adding it
        ident.AddClaim(new Claim(ClaimTypes.Email, user.Email));
    }
    else
    {
        //ADFS
        //ADFS already has claim type "Email"
        //ADFS didn't have claim type "Name" - adding it
        ident.SetClaim(ClaimTypes.Name, user.UserName);
    }

    //now ident has "Name" and "Email", regardless of where it came from
    return ident;
}

OTHER TIPS

Cmedine, based on Brett's answer i configured my authentication and authorization. I show you the code as you requested some sample code.

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            SlidingExpiration = false,
            CookieName = "identity",
            //short time for testing only
            ExpireTimeSpan = TimeSpan.FromSeconds(120),
            Provider = new CookieAuthenticationProvider
            {
                OnResponseSignIn = ctx =>
                {
                    ctx.Identity = TransformClaims(ctx);
                }
            }
        });

        app.UseWsFederationAuthentication(
            new WsFederationAuthenticationOptions
            {
                MetadataAddress = "https://[[ADFS_Url]]/FederationMetadata/2007-06/federationmetadata.xml",
                Wtrealm = "[[realm]]",
                UseTokenLifetime = false
            }
        );

    }

    private ClaimsIdentity TransformClaims(CookieResponseSignInContext ctx)
    {
        return new IdentityCreator().CreateIdentity(ctx.Identity, [[ApplicationName]]);
    }
}

The IdentityCreator takes the ClaimsIdentity and an Application name and goes to a DB and gets the claims for that user in that application. Hope it helps!!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top