Hybrid of Windows Authentication and Forms Authentication in ASP.NET MVC 4 - asp.net-mvc

We have an ASP.NET MVC 4 intranet application. We’re using Windows Authentication and that aspect works fine. The user’s credentials are used and we can access those credentials from the web app.
What we really want is some sort of hybrid mode, however. We want to get the user’s credentials from the browser, but we also want to verify that the user is in our application’s database. If the user’s in the database, then they can just continue on. If they’re not, we want to redirect them to a page asking for alternate credentials. What I’m doing now is, in Global.asax.cs, I’ve got an Application_AuthenticateRequest method and I’m checking to see if the user is authenticated. If they are and their cookie information doesn’t reflect the fact that they’re logged into the system, then I log them in and set up some cookies with info about the user. If they’re not authenticated, I redirect them to a login page. We can’t use AD roles for reasons involved with company policy, so we need to use the database for additional authentication.
I’m guessing Application_AuthenticateRequest isn’t the place to do this, but maybe it is. But we basically need a place to filter the requests for authentication. But additionally this implementation leads me to another issue:
We have certain URLs in our app that allow anonymous access. I’ve added <location> tags to the web.config for these. The problem is, when anonymous calls are made into these, it gets to Application_AuthenticateRequest and tries to log the user into the DB. Now, I can add code into Application_AuthenticateRequest to handle these URLs and that’s currently my plan, but if I’m write and Application_AuthenticateRequest isn’t the place to be doing this, then I’d rather figure it out now than later.

You need to use Action Filters for this purpose. You can extend the AuthorizeAttribute like this:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private UnitOfWork _unitOfWork = new UnitOfWork();
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = false;
var username = httpContext.User.Identity.Name;
// Some code to find the user in the database...
var user = _unitOfWork.UserRepository.Find(username);
if(user != null)
{
isAuthorized = true;
}
return isAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext))
{
SetCachePolicy(filterContext);
}
else
{
// If not authorized, redirect to the Login action
// of the Account controller...
filterContext.Result = new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary {
{"controller", "Account"}, {"action", "Login"}
}
);
}
}
protected void SetCachePolicy(AuthorizationContext filterContext)
{
// ** IMPORTANT **
// Since we're performing authorization at the action level,
// the authorization code runs after the output caching module.
// In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later
// be served the cached page. We work around this by telling proxies not to
// cache the sensitive page, then we hook our custom authorization code into
// the caching mechanism so that we have the final say on whether a page
// should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidationHandler, null /* data */);
}
public void CacheValidationHandler(HttpContext context,
object data,
ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Then, you can use this attribute at the Controller level or Action level like this:
[MyAuthorize]
public ActionResult SomeAction()
{
// Code that is supposed to be accessed by authorized users only
}

Related

MVC3: Session_Start Fires twice when testing for Roles

I need to do some authentication for a web app with MVC3. The customer would like there to be a generic page to show if they do not have any of the role groups in windows AD that are allowed to use the app. I found a pretty simple way to do it, but just curious if it is a valid way or if there is something better out there.
Basically in the Session_Start in the global I am checking for User.IsInRole() and if that returns false then I do a Response.Redirect(). This question is: after it his the code in the IF statement and hits the Response.Redirect() code then it hits the session one more time before it goes to the AccessDenied page in the root of the app. Is this okay? Will it cause any issues If they are valid and does not enter the If to do the response.redirect?
//if (!User.IsInRole("test_user"))
//{
// Response.Redirect("~/AccessDenied.aspx", true);
//}
I would recommend you to write your Authorization filter for MVC3 and do this type of logic there:
public class RoleFilter: AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext filterContext)
{
if (!User.IsInRole("test_user"))
{
filterContext.HttpContext.Response.StatusCode = 302;
filterContext.Result = new RedirectResult("~/AcessDenied.aspx");
}
}
}
Also I wouldn't recommend you to use Response.Redirect because it aborts current thread.

ASP.NET MVC request context details

Where can I plug in a custom provider to set up the request context?
I want to run an ASP.NET MVC application in "slave" mode while gradually transitioning features from a legacy system. Each request will have a cookie, and I want to grab the cookie, make an external call to resolve it to a user identity, and set up that user identity for the remainder of the request.
I might set a forms authentication cookie, or use Session, but the source of truth about authentication has to be the externally-set cookie, on every request.
What's the best way to do this? Where do I plug in? I've looked at Authentication providers, and the Authorization attribute, but neither of those seems the right place for this.
I would have thought an HttpModule would be ideal for this scenario?
If I understand you correctly, I did something similar on a project I was working on recently:
public class UserSessionHttpModule : IHttpModule
{
private HttpApplication mApplication;
public void Dispose()
{
}
public void Init(HttpApplication context)
{
mApplication = context;
context.BeginRequest += new EventHandler(CheckUserSession);
}
private void CheckUserSession(Object sender, EventArgs e)
{
var extension = Path.GetExtension(mApplication.Context.Request.Path);
if (extension == "" || extension == ".aspx")
{
var userSessionService = ObjectFactory.GetInstance<IUserSessionService>();
userSessionService.CheckUserSession();
}
}
}
you could override the Init method in Global.asax page and listen to the PostAuthenticateRequest event, the events fires after authentication so you could change whatever values you like from the form authenticate cookies and inject your own

How do I invalidate a bad authentication cookie early in the request?

Scenario
I develop an MVC application that will be hosted in Windows Azure. During development, I test against a local database and local user membership service. In production, the application hits off a SQL Azure database and a cloud-hosted user membership service.
Sometimes I'll log in to the local version of the site with a user account that only exists on my local machine. If I forget to log out but switch my machine out of test mode, things start to break.
The Problem
I set my hosts file to point my application URL at 127.255.0.0 so that browser requests are handled by the Azure emulator locally. This also means my test application and the production application use the same host name - cookies for one are seen as valid cookies for the other.
If I log in to my local system, an ASPXAUTH cookie is created for the domain and contains the credentials of the user from my local system. If I forget to log out and switch my hosts file back to normal (browser requests go to the live application), it sends the same ASPXAUTH cookie to the server.
Since this user doesn't exist on the server, only locally, any requests like Membership.GetUser().Email return an object null exception.
What I'd Like to Do
Ideally, I could inject some code early in the request to check that the user exists. Something like:
MembershipUser user = Membership.GetUser();
if(user == null) {
FormsAuthentication.SignOut();
}
This will automatically remove the ASPXAUTH cookie for invalid users. But where do I put it? I started looking at my controllers - all of them inherit from a BaseController class, but even putting this code in the base controller's constructor isn't early enough. I also looked at Global.asax, but I'm not sure which event would be best to tie this action to.
Also, I'm not even sure this is the right way to do things. If not, what is the best way to ensure that the authentication cookie is for a valid user before just depending on the membership class like I am currently?
You may handle the below event in you global.aspx:
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
{
}
EDIT:
In the above method, Membership.GetUser() will return null, since it takes the userName from HttpContext.Current.User.Identity.Name property and in this event the user has not yet been authenticated. The above method is invoked by FormsAuthenticationModule when it raised Authenticate even. You can handle this event like so:
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported &&
Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
var formsAuthTicket = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value);
MembershipUser user = Membership.GetUser(formsAuthTicket.Name);
if (user == null)
{
FormsAuthentication.SignOut();
Request.Cookies[FormsAuthentication.FormsCookieName].Value = null;
}
}
catch (Exception ex)
{
//TODO:log ex
}
}
}
You can also consider to handle the AuthenticateRequest event by declaring the below method in the global.aspx.
NOTE: Application_AuthenticateRequest event is fired after the FormsAuthentication_OnAuthenticate event.
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated)
{
MembershipUser user = Membership.GetUser();
if (user == null)
{
FormsAuthentication.SignOut();
HttpContext.Current.User = null;
}
}
}
Hope this helps..
You create a base controller that overrides the OnActionExecuting and place your code there.
Have all your controller controllers inherit from him. This the OnActionExecuting from a older project I had
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Request.IsAuthenticated)
{
if (Session[SomeKey] == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Account" }, { "action", "LogOff" } });
}
}
base.OnActionExecuting(filterContext);
}
I'm basically checking if the request is authenticated otherwise I redirect it to logout action.
http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.onactionexecuting.aspx

Should I Use Session.Abandon() in my LogOff Method?

Technologies I'm Using:
MVC v2
Forms Authentication (Sliding Expiration)
Session State Server
Custom Authorization Attribute
I'm using the state server process for my mvc app. During testing, when an authenticated user would click the "LogOff" button, it would correctly take them to the authentication screen, and upon successful credential entering, would log them back in. BUT, it would find their prior session variable state, and NOT reload any new permissions I'd given them. This is due to how I'm loading a user in the following code:
public override void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null)
throw new ArgumentNullException("FilterContext");
if (AuthorizeCore(filterContext.HttpContext)) {
IUser customUser = filterContext.HttpContext.Session["CustomUser"] as IUser;
if ((customUser == null) || (customUser.Name != filterContext.HttpContext.User.Identity.Name)) {
customUser = new User(filterContext.HttpContext.User.Identity.Name,
filterContext.HttpContext.User.Identity.IsAuthenticated);
}
if (_privileges.Length > 0) {
if (!customUser.HasAtLeastOnePrivilege(_privileges))
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
filterContext.HttpContext.Session["CustomUser"] = customUser;
}
}
So, you can see I'm storing my customUser in the Session and that value is what was fetched from the prior session even though the user had logged off between (but logged back on within the sliding expiration window.
So, my question is, should I place a simple Session.Abandon() call in my LogOff method in the AccountController, or is there a cleaner more advantageous way of handling this?
Normally Session.Clear() should be enough and remove all values that have been stored in the session. Session.Abandon() ends the current session. It might also fire Session_End and the next request will fire Session_Start.

With ASP.NET membership, how can I show a 403?

By default, ASP.NET's membership provider redirects to a loginUrl when a user is not authorized to access a protected page.
Is there a way to display a custom 403 error page without redirecting the user?
I'd like to avoid sending users to the login page and having the ReturnUrl query string in the address bar.
I'm using MVC (and the Authorize attribute) if anyone has any MVC-specific advice.
Thanks!
I ended up just creating a custom Authorize class that returns my Forbidden view.
It works perfectly.
public class ForbiddenAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext))
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else
{
// auth failed, display 403 page
filterContext.HttpContext.Response.StatusCode = 403;
ViewResult forbiddenView = new ViewResult();
forbiddenView.ViewName = "Forbidden";
filterContext.Result = forbiddenView;
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Asp.net has had what I consider a bug in the formsauth handling of unauthenticated vs underauthenticated requests since 2.0.
After hacking around like everyone else for years I finally got fed up and fixed it. You may be able to use it out of the box but if not I am certain that with minor mods it will suit your needs.
be sure to report success or failure if you do decide to use it and I will update the article.
http://www.codeproject.com/Articles/39062/Salient-Web-Security-AccessControlModule.aspx

Resources