MVC give authorization on login to access actions - asp.net-mvc

I've been running in circles trying to find an answer to this, and I can't seem to make any progress.
All I want to do, is check for a correct username and password combo, then GIVE the user authorization to access MVC actions decorated with the [Authorize] tag.
public ActionResult DoLogin(PageInitModel model)
{
EF_db db = new EF_db();
string saltPassword = getSaltedPasswordEncryption(model.UserName, model.Password);
var user = (from s in db.Users
where s.Username == model.UserName
&& s.Password == saltPassword
select s).FirstOrDefault();
if(user == null)
{
model.LoginFail = true;
return View("Login", model);
}
else
{
//
// give the user some magical token to access [Authorize] actions here
//
return RedirectToAction("Index", "Menu");
}
}
Above is the login action (called from a basic form), and below would be one of the actions I would like to restrict access to:
public class MenuController : Controller
{
[Authorize]
public ActionResult Index()
{
var pageInitModel = new PageInitModel();
return View("Menu",pageInitModel);
}
}
I would like to keep track of the users myself (in my own tables), because there are many additional attributes I would like to track. I'm not sure if I need to write a custom AuthorizeAttribute, or what, but I can't seem to make any headway.
Any help pointing me in the right direction is greatly appreciated.
Thanks!

You should look into ASP.NET Identity. Note this was introduced in ASP.NET 5 (?) and replaces some older frameworks Microsoft had like Basic Membership etc.
Honestly you really don't want to roll your own. ASP.NET Identity does exactly what you describe right out of the box. Keep in mind there are two distinct concepts Authentication and Authorization.
Authentication is verifying the user is who he says he is.
Authorization is restricting access to only users who are "allowed".
There are many ways to structure Authorization but I assume Role based Authorization will meet your need. You will define multiple roles, say User, Admin, Moderator, Admin, etc.
You then restrict access to actions based on the role. You can even make roles overlap and allow a single user to have multiple roles. The end result is that once a user logs in their role determines what they can do via the authorize tags.
[Authorize(Roles="Admin")]
If the user is not logged in, they will be redirected to the login form to AUTHENTICATE ("Um I don't know who you are". Once authenticated if they are not authorized they will still be restricted from this action ("Oh I know who you are but you are not allowed to do this".
To round it out you can also have anonymous actions which mean no authentication is required and Authorize actions which are not limited to a specific role (any authenticated user is allowed but not unauthenticated users).
The fact that even with a basic [Authorize] you are having issues leads be to believe there is some configuration problems in even the Authentication. I recommend going through a tutorial building an example app like this one:
http://blogs.msdn.com/b/webdev/archive/2013/10/20/building-a-simple-todo-application-with-asp-net-identity-and-associating-users-with-todoes.aspx

As I understood you have your users information and you may want to use the authentication by custom code. But to actually login the user you must create a session of the user (Cookie session) for further authentication of requests.
You have already verified the username and password. now you've to create a session cookie using form authentication. Here's answer to your first problem:
// give the user some magical token to access [Authorize] actions here
// User name and password matches
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
The Authorize attribute will then check if the request coming for the action is authenticated. This attribute internally checks if a session cookie is valid and authenticated.
Also this attribute not only checks for authentication but also can verify authorization. So you can use custom roles as well with Authorize attribute to restrict users to accessing specific views/actions.

It was pointed out to me that I needed to look into cookies or existing membership providers, so I looked around in FormsAuthenticationTickets and rolled my own solution that I would love feedback on.
I ended up creating a new ticket when the login was successful with the FormsAuthenticationTicket class (see below).
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
user.Username,
DateTime.Now,
DateTime.Now.AddHours(2),
false,
userData,
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
Then I wrote a custom ActionFilter, so I could make my own method decorators (or whatever they're called).
namespace MyApp.Filters
{
public class CustomLoginFilter : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
// Retrieves the cookie that contains your custom FormsAuthenticationTicket.
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
// There's actually a cookie
// Decrypts the FormsAuthenticationTicket that is held in the cookie's .Value property.
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && user != null && data == authTicket.UserData)
{
// Everything looks good
this.OnActionExecuting(filterContext);
}
else
{
//log bounceback - someone screwed with the cookie
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
else
{
//log bounceback - not logged in
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
}
}
I'll likely play with it more, but it seems to work fine for now.
Thanks for the help.

Related

What's the purpose of this SignInManager call when the controller has the [Authorize] attribute?

Having set up a default ASP.Net MVC 5 application, I fail to understand why the below snippet has a call to SignInManager.
This is in the ManageController, which has the [Authorize] attribute.
//
// POST: /Manage/RemoveLogin
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RemoveLogin(string loginProvider, string providerKey)
{
ManageMessageId? message;
var result = await UserManager.Get().RemoveLoginAsync(User.Identity.GetUserId<int>(), new UserLoginInfo(loginProvider, providerKey));
if (result.Succeeded)
{
var user = await UserManager.Get().FindByIdAsync(User.Identity.GetUserId<int>());
if (user != null)
{
await SignInManager.Get().SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
message = ManageMessageId.RemoveLoginSuccess;
}
else
{
message = ManageMessageId.Error;
}
return RedirectToAction("ManageLogins", new { Message = message });
}
I am wondering if, whenver I retrieve the authenticated user, I should repeat this step, i.e. check if the user is null and if not, await SignInAsync.
Edit: check if the user is null and if it is, await SignInAsync
Now, I've created a new controller, that I've given the [Authorize] attribute, and in the Index() function of the controller, I do:
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId<int>());
If I load that page in two tabs, sign out in one of them and then refresh the page, I'm redirected to the login screen. I've attached a debugger and have been unable to cause a case where the SignInManager is hit.
In what scenario would the user be not-null?
They do entirely different things. The Authorize attribute checks that the user is authenticated and authorized to access the action (based on role permissions and such). It doesn't authenticate the user. That's what the call to SignInManager is for. It actually authenticates the user, so that the user can then pass checks made by the Authorize attribute.
As for when the user might be null, effectively, if the action is protected by Authorize it probably never will be. It would have to take some strange confluence of events where the user was found in the database in order to sign them in, but then somehow was removed by the time you tried to retrieve it. In other words: not bloody likely. Still, it's good practice to always perform a null-check like this when a value could potentially be null, which user could.

Custom Authentication .Net MVC

I have a web app that allows a user to pay for a holiday booking.
The user will log in using a combination of a unique booking ID and a booking password.
Is there a simple way I can authenticate the user once they have successfully logged in. Is it a good idea to use the .net session object for this purpose?
The app wont be load balanced so I don't need to worry about storing session across different machines.
I would like to not have to use .Net membership as I don't want to create any extra tables in the database.
If you are able to retrieve the user, I guess this is possible. Below is the way I would try:
public ActionResult UrlAuth(string bookingId, string bookingPass)
{
var userManager=HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
// Retrieve the user by the give booking values (Custom part in your app
string userId= _bookingRepository.GetUserIdByBooking(bookingId, bookinggPass);
var user = userManager.FindById(userId);
if (user != null)
{
var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, userIdentity );
// authentication succeed do what you want
return Redirect("Wherever");
}
ModelState.AddModelError("", "Invalid booking values");
return View();
}
Here's a good general article with custom authentication for MVC
http://www.ryadel.com/en/http-basic-authentication-asp-net-mvc-using-custom-actionfilter/
http://www.codeproject.com/Articles/1005485/RESTful-Day-sharp-Security-in-Web-APIs-Basic

ASP.net MVC Authentication using external PHP API

I'm developing an asp.net MVC website with the following requirements:
Develop pages for Admin and Users, these pages must be accessed
based on logged in user role: Admin or User
The website supports login only, You will call a PHP API which resides on an external website, it returns a JSON as a result that includes id, username, and role (admin, user)
You may save the result of returned json on a session to be used in your pages but this data must disappear after logout or session expiration.
I know how to develop the calling HTTP stuff and processing json, but I'm not familiar with authorization and authentication stuff, nor with using membership providers, I searched a lot and at first I thought of using SimpleMembership but I found that won't work since it depends on SQL queries and in my case I'm not going to use any type of databases.
I heard about asp.net identity but I'm not sure how to use it or if it's for my case or not, I searched again and I couldn't find any resource to help me achieve authentication and authorization for my case
I'm asking for your help to help me out and point me in the right direction
Thank you for your help
There is an example of using OAuth separated http auth API:
http://www.asp.net/web-api/overview/security/external-authentication-services
Yes, this example depends on some specified http API..
But in case when you have some another JSON/XML RPC API you can try to create your own feature like a:
public class ExternalAuthAPIClient {
public User Auth(string username, string password) { .... }
}
And use it in your AuthController in the method Login
BUT! This approach requires a lot of side changes.. where to store your user.. then create custom AuthenticateAttribure ... etc.
The better solution is to create oAuth supported API on your PHP side and use it with ASP.NET Identity.
I finally found a solution,I didn't need to use any membership providers since my website supports only login and via an API,I wrote the following code,this one is in AccountController :
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel login, string returnUrl)
{
if (!ModelState.IsValid)
{
ViewBag.Error = "Form is not valid; please review and try again.";
return View(login);
}
//Call external API,check if credentials are valid,set user role into userData
string userData="Admin";
var ticket = new FormsAuthenticationTicket(
version: 1,
name: login.Username,
issueDate: DateTime.Now,
expiration: DateTime.Now.AddSeconds(HttpContext.Session.Timeout),
isPersistent: false,
userData: userData);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Response.Cookies.Add(cookie);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", userData);
}
Then decorate admin/user controller with Authorize attribute like this:
[Authorize(Roles = "admin")]
public class AdminController : Controller
Then add the following code in Global.asax :
public override void Init()
{
base.PostAuthenticateRequest += Application_PostAuthenticateRequest;
}
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var decodedTicket = FormsAuthentication.Decrypt(cookie.Value);
var roles = decodedTicket.UserData;
var principal = new GenericPrincipal(HttpContext.Current.User.Identity, roles);
HttpContext.Current.User = principal;
}
}

Hybrid of Windows Authentication and Forms Authentication in ASP.NET MVC 4

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
}

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.

Resources