1

Backstory

In my application, an Identity User is associated to many organisations, each association acts similarly to an individual 'account' but under the same login. On initial login, the user selects which account they want to proceed with, upon doing so, I create a claim (wiping any existing claim to the type) and set up the ID for which account they select.

The Question

Currently I use a policy to check that the appropriate ID claim is filled in on my controllers, is there any way I can redirect the user to the 'Account selection' page using my policy? If not, what would be considered the Asp.net way. Should I be performing a check on each controller mapped method?

To Complicate Things Further

A lot of my methods are of return type JSONResult for Ajax, and so I can foresee myself having to return a 'direct to' field in my JSON and handling it appropriate in the JavaScript if it exists in a response, again, is there a better way of doing this?


Many thanks

David Moores
  • 815
  • 7
  • 21

2 Answers2

1

Use a filter that is applied to a controller in case a specific role/claim is not set:

public class CheckAuthorizationFilter : ActionFilterAttribute 
{
    private static readonly List<string> Exceptions = new List<string>
        {
            "account/",
            "public/"
        };

    /// <summary>
    /// Called by the ASP.NET MVC framework before the action method executes.
    /// </summary>
    /// <param name="filterContext">The filter context.</param>
    public override void OnActionExecuting([NotNull] ActionExecutingContext filterContext)
    {
        Contract.Requires(filterContext != null);
        Contract.Requires(filterContext.HttpContext != null);
        Contract.Requires(filterContext.HttpContext.User != null);

        if (filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            string actionName =
                $"{filterContext.ActionDescriptor.ControllerDescriptor.ControllerName}/{filterContext.ActionDescriptor.ActionName}"
                    .ToLower();
            if (!Exceptions.Any(actionName.StartsWith))
            {
                UserProfile user = UserProfileCache.Instance.Get(new TimeJackContext(),
                    new object[] {filterContext.HttpContext.User.Identity.GetUserId()});
                if (user == null || user.UserState != UserState.Active)
                {
                    SessionMessage.AddMessage(
                        $"You ({filterContext.HttpContext.User.Identity.Name}) are not allowed to use this smart tool at this time.", SessionMessage.MessageType.warning);
                    filterContext.Result = new RedirectToRouteResult(
                        new RouteValueDictionary
                        {
                            {"Controller", "Account"},
                            {"Action", "LogOff"}
                        });
                }
            }
        }
        base.OnActionExecuting(filterContext);
    }
}

and add the filter to the list of global filters:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    Contract.Requires(filters != null);
    filters.Add(new DatabaseUpgradeNeededFilter());
    filters.Add(new HandleErrorAttribute());
    filters.Add(new WarnAboutOldBrowsersAttribute());
    filters.Add(new CheckAuthorizationFilter()); // <---
    filters.Add(new RedirectToSslFilter());
    // ...
}
Stephen Reindl
  • 5,028
  • 2
  • 32
  • 37
  • This is fairly new to me, so I'll play around with the above and get back to you. But thank you, it looks like what I was after! – David Moores Feb 05 '16 at 13:29
0

If you're using cookie auth middleware to persist your principal then you could use the Forbidden setting to choose a page to redirect to when policy evaluation fails;

app.UseCookieAuthentication(options =>
{
    options.AuthenticationScheme = "MyCookieMiddlewareInstance";
    options.LoginPath = new PathString("/Account/Login/");
    options.AccessDeniedPath = new PathString("/Account/Switch/");
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
});

Then implement your switching logic in /Account/Switch

blowdart
  • 52,422
  • 11
  • 102
  • 145
  • Sadly I'm not using cookie auth middleware, I'm building upon the asp.net core Web Project template and using the Identity Framework with AuthorizationOption policies. My controller has my policy attribute assigned to it so that if it's hit by the user like you mentioned [here](http://stackoverflow.com/a/31465227/2352472). Is there a way I can redirect the failures against this authentication to a given path? – David Moores Feb 05 '16 at 13:23