MVC IsAuthorized and AuthorizedActionLink

UPDATE 2: Improved IsAuthenticated helper method can be found here.

UPDATE: Fixed broken IsAuthenticated logic (thanks Travis).

Robert Dean published a great post last year about his Security Aware Html.ActionLink.  It allowed you to conditionally render links based on the [AuthorizeAttribute] assigned to a controller’s action methods.

Here’s an alternative implementation based on expressions, so you get type safety, intellisense and slightly simpler code 🙂  First, a helper to determine if an action is authorized:

public static class AuthorizationExtensions
{
    private static Dictionary<Expression, AuthorizeAttribute[]> expressionAuthorizers = new Dictionary<Expression, AuthorizeAttribute[]>();

    /// <summary>
    /// Determines whether the specified action is authorized.
    /// </summary>
    /// <typeparam name="TController">The type of the controller.</typeparam>
    /// <param name="helper">The helper.</param>
    /// <param name="actionMethod">The action method.</param>
    /// <returns>
    ///     <c>true</c> if the specified helper is authorized; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsAuthorized<TController>( this HtmlHelper helper, Expression<Action<TController>> action )
    {
        var call = action.Body as MethodCallExpression;

        if ( call == null ) return false;

        var authorizers = expressionAuthorizers.ContainsKey( action )
            ? expressionAuthorizers[ action ]
            : expressionAuthorizers[ action ] = GetAttributes<AuthorizeAttribute>( call );

        return ( authorizers.Length > 0 )
            ? authorizers.All( a => a.IsAuthorized( helper.ViewContext.HttpContext.User ) )
            : true;
    }

    /// <summary>
    /// Gets the specified attributes for an action method.
    /// </summary>
    /// <param name="call">The call.</param>
    /// <returns></returns>
    private static TAttribute[] GetAttributes<TAttribute>( MethodCallExpression call ) where TAttribute : Attribute
    {
        return call.Object.Type.GetCustomAttributes( typeof( TAttribute ), true )
            .Union( call.Method.GetCustomAttributes( typeof( TAttribute ), true ) )
            .Cast<TAttribute>()
            .ToArray();
    }

    /// <summary>
    /// Determines whether the specified <see cref="AuthorizeAttribute"/> authorizes the specified user.
    /// </summary>
    /// <param name="authorize">The <see cref="AuthorizeAttribute"/>.</param>
    /// <param name="user">The user.</param>
    /// <returns>
    ///     <c>true</c> if the specified user is authorized; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsAuthorized( this AuthorizeAttribute authorize, IPrincipal user )
    {
        if ( !user.Identity.IsAuthenticated ) return false;

        var users = authorize.Users.SplitString();
        if ( users.Length > 0 && !users.Contains( user.Identity.Name, StringComparer.OrdinalIgnoreCase ) ) return false;

        var roles = authorize.Roles.SplitString();
        if ( roles.Length > 0 && !roles.Any( user.IsInRole ) ) return false;

        return true;
    }

    /// <summary>
    /// Splits and trims the specified string.
    /// </summary>
    /// <param name="original">The original.</param>
    /// <returns></returns>
    public static string[] SplitString( this string original )
    {
        return string.IsNullOrEmpty( original )
            ? new string[ 0 ]
            : original.Split( '.' ).Select( s => s.Trim() ).Where( s => !string.IsNullOrEmpty( s ) ).ToArray();
    }
}

With that, it’s possible to write markup like this:

<% if ( Html.IsAuthorized<ModelingController>( c => c.Index() ) ) {%>
    <div>My conditional content...</div>
<%}%>
 

If you’re using ASP.NET MVC Futures, it’s straightforward to write an authorized ActionLink:

public static class LinkExtensions
{
    /// <summary>
    /// Render an HTML action link if authorized by the target action.
    /// </summary>
    /// <typeparam name="TController">The type of the controller.</typeparam>
    /// <param name="helper">The helper.</param>
    /// <param name="action">The action.</param>
    /// <param name="linkText">The link text.</param>
    /// <returns></returns>
    public static string AuthorizedActionLink<TController>( this HtmlHelper helper, Expression<Action<TController>> action, string linkText )
        where TController : Controller
    {
        return AuthorizedActionLink<TController>( helper, action, linkText, null );
    }

    /// <summary>
    /// Render an HTML action link if authorized by the target action.
    /// </summary>
    /// <typeparam name="TController">The type of the controller.</typeparam>
    /// <param name="helper">The helper.</param>
    /// <param name="action">The action.</param>
    /// <param name="linkText">The link text.</param>
    /// <param name="htmlAttributes">The HTML attributes.</param>
    /// <returns></returns>
    public static string AuthorizedActionLink<TController>( this HtmlHelper helper, Expression<Action<TController>> action, string linkText, object htmlAttributes )
        where TController : Controller
    {
        var routeValuesFromExpression = ExpressionHelper.GetRouteValuesFromExpression<TController>( action );

        return helper.IsAuthorized( action )
            ? helper.RouteLink( linkText, routeValuesFromExpression, new RouteValueDictionary( htmlAttributes ) )
            : null;
    }
}

NOTE – You could do the same thing without MVC Futures, but would need a little more code (you’d need something like ExpressionHelper’s GetRouteValuesFromExpression method).

Here’s a markup example:

<%= Html.AuthorizedActionLink<ModelingController>( c => c.Index(), "Modeling" ) %>
 

9 Comments

  1. Unless I’m missing something, but I think IsAuthorizied(..) has some backwards logic, should it not be this instead?

    public static bool IsAuthorized(this AuthorizeAttribute authorize, IPrincipal user)
    {
    if (!user.Identity.IsAuthenticated) return false;

    var users = authorize.Users.Split();
    if (users.Length > 0 && users.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) return true;

    var roles = authorize.Roles.Split();
    if (roles.Length > 0 && roles.Any(user.IsInRole)) return true;

    return false;
    }

    Reply

  2. Chris you might want to update the
    public static bool IsAuthorized(this AuthorizeAttribute authorize, IPrincipal user)

    method with Travis’s corrections.

    Reply

  3. Chuck – Actually I’ve already fixed the code, but it’s a little different to Travis’s suggestion. It’s basically the same as AuthorizeAttribute’s AuthorizeCore method, but I’d originally mis-typed it. The only real change was inverting the Contains / All expressions:

    if ( users.Length > 0 && !users.Contains( …

    Let me know if the logic still seems wrong though (I could easily have missed something else).

    Thanks for the feedback!

    Reply

  4. 🙂 I found another bug in the code Chris.

    The argument for original.Split() in SplitString(…) should be a comma instead of a period. First time I am dealing with multiple roles today, that’s why its just now coming up for me.

    Reply

Leave a comment