ASP.NET MVC - Authorization Refactor - c#

The problem i see with this code, is that it's going to be reused a lot; anything being edited/created by a authenticated user (except for Site administrators) will only have access to a their "studios" objects.
My question to you all; how would you re-factor this so the service layer can be abstracted away from the knowledge of the client. I intend to reuse the service layer in a stand-alone desktop application later.
Please shed some light on my erroneous ways! I greatly appreciate it.
AuthorizeOwnerAttribute.cs (AuthorizeAttribute)
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// Get the authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = httpContext.Request.Cookies[cookieName];
// If the cookie can't be found, don't issue the ticket
if (authCookie == null) return false;
// Get the authentication ticket and rebuild the principal & identity
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] userData = authTicket.UserData.Split(new[] { '|' });
int userId = Int32.Parse(userData[0]);
int studioID = Int32.Parse(userData[1]);
GenericIdentity userIdentity = new GenericIdentity(authTicket.Name);
WebPrincipal userPrincipal = new WebPrincipal(userIdentity, userId, studioID);
httpContext.User = userPrincipal;
return true;
}
Inside of my "User" Controller attach this attribute to any method that requires an owner
[AuthorizeOwner]
public ActionResult Edit(int Id)
{
IUser user = userService.GetById(HttpContext.User, Id);
return View(user);
}
Now, in my Service layer is where I'm checking the passed down IPrincipal if it has access to the object being requested. This is where it's getting smelly:
UserService.cs
public IUser GetById(IPrincipal authUser, int id)
{
if (authUser == null) throw new ArgumentException("user");
WebPrincipal webPrincipal = authUser as WebPrincipal;
if (webPrincipal == null) throw new AuthenticationException("User is not logged in");
IUser user = repository.GetByID(id).FirstOrDefault();
if (user != null)
{
if (user.StudioID != webPrincipal.StudioID) throw new AuthenticationException("User does not have ownership of this object");
return user;
}
throw new ArgumentException("Couldn't find a user by the id specified", "id");
}

I'm not sure I'd be storing the actual IDs in the cookie, that's a little too exposed. I'd be more inclined to use the Session hash to store that data thus keeping it on the server and not exposed.
I'd also use the Model (by passing the userID) to determine which objects to return, i.e. those that have a matching studioID. That way your controller would only ever have to call "GetObjects(int id)", if they don't have access to anything then you get a null or empty collection back. That feels cleaner to me.

Related

How to get logged in users data (in a secure way)

I'm using ASP.NET MVC Entity Framework.
In my application I'm currently able to login and retrieve some data, but I'm not sure if the way I do it is a secure way.
Here is how my application works:
Currently when a user logs in, I make use of the following to store the username in a cookie:
FormsAuthentication.SetAuthCookie(user.Username, true);
Then I have a RoleProvider class that contains the following method, which returns a string array with the users role:
public override string[] GetRolesForUser(string username)
{
List<string> rolesList = new List<string>();
string role = CurrentlyLoggedInUser.User.Role.Name;
rolesList.Add(role);
string[] rolesArray = rolesList.ToArray();
return rolesArray;
}
Then I have the following class, that stores data of the currently logged in user:
public class CurrentlyLoggedInUser
{
private const string UserKey = "MyWebApp.Infrastructure.UserKey";
public static User User
{
get
{
MyDBEntities db = new MyDBEntities();
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
return null;
}
var user = HttpContext.Current.Items[UserKey] as User; //we set the
if (user == null)
{
user = db.Users.FirstOrDefault(x => x.Username == HttpContext.Current.User.Identity.Name);
if (user == null)
{
return null;
}
HttpContext.Current.Items[UserKey] = user;
}
return user; //Then we return the user object
}
}
}
Then whenever I need to query the database to find any related data of the user, I retrieve the users Id as follow:
int id = CurrentlyLoggedInUser.User.UserId;
Now I can make queries using this Id.
When I'm done, I logout and call the following:
FormsAuthentication.SignOut();
Is this recommended and secure enough?
Consider requiring https to secure you login form, don't forget about Authorize attribute on your MVC controllers and/or actions (or, preferably set Authorize attribute as a global filter) and on the basic level that's enough. #Chad-Nedzlek is not absolutely correct, FormsAuthentication, you involve, takes care about your authentication cookie encryption, so it's not so easy for anyone to impersonate any other. But you have to take care about preventing bruteforce attacks when implementing your custom login: consider password lifetime limitation, required complexity etc.
And if you are thinking about future extensibility, SSO for web, mobile apps and API, or possibility of easy use of external (Facebook, Google etc) login, consider switching to OpenId Connect.

Why does the authorize attribute fail to authorize the action with valid user being logged in?

I am using authorize attribute over an action.
[Authorize(Users= "admin" )]
[HttpGet]
public JsonResult GetServices()
{
return Json(ServicesRepository.SelectServices(), JsonRequestBehavior.AllowGet);
}
While successfully logging in I am setting:
Session["Users"] = usersModels;
Session["UHTUserName"] = usersModels.UserName;
FormsAuthentication.SetAuthCookie(usersModels.UserName, LoginVM.RememberMe);
AuthorizeAttribute aattr = new AuthorizeAttribute();
aattr.Users = usersModels.UserName;
but still, it fails to authorize.
Based on the above code snippet, you are using form authentication with MVC.
When Forms authentication is being used, whenever the need for authentication arises, the ASP.NET framework checks with the current IPrinciple type object. The user ID and Role contained in this IPrinciple type object will determine whether the user is allowed access or not.
So far you have not written code to push your user's Role details in this principle object. To do that you need to override a method called FormsAuthentication_OnAuthenticate in global.asax. This method is called each time ASP.NET framework tries to check authentication and authorization with respect to the current Principle.
What you need to do now is to override this method. Check for the authentication ticket (since the user has already been validated and the ticket was created) and then supply this User/Role information in the IPrinciple type object. To keep it simple, you will simply create a GenericPriciple object and set user specific details into it, as follows:
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (userDbEntities entities = new userDbEntities())
{
User user = entities.Users.SingleOrDefault(u => u.username == username);
roles = user.Roles;
}
//let us extract the roles from our own custom cookie
//Let us set the Pricipal with our user specific details
e.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
//somehting went wrong
}
}
}
}
Note: In MVC 4 and later versions, this event will not work. To make the custom forms authentication work in MVC 4 and later versions, we need to put this code in Application_PostAuthenticateRequest event in the Global.asax file.
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (userDbEntities entities = new userDbEntities())
{
User user = entities.Users.SingleOrDefault(u => u.username == username);
roles = user.Roles;
}
//let us extract the roles from our own custom cookie
//Let us set the Pricipal with our user specific details
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
//somehting went wrong
}
}
}
}
Reference: https://www.codeproject.com/Articles/578374/AplusBeginner-splusTutorialplusonplusCustomplusF
Did you set the settings in web.config for Forms Authentication
<system.web>
<authentication mode="Forms"></authentication>
<system.web>
And while login set the cookie as follows
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, //version
UserName, // user name
DateTime.Now, // create time
expiration, // expire time
RememberMe, // persistent
strUserData); // user data/role
HttpCookie objHttpCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
objHttpCookie.Path = FormsAuthentication.FormsCookiePath;
Response.Cookies.Add(objHttpCookie);

How to get user's information on WebAPI controller after authenticated with IdentityServer?

I cannot get user's information on WebAPI controller after my client app authenticates with IdentityServer3 successfully. Below are the steps:
"Login With Profile and Access Token" successfully from JavaScript Implicit Client app
I see user's data on "ID Token Contents" panel
I do "Call service" to my WebAPI service, I see many claims in ClaimsPrincipal but cannot get values such as email, roles displayed on client side. Below are code & responses.
Could anyone provide me some helps how to get user's data on WebAPI?
if you use owin, you can try this code.
var owinUser = TryGetOwinUser();
var claim= TryGetClaim(owinUser, "email");
string email = claim.Value;
private ClaimsPrincipal TryGetOwinUser()
{
if (HttpContext.Current == null)
return null;
var context = HttpContext.Current.GetOwinContext();
if (context == null)
return null;
if (context.Authentication == null || context.Authentication.User == null)
return null;
return context.Authentication.User;
}
private Claim TryGetClaim(ClaimsPrincipal owinUser, string key)
{
if (owinUser == null)
return null;
if (owinUser.Claims == null)
return null;
return owinUser.Claims.FirstOrDefault(o => o.Type.Equals(key));
}

Principal get lost after one request

I have the function AffiliateLogin in a controller that sets the Principal.
the row principal.User = user; is actually the one storing the Principal.
But after I redirect to another controller, and test my AuthorizeWithRolesAttribute attribute, the principal is reset.
This is one second after the login, you can see the red arrow:
this is the function that stores it.
What am I doing wrong?
Thanks
public JsonResult AffiliateLogin(string email, string password)
{
if (ModelState.IsValid)
{
Affiliate user = api.GetUserByCredencials<Affiliate>(email, password);
if (user != null)
{
IIdentity identity = new UserIdentity(true,user.Email);
UserPrincipal principal = new UserPrincipal(identity, new string[] {"Affiliate"});
principal.User = user;
HttpContext.User = principal;
return Json("Login success");
}
}
return Json("Fail To Login");
}
The principal property won't survive between web requests. You had to set it again in the next request after redirection.
If your doing doing custom authentication/forms authentication you should call
FormsAuthentication.SetAuthCookie
The next http from the browser with that cookie , Asp.net will process the cookie and set
the current claims principal. So you can check
var principal = ClaimsPrincipal.Current; //normally this reverts to Thread.CurrentPrincipal,
Here is a good place to learn a bit more
http://msdn.microsoft.com/en-us/library/system.security.claims.claimsprincipal.current

ASP.NET MVC Forms Authentication multiple simultaneous logins

My scenario is probably the opposite of most, I want to ALLOW multiple simultaneous logins but only for different types of users.
User — Has their own area
Admin — Has their own area
The problem occurs as an Admin can be a User as well (they have two accounts, this is mainly so they can check how the system is working from a user PoV) and want to be logged into both at the same time.
With Forms authentication, this doesn't seem possible. So I've had to "hack" around it slightly and am worried I might have overlooked something.
Plan:
Two action filters for each type of user: UserAuthorise &
AdminAuthorise
Have two session cookies for each type of user
Decorate controllers which the correct action filter based on what
user can access it.
Code might need some tidying up.
I'll make the cookie names more unique as well.
Excluded stuff like Views/Routes as they don't seem relevant.
Left password salting/hashing out of samples and stuck with test values.
UserAuthorise:
public class UserAuthorize : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var authCookie = filterContext.RequestContext.HttpContext.Request.Cookies["User"];
if (authCookie == null || authCookie.Value == "")
{
filterContext.HttpContext.Response.Redirect("/login");
base.OnActionExecuting(filterContext);
return;
}
FormsAuthenticationTicket authTicket;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
filterContext.HttpContext.Response.Redirect("/login");
base.OnActionExecuting(filterContext);
return;
}
if (authTicket.Expired || authTicket.Expiration <= DateTime.Now)
{
filterContext.HttpContext.Response.Redirect("/login");
}
base.OnActionExecuting(filterContext);
}
}
AdminAuthorise:
public class AdminAuthorise : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var authCookie = filterContext.RequestContext.HttpContext.Request.Cookies["Admin"];
if (authCookie == null || authCookie.Value == "")
{
filterContext.HttpContext.Response.Redirect("/admin/login");
base.OnActionExecuting(filterContext);
return;
}
FormsAuthenticationTicket authTicket;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
filterContext.HttpContext.Response.Redirect("/admin/login");
base.OnActionExecuting(filterContext);
return;
}
if (authTicket.Expired || authTicket.Expiration <= DateTime.Now)
{
filterContext.HttpContext.Response.Redirect("/admin/login");
}
base.OnActionExecuting(filterContext);
}
}
User Login controller action:
[HttpPost]
public virtual ActionResult Login(FormCollection form)
{
if (form["username"] == "admin" && form["password"] == "pass")
{
var authTicket = new FormsAuthenticationTicket(
1, // version
form["username"], // user name
DateTime.Now, // created
DateTime.Now.AddMinutes(20), // expires
false, // persistent?
"" // can be used to store roles
);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie("User", encryptedTicket);
Response.Cookies.Add(authCookie);
// Redirect back to the page you were trying to access
return RedirectToAction(MVC.Home.Index());
}
else
{
ModelState.AddModelError("", "Bad info mate");
}
return View();
}
Admin Login controller action:
[HttpPost]
public virtual ActionResult Login(FormCollection form)
{
if (form["username"] == "admin" && form["password"] == "pass")
{
var authTicket = new FormsAuthenticationTicket(
1, // version
form["username"], // user name
DateTime.Now, // created
DateTime.Now.AddMinutes(20), // expires
false, // persistent?
"" // can be used to store roles
);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie("Admin", encryptedTicket);
Response.Cookies.Add(authCookie);
// Redirect back to the page you were trying to access
return RedirectToAction(MVC.Admin.Home.Index());
}
else
{
ModelState.AddModelError("", "Bad info mate");
}
return View();
}
Does this all seem sensible and secure?
Looking in FireFox's Page Info window at cookies I see each user type has its own cookie and you can't access a user type area without logging in.
First, you should probably be deriving from AuthorizeAttribute rather than ActionFilterAttribute. AutorizationFilters exectue before ActionFilters, and allow short-circuiting (that is, if an authorization filter fails, action filters will never execute). Also, ActionFilters are chained together, and might execute in any order.
Second, it's not a good idea to have the admin username and password hard coded into the attribute. Passwords should really be one-way hashed.
What you need for this scenario is called impersonation, basically all you have to do is set a fake authentication cookie with the impersonated user data (so the admin can see what the customer see).
Probably you would also want to keep track of this, so you can place on the user interface of the admin impersonating the user infos about the state of the application, and also give it a link to end the impersonation session (you would at this point restore the previous cookie), instead of letting him "log in twice".
You can check this out, as it may contain some useful infos for you (a bit old but always valid).
Regarding your database model, I'd assign several roles (simple user, admin, supervisor, etc) to a user. This way, you would login just once using a default role (admin), and have an option to switch to another role (simple user PoV) and store permissions on session.

Categories