8

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.

sksallaj
  • 3,872
  • 3
  • 37
  • 58

2 Answers2

10

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;
}
Brett
  • 484
  • 6
  • 16
  • Wow that helped me out a lot! Thanks!! – sksallaj May 20 '14 at 14:25
  • 1
    Hi Brett, can you give me an example of the TransformClaims function? I'm trying to migrate my security infrastructure to owin/katana but i'm still a little lost with it. I wanted to bring all user claims from my DB right after the authentication and put all the claims in a cached cookie for one hour. Do you have anything like that? Thanks a lot!! – snekkke Mar 19 '15 at 15:35
  • Brett, thanks for posting the code, it's very helpful. Does the claims transformation can be cached for certain amount of time in a cookie and after that time has passed the claims transformation runs again on a request, getting the permissions from the DB again? Thanks!!!! – snekkke Mar 20 '15 at 20:21
  • I'm not 100% sure but I believe that will be a function of the token timeout settings in ADFS. Once you're initially authenticated, the claims you transform or add will follow you around on refreshes until the auth token expires, at which point on the next refresh the site will sense that your token is invalid and you'll re-authenticate then hit the OnResponseSignIn event again. – Brett Mar 21 '15 at 20:58
  • I'll give it a try Brett. Also, another thing i was thinking of.....is it possible to have a specific cache time for the authentication cookie and a different cache time for the claims i get from the DB? I mean one cookie for authentication and another for authorization. Is it possible? Another thing, do you know where i can get some documentation for reading? i've been searching but there's nothing official. Thanks Brett! – snekkke Mar 23 '15 at 02:12
  • Any chance you can provide a link to a test solution that we can take a look at ? – cmedine Apr 07 '15 at 16:46
  • There wouldn't be much to see, and to experience the login you'd have to have an account in a linked AD/ADFS. The UX experience is nearly identical to what you get with the default Identity 2.0 starter solution. oAuth to FB, Twitter, etc., with just another button for AD. – Brett Apr 08 '15 at 17:40
  • Thanks Brett - I am new to federated authentication with ADFS and I am trying to figure out how to setup a web app (MVC) that can communicate with a separate WebApi using ADFS. I can't seem to find a good example, everything I have found is using Azure and I need onpremise ADFS. Do you have any advice or links that may help me? – cmedine Apr 09 '15 at 20:36
  • 1
    Yeah, that's tricky. Check out http://stackoverflow.com/questions/17797698/pass-adfs-token-to-a-service. – Brett Apr 09 '15 at 22:28
1

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!!

snekkke
  • 441
  • 1
  • 5
  • 15