I have an MVC 5 application that I lock down by only allowing certain authenticated users to have access to specific actions within my controller. I utilize the authorize attribute at the top of the class allowing only the user(s) I want to gain access after login. I do this with the following attribute placed at top of my class...
[Authorize(Users="user1,user2")]
This works great! However, what if I don't want to recompile and deploy the application everytime I want to add a new user to this specific controller?
I thought I might add this in my web.config file under as a key like so...
<appSettings>
<add users="user1,user2"/>
</appSettings>
But when I try to access this key in my controller like so: [Authorize(Users=ConfigurationManager.AppSettings["users"])] I am getting an error: Cannot resolve symbol 'AppSettings'.
Is there a way to do this?
I'm not sure why an answer that didn't answer the question was accepted. Regardless, I thought it might be worth adding an answer for any future travelers.
While this functionality isn't provided out of the box, it's certainly possible by writing your own authorize attribute.
public class ConfigAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var allowedUsers = ConfigurationManager.AppSettings["CoolActionAllowedUsers"];
var allowedUsersArray = allowedUsers.Split(',');
if (httpContext.User.Identity != null && allowedUsersArray.Contains(httpContext.User.Identity.Name))
{
return true;
}
return false;
}
}
And to use the attribute:
[ConfigAuthorize]
public ActionResult CoolAction() {
//...
}
In the code above when your authorization is performed in AuthorizeCore, the configuration value from CoolActionAllowedUsers will be pulled into memory and the currently authenticated user will be verified if they are in the list of allowed users. If you make a change to your config file it won't be a problem; the application pool will automatically restart and the next time the code runs to read the config file your new value will be read.
I completely agree with #Shoe that roles should be used. Managing a list of users in your code is just a pain in the arse. In fact, at work, anytime I get a request for just one random user to have access to a page I always require a group to be setup. However the code above could apply to a list of roles as well.
Instead of using the Users parameter use the Roles parameter.
[Authorize(Roles="CanExecuteActions")]
Now you can manage what users have access to your controller by giving them this role. Any user without the role can't execute any actions of the controller.
Related
I'm using https://github.com/filipw/AspNetWebApi-OutputCache to add easy caching to my web-api project and I have an action that look something like this:
[HttpGet]
[CacheOutput(ClientTimeSpan = 86400, ServerTimeSpan = 86400)]
public List<Things> GetThings()
{
return service.GetThings();
}
Now things are a combination of a list of things that apply to everybody along with user-defined things that are created by a user and accessible only to that user. So I want the cache here to be tied to a specific user. I don't want user Bob getting a list of things that included things that are specific to Sally. So I created my own key generator, inheriting from DefaultCacheKeyGenerator that will append the user id:
public override string MakeCacheKey(System.Web.Http.Controllers.HttpActionContext context, System.Net.Http.Headers.MediaTypeHeaderValue mediaType, bool excludeQueryString = false)
{
var key = base.MakeCacheKey(context, mediaType, excludeQueryString);
return string.Format("{0}:{1}", key, userService.CurrentUser.UserID);
}
The UserID here is ultimately pulled from the user authorization cookie.
This seems to work fine.
However, I have another action that will let the user save their custom thing and obviously when I POST here I want to invalidate the cache, so it looks something like this:
[HttpPost]
[InvalidateCacheOutput("GetThings")]
public void SaveUserThing(UserThingModel thing)
{
service.Save(thing);
}
The problem (or rather the inefficiency) here is that from my understanding this will flush everything under this control and GetThings (the base key for all caches) which will include the cache for every user. This means if Bob saves a new thing, I'm going to force Sally to have to get a whole new list of things, even though her list won't have changed.
Is there an easy way around this? I suspect the problem lies in CacheOutputConfiguration.MakeBaseCacheKey, but there doesn't seem to be a mechanism to override that functionality to have it build a base key from controller, action and userId.
I could probably just grab the source from GitHub and adapt to suit my needs, but I wanted to be sure I wasn't a) missing something obvious and b) barking up the wrong tree.
I have an application where one username can belong to many companies. Thus to distinguish them, i need to use the both username and password as unique pair to login.
I'm using ASP.NET MVC and i struggle to understand where the Login occurs.
Actually i can see where it validates the user but i don't find where it retrieves the user.
So where the
Select user where username=xx and password=xx occurs ?
Asked differently : i did not find wher User is set ? I see User.Identity.Name it in the code, but i don't see :
User=Select....
Thanks
John
John, as you are using MVC. You wont be seeing any queries in the code aside from the LINQ syntax. Im guessing what you are trying to do is a many to many relationship between the User table and the Company table. (one user has multiple companys and 1 company has multiple users)
Pretty much database wise this would mean you need an extra table with both primairy keys of Company and Users.
To get back to your question. ASP.net MVC has its own membership provider. You can choose to either use the default one with its own tables or overwrite it and create ur own custom membership provider (with the ability to use ur own user table)
The default one pretty much should contain most of the basic attributes. (password reset, password salt, email,..)
http://www.asp.net/web-forms/tutorials/security/membership/creating-the-membership-schema-in-sql-server-cs
skip to the step: Installing the Application Services to generate the tables
However guessing you already have a database with your very own user table. you should overwrite the custom membership class.
Simply this would mean you make a new class that inherits from the abstract class "MembershipProvider"
public class MyMembershipProvider : MembershipProvider
{
}
After that you have to let asp know that you will be overwriting the default membershipprovider with yours in web.config:
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear />
<add name="MyMembershipProvider"
applicationName="MyApp"
Description="My Membership Provider"
passwordFormat="Clear"
connectionStringName="MyMembershipConnection"
type="MyApp.MyMembershipProvider" />
</providers>
</membership>
Some methods in the membership provider requires you to return or use an object of MembershipUser. Everything of how to implement this is right here:
http://msdn.microsoft.com/en-us/library/system.web.security.membershipuser.aspx
This is not a necessary step but its recommended.
Good luck john :)
If theres any confusion in the explanation, dont hesitate to ask
Short Answer:
The code you're describing happens behind the scenes in the LogOn action of the Account Controller:
MembershipService.ValidateUser(model.UserName, model.Password)
Which returns true for a valid user. The user is then "signed in" with the next line in the code:
FormsService.SignIn(model.UserName, model.RememberMe)
(You can see both of those functions defined in the AccountModels file under the Models folder)
If you want to also check company id while authenticating the user then you'll need to write your own auth method to replace ValidateUser. Ths will depend on what you're using for your store (SQL?)
But, as a broader point, best practices you should not allow the same user name for different users. It's just a bad idea and will lead to trouble.
UPDATE:
If I were recommending how to do this, I would suggest you user the UserProfile aspect of ASP.NET Membership. It is designed to capture and store additional user variables (such as company) while still using the nicely built and secure Membership that they've written for you. Read up on it, but below is my CreateUser function in the app I'm currently working on. Note how I use the Profile to store first and last name as well as a flag that the user needs their password reset.
Again, this would preempt the ability to have multiple users with the same username, but I really think you ought to avoid that.
[HttpPost]
public ActionResult CreateUser(string username, string email, string first, string last, string role)
{
string password;
MembershipUser user;
//Generate a random password
password = Auth.CreateRandomPassword(6);
try
{
//Create the user
user = Membership.CreateUser(username, password, email);
//Add the user to the chosen role
Roles.AddUserToRole(username, role);
//Create the user profile
UserProfile profile = UserProfile.GetUserProfile(username);
profile.FirstName = first;
profile.LastName = last;
profile.ForcePasswordReset = true;
profile.Save();
EmailNewUser(username, email, password);
}
catch (Exception ex)
{
HttpContext.Response.StatusCode = 500;
HttpContext.Response.StatusDescription = ex.Message;
HttpContext.Response.Clear();
}
return PartialView("UserTable", Auth.Users());
}
I was thinking yesterday how to solve this issue, because everything what i give or check about user is depended of his ProviderUserKey (ID).
So i made one static function like
public static Guid GetUserID()
{
string UserID = string.Empty;
if(HttpContext.Current.Session["UserID"] != null)
{
UserID = HttpContext.Current.Session["UserID"].ToString();
}
if(!string.IsNullOrEmpty(UserID))
{
return new Guid(UserID);
}
UserID = Membership.GetUser().ProviderUserKey.ToString();
HttpContext.Current.Session["UserID"] = UserID;
return new Guid(UserID);
}
Main point of this class is to reduce database connections to check/get user ID.
My problem with this function is not that this is not working, my problem is what if logged user log out and log with another account?
Or Is it better to add session value on log in and clear session value on log out?
Where you can see any other problem with this kind of "Get User ID"?
If you log the user out then you should also be killing the session.
When you login as another user you would also have the session reinitialized.
Note you'll want to keep the session and forms auth timeouts (assuming you are using forms auth) in sync with each other:
How can I handle forms authentication timeout exceptions in ASP.NET?
This should help keep the session in line with the forms auth token. You'll in turn need to kill the session on logout and intialize it upon login.
Another alternative is to implememt your own membership provider that caches this key to prevent constant db hits.
have you tried using the ProfileProvider?
you can use and customize with special properties and that's is managed by session each user.
example to get values:
HttpContext.Profile.GetPropertyValue["CustomProperty"]
In this video you can lean to implement it, create, configure and use...
http://www.asp.net/web-forms/videos/how-do-i/how-do-i-create-a-custom-profile-provider
i'm trying to control the session to forbid the access to some pages of my web app. The way is simple, a boolean session variable. The thing is there's one page for every action, but, i think is not elegant at all to ask in every action if the user is logged or not. How can i do this elegantly in a MVC architecture? It looks crappy this way. I was thinking that there is a parent action that redirects to the final one, the one that renders the page, is it right? maybe i could make the check there.
public function createAction(Request $request){
$sess = $this->getRequest()->getSession();
if ($sess->get('logged') == true) {
// ---- ACTION CODE GOES HERE ---- //
}
}
In Symfony2, if the sections of the site that need authorization are under the same path, you can use the access_control section in the security configuration:
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/secured/area, roles: ROLE_USER }
You can find more ways to secure your app in the book
In the last three days I've struggled trying to find a way to accomplish what I though was supposed to be a simple thing. Doing this on my own or searching for a solution in the web, didn't help. Maybe because I'm not even sure what to look for, when I do my researches.
I'll try to explain as much as I can here: maybe someone will be able to help me.
I won't say how I'm doing it, because I've tried to do it in many ways and none of them worked for different reasons: I prefer to see a fresh advice from you.
In most of the pages of web application, I have two links (but they could be more) like that:
Option A
Option B
This is partial view, retured by a controller action.
User can select or both (all) values, but they can't never select none of them: meaning that at least one must be always selected.
These links must che accessible in almost all pages and they are not supposed to redirect to a different page, but only to store this information somewhere, to be reused when action needs to filter returned contents: a place always accessible, regarding the current controller, action or user (including non authenticated users) (session? cookie?).
This information is used to filter displayed contents in the whole web application.
So, the problem is not how to create the business logi of that, but how (and where) to store this information:
without messing with the querystring (means: keeps the querystring as empty/clean as possible)
without redirecting to other pages (user must get the current page, just with different contents)
allow this information to persists between all views, until user click again to change the option(s)
My aim is to have this information stored in a model that will contains all options and their selection status (on/off), so the appropriates PartialView will know how to display them.
Also, I could send this model to the "thing" that will handle option changes.
Thanks.
UPDATE
Following Paul's advice, I've took the Session way:
private List<OptionSelectionModel> _userOptionPreferences;
protected List<OptionSelectionModel> UserOptionPreferences
{
get
{
if (Session["UserOptionPreferences"] == null)
{
_userOptionPreferences= Lib.Options.GetOptionSelectionModelList();
}
else
{
_userOptionPreferences= Session["UserOptionPreferences"].ToString().Deserialize<List<OptionSelectionModel>>();
}
if (_userOptionPreferences.Where(g => g.Selected).Count() == 0)
{
foreach (var userOptionPreferencesin _userOptionPreferences)
{
userOptionPreferences.Selected = true;
}
}
UserOptionPreferences= _userOptionPreferences;
return _userOptionPreferences;
}
private set
{
_userOptionPreferences= value;
Session["UserOptionPreferences"] = _userOptionPreferences.SerializeObject();
}
}
Following this, I've overridden (not sure is the right conjugation of "to override" :) OnActionExecuting():
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
GetOptionSelections();
base.OnActionExecuting(filterContext);
}
GetOptionSelections()...
private void GetOptionSelections()
{
if (String.IsNullOrEmpty(Request["optionCode"])) return;
var newOptionCode = Request["optionCode "];
foreach (var userOptionPreferencesin UserOptionPreferences)
{
if (userOptionPreferences.OptionCode == newOptionCode )
userOptionPreferences.Selected = !userOptionPreferences.Selected;
}
}
This code I think can be better, but right now I just want to make it work and it doesn't.
Maybe there are also other issues there (quite sure, actually), but I believe the main issue is that OnActionExecuting is called by each action in a controller that inherit from BaseController, therefore it keeps toggling userOptionPreferences.Selected on/off, but I don't know how to make GetOptionSelections() being called only once in each View: something like the old Page_Load, but for MVC.
Last update AKA solution
Ok, using the session way, I've managed to store this information.
The other issue wasn't really on topic with this question and I've managed to solve it creating a new action that take cares of handling the option's change, then redirects to the caller URL (using the usual returnUrl parameter, but as action parameter).
This way, the option change is done only once per call.
The only thing I don't really like is that I can't simply work with the UserOptionPreferences property, as it doesn't change the session value, but only the value in memory, so I have to set the property with the new object's status each time: not a big deal, but not nice either.
This is a place to use session.
The session will keep your setting between requests while keeping it out of the url querystring. It seems that you have probably tried this already, but try it again and if you have problems ask again. I think it will be the best way for you to solve this problem.