asp.net MVC authentication with Shibboleth
Asked Answered
L

1

6

Shibboleth is a SSO Authentication that is added to IIS as a "plugin". After a user has done a Login there are Headers showing the Shibboleth Session: ShibSessionID ShibIdentityProvider eppn affiliation entitlement unscopedaffiliation ...more

So i can extract username and roles from the Headers. so far so fine.

Question: How can I implement a handler that does read the headers and set the status that a user is authorized? Idea is to use the [Authorize] Attribute and the Method Roles.IsUserInRole. All from the Headers, no Database, no User Management.

Update

Implementation According to the Answer from @Pharylon

In this Update there is nothing new, just a help for the copy&past friends. Of course you have to adjust the properties and Header fieldnames according to your Shibboleth Setup.

File: ShibbolethPrincipal.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal; //GenericPrincipal

namespace Shibboleth
{
    public class ShibbolethPrincipal : GenericPrincipal
    {
        public string username
        {
            get { return this.Identity.Name.Replace("@ksz.ch", ""); }
        }

        public string firstname
        {
            get { return HttpContext.Current.Request.Headers["givenName"]; }
        }

        public string lastname
        {
            get { return HttpContext.Current.Request.Headers["surname"]; }
        }

        public string phone
        {
            get { return HttpContext.Current.Request.Headers["telephoneNumber"]; }
        }

        public string mobile
        {
            get { return HttpContext.Current.Request.Headers["mobile"]; }
        }

        public string entitlement
        {
            get { return HttpContext.Current.Request.Headers["eduzgEntitlement"]; }            
        }

        public string homeOrganization
        {
            get { return HttpContext.Current.Request.Headers["homeOrganization"]; }            
        }

        public DateTime birthday
        {
            get
            {
                DateTime dtHappy = DateTime.MinValue;
                try
                {
                    dtHappy = DateTime.Parse(HttpContext.Current.Request.Headers["dateOfBirth"]);
                }
                finally
                {                    
                    
                }

                return dtHappy;
            }
            set {}
        }
        
        public ShibbolethPrincipal()
            : base(new GenericIdentity(GetUserIdentityFromHeaders()), GetRolesFromHeader())
        {
        }

        public static string GetUserIdentityFromHeaders()
        {            
            //return HttpContext.Current.Request.Headers["eppn"];            
            return HttpContext.Current.Request.Headers["principalName"];                        
        }

        public static string[] GetRolesFromHeader()
        {
            string[] roles = null;
            //string rolesheader = HttpContext.Current.Request.Headers["affiliation"];
            string rolesheader = HttpContext.Current.Request.Headers["eduzgEntitlement"];
            if (rolesheader != null)
            {
                roles = rolesheader.Split(';');
            }
            return roles; 
        }
    }
}

File: ShibbolethController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Shibboleth
{
    public class ShibbolethController : Controller
    {
        protected new ShibbolethPrincipal User
        {
            get
            {
                return (base.User as ShibbolethPrincipal) ?? null; //CustomPrincipal.GetUnauthorizedPrincipal();
            }
        }
    }
}

File: Global.asax

void Application_PostAuthenticateRequest(object sender, EventArgs e)
        {
            var ctx = HttpContext.Current;

            var principal = new ShibbolethPrincipal();
            HttpContext.Current.User = principal;            
        }

Using examples:

 namespace itservices.Controllers
    {
        [Authorize] //examples : [Authorize(Roles="Administrators")], [Authorize(Users="Alice,Bob")]
        public class PasswordMailController : ShibbolethController
        {

  

    if(User.IsInRole("staff"))
    {
Lindon answered 25/2, 2015 at 11:35 Comment(5)
Did you try to create an action filter and do this handling in the OnActionExecuting method ? msdn.microsoft.com/en-us/library/dd381609%28v=vs.100%29.aspxGoodsell
I think it's better to set the Principal/Identity at the beginning of processing the Request so the normal Authorization filters and methods work.Mesothorium
@RazvanDumitru - ActionFilters execute AFTER Authorization filters, they would be of no useBlazon
Glad I could help. I'd suggest, though, that ShibbolethController's User shouldn't ever return a null object. You don't want User.Identity.IsAuthenticated to throw an exception - it should be a reliable way to check if the User is valid. Also, you should set the values of the Principal at instantiation (I like making it an immutable object). Otherwise, you're going to have a bad time with unit testing if you have it calling HttpContext.Current constantly.Mesothorium
Hello Pharylon. Jup, that makes sense. I'm changing for UnauthorizedPrincipal as well. ThanksLindon
M
7

You'll want to create a method in Global.asax.cs that has the following signature

protected void Application_PostAuthenticateRequest()
{
    //Your code here.
}

This will be called automatically before almost anything else is done (MVC will call this method if it exists, you don't have to "turn it on" anywhere), and this is where you need to set the Principal. For instance, let's assume you have a header called RolesHeader that has a comma separated value of roles and another header called UserId that has (duh) the user ID.

Your code, without any error handling, might look something like:

protected void Application_PostAuthenticateRequest()
{
    var rolesheader = Context.Request.Headers["RolesHeader"];
    var userId = Context.Request.Headers["UserId"];
    var roles = rolesheader.Split(',');
    var principal = new GenericPrincipal(new GenericIdentity(userId), roles);
    Context.User = principal;
}

It's the Principal/Identity that the [Authorize] attribute uses, so setting it here at the beginning of the request lifecycle means the [Authorize] attribute will work correctly.

The rest of this is optional, but I recommend it:

I like to create my own custom classes that implement IPrincipal and IIdentity instead of using the GenericPrincipal and GenericIdentity, so I can stuff more user information in it. My custom Principal and Identity objects then have much more rich information, such as branch numbers or email addresses or whatever.

Then, I create a Controller called BaseController that has the following

protected new CustomPrincipal User
{
    get
    {
        return (base.User as CustomPrincipal) ?? CustomPrincipal.GetUnauthorizedPrincipal();
    }
}

This allows me to access all my rich, custom Principal data instead of just what's defined in IPrincipal. All of my real controllers then inherit from BaseController instead of directly from Controller.

Obviously, when using a custom Principal like this, in the Application_PostAuthenticateRequest() method, you'd set the Context.User to be your CustomPrincipal instead of a GenericPrincipal.

Mesothorium answered 25/2, 2015 at 16:55 Comment(1)
Brilliant! This is much easier than i expected. I was afraid that i have to implement a ExtendetMembershibProvider and RoleProvider. Thank you very muchLindon

© 2022 - 2024 — McMap. All rights reserved.