We faced following problem. We developed set of post-login pages for our system. And users could have one of 5 roles: Free User Type 1, Premium User Type 1, Free User Type 2, Premium User Type 2 and Admin.
The problem is that even though all pages for every of these roles should look almost the same but it it is still a bit different depending on user role (for example links point to different URLs, or different modal are shown when buttons are clicked, some options are shown and some are hidden).
What we are trying to do is to wrap this all in small partial views and render different partial views depending on user role. But it gets more and more complex.
Maybe there is some kind of design pattern or general approach to deal with this problem?
Thanks!
Seems based on your description which is not 100% clear that the best approach would be to create the different partial views for the different roles.
Then on login get the user role from the DB, and return the different partial views based on the role.
If you have multiple partial views i.e. more than one page per user role, you can add the user role to a session or a cookie so you do not have to hit the DB again.
Id recommend using the cookie approach if this is the case.
Set Session
var userRole = 1;
Session["UserRole"] = userRole;
Get Session:
var userRole = Session["UserRole"] as int?;
Set Cookie
var cookie = new HttpCookie("UserRole");
cookie.Value = GetUserRole();
cookie.Expires = DateTime.Now.AddDays(1);
HttpContext.Current.Response.Cookies.Add(cookie);
Get Cookie
if (HttpContext.Current.Request.Cookies["UserRole"] != null)
{
var userRole = HttpContext.Current.Request.Cookies["UserRole"].Value);
}
Related
So I have multiple websites running off one code base (asp.net standard MVC). I'm using the built in ASPNet.Identity methods for users to register/log in (using ApplicationSignInManager, ApplicationUserManager).
Currently, all websites are using a single database to store user information. This is causing a couple of issues:
When user A registers on website A, they are now able to log into website B with the same details. As far as they are aware, they did not register on website B. Not good!
If I constrain user A to only access website A, if that user then tried to register on website B, they get the 'email address already in use' error. Also not good!
I've tried separating the databases, one per site, to get around this issue but I don't know how to dynamically change the DBContext assigned to the ApplicationSignInManager and ApplicationUserManager in my controller.
For example, when a user comes to website A, I grab the connection string for website A and perform login/register actions. I need the domain name to work out which connection string to load, which I can't access until after startup.cs code has run, configuring my manager instances.
I figure other people must have done this. Ideally I need to dynamically change the DBContext AFTER Startup.cs has run. Failing that, I need a nice approach to storing multiple identical email addresses in the same DB
Thanks
If you have a flag somewhere which website the current context is for, i'd say easiest to achieve that is to do two things:
extend the IdentityUser with a Website property, something simple either just an int WebsiteId or a String.
extend the AccountController to use that property wherever needed, I think you'd need to modify "Register" and all "Login" functions to verify the website the account is for.
I managed to find a solution. It's not the most elegant but it solves the issue until I can figure out how to dynamically change the DB Context used during login/register
In IdentityConfig.cs I switched 'RequireUniqueEmail' to false:
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = false
};
Then when a user registers, I take the email address, make it unique to the website they registered on, and store it in the UserName field (I don't use the UserName for anything). When they log in, I make the same alteration to the entered email address before attempting login.
If this user registers on a different website, the username will be different even though the email is identical. Not perfect, but it works.
E.g.
string uniqueCode = "Website_Specific_String";
var user = new ApplicationUser { uniqueCode + model.Email, Email = model.Email};
var result = await UserManager.CreateAsync(user, model.Password);
and the login
var user = await UserManager.FindByNameAsync(uniqueCode + model.Email);
Still open to better ideas. Thanks
currently dealing with an interesting problem. I am building a website with three different user roles. When logged in, the MVC partial view shows navigation options for the users. I want to show different options depending on the user's role. In previous websites, I have used the following code to determine a role:
#if (Roles.IsUserInRole("intern"))
{
<li>#Html.ActionLink("Log Time", "Index", "Time")</li>
}
Unfortunately, when I attempted this in my current code, I got the message:
The Role Manager feature has not been enabled.
So apparently in the new MVC they disable the role manager by default and have a new way of doing it. No biggie. Searching the issue suggested that I enable the feature in web.config. I followed several instructions on how to do that (I promise I can google search) but it seems to mess with my SQL Server connection string, giving me errors that indicate it's trying to log in to a local db that doesn't exist rather than my Azure SQL Server. I've played around for a while and I don't know why this is the case.
Anyway, long story short, rather than work around and re-enable a vestigial Identity feature, how are you supposed to accomplish this in the new MVC? I can get the roles fine controller side with user manager, but I can't use that in a view. Similarly a Viewbag full of roles can't work because this is navigation on every page.
I appreciate all the help in advance, thanks everyone!
Just found the answer, I'll leave this up for other people dealing with this. The correct way to do this is:
#if (User.IsInRole("intern"))
This makes sense since MVC is moving away from Role based objects and towards User based objects.
I think it's not really a good idea to ask for the user's role all the time, too many requests are made.
It would be better to ask once and save it on a variable in Razor. Then just check that variable whenever you need it.
By the way, if the roles are different, you don't even want to ask if the user is in that role rather than another one. Rather get the list of roles in a list and check if the role indicated is in the list.
Example (I'm not sure it will compile, look a the idea):
#using Microsoft.AspNetCore.Identity
#using Microsoft.AspNetCore.Mvc.Localization
#inject UserManager<ApplicationUser> UserManager
#{
ApplicationUser currentUser = await UserManager.GetUserAsync(User);
var roles = await UserManager.GetRolesAsync(currentUser);
bool isIntern = roles.Contains("intern");
bool isExtern = roles.Contains("extern");
bool isFoo = roles.Contains("foo");
...
}
then, further on
#if (isIntern)
{
<li>#Html.ActionLink("Log Time", "Index", "Time")</li>
}
else if (isExtern)
{
...
}
You can control the role of the user as many times as you want without having to make other requests and it's all much more readable.
I'm developing an aspnet mvc application and i have a problem to reload some data, i don't know if the problem is in the security principal info that every view contains or in my partial view that doesn't reload when some other view is shown.
I have a layout page that is used in my whole application, and inside this layout page i have this partial view code fragment:
#Html.Partial("_PartialActiveClient")
this partial view contains the next lines:
#if (User.IsInRole("HasClients"))
{
<p>An Active Client</p>
Change active client
}
else
{
<p>You don't have any clients<p>
Create client
}
So when a user creates an account for the first time, this user has 0 clients and doesn't belong to the "HasClients" role and the "you don't have any clients" message is shown, but when this user creates his first client, the user is added to the "HasClients" role by default in my controller. The problem is that the "you don't have any clients" is still there even if i change the views. In order for the other message to show, this user has to log out and then log in again.
So my question is, how can my users see the "An Active Client" message immediately after they create a client?
The roles are cached in a cookie , so you can force them to refresh by deleting the cookie. You need to refresh users role. Here is a ways
you can call this Roles.DeleteCookie(); and it will clear roles and reload it in the next request.
This is taken from this question page Refresh ASP.NET Role Provider
Just to clarify my comment to the answer provided by Tchaps. This is the code (not my original code, but make my point) to my solution, like i wrote in the comment, it's not elegant but i works, at least in my case.
// Add to role, save changes
await _userManager.AddToRoleAsync(userInSession.Id, roleHaveClients.Name);
await _userManager.UpdateAsync(userInSession);
// Sign out / Sing in
_registerManager.AuthenticationManager.SignOut();
await __registerManager.SignInAsync(userInSession, false, false);
// Redirect to another action
return RedirectToAction("someAction","someController");
I have searched through many SO and other pages but still couldn't find a similar case.
My application is using SimpleMembership. It has role-based user menu and most actions are with [Authorize(Roles = "aaa,bbb")] attribute.
After the application build or leaving the browser idle over some time, user will be redirected to login page from whatever link is clicked. Strange thing is that the login name is still valid to be shown but its without the role names it attached.
Users are able to access actions with [Authorize] attribute in the above situation. I am wondering if there is anything I am missing, or I can do to keep the role information alive as long as the authentication is valid?
Here is code from the controller action to render the menu:
public ActionResult Menu()
{
if (Roles.IsUserInRole("Admin"))
{
return View("MenuAdmin");
}
if (Roles.IsUserInRole("Advising"))
{
return View("MenuAdvising");
}
if (Roles.IsUserInRole("StudentDevelopment"))
{
return View("MenuStudentDevelopment");
}...
Customized _logonPartial to display the login name and roles
> #if (Request.IsAuthenticated) {
<text>
Logged in: #Html.ActionLink(User.Identity.Name, "Manage", "Account", routeValues: null, htmlAttributes: new { #class = "username", title = "Manage" }) as
#String.Join(",", Roles.GetRolesForUser(User.Identity.Name))
#using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" })) {
#Html.AntiForgeryToken()
Log off
}
</text>
The role information is available as long as the user is logged in. In your case you are calling:
Roles.GetRolesForUser(User.Identity.Name)
to get the list of roles. This actually hits the database to get the roles, so as long us the user name is available in Identity it will return roles, assuming that the user has roles assigned to them. And the user name should be available if the user id authenticated. You are checking Request.IsAuthenticated. It is probably better to check User.Identity.IsAuthenticated.
There is a way to get the roles without querying the database using the claims. And in general I would not put such logic and access to system variables in your Views. I would use this approach in my controller action to get the roles.
var prinicpal = (ClaimsPrincipal)Thread.CurrentPrincipal;
var roles = prinicpal.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value);
ViewBag.Roles = roles;
Either put the information you need in your Views in the the ViewBag or create a model that is passed to the View. This approach for getting roles does not require a round trip to the database.
Update to Address Comments
The standard AuthorizeAttribute for MVC 4 uses the configured membership and role provider (in your case SimpleMembership) to get the roles. It does not assume claims where used so it goes to the database every time. In fact it has to go the database twice, once to get the user ID for the logged in user and then to get the roles based on that user ID. Not very efficient. Surprisingly it does this even if you do not pass in any roles as parameters, where it does not need to authorize the user against roles and should just authenticate. I verified this with a profiler.
To make it more efficient and not require going to the database you could write a custom AuthorizeAttribute to use claims instead. Here is an article that describes how to do this, with links to example source code.
I cannot explain the scenario you are describing, which occurs right after a build. But if you create a custom attribute and use it instead you will be able to debug and see what exactly is going on. Plus I would not worry too much about it as it should not be a typical scenario you would see in production.
I am new in ASP.NET 4.0 and C#..If I want hide/show menu item based on user logged in using web.sitemap, I must use a role and set it in web.config..I want to ask, where I can get that role?
if (User.IsInRole("rolename")) {
// what you wan't to do.
}
If you are really new to ASP.NET, you need to learn about Users and Roles. Try to use Membership API with standard elements like "Login". After that you have to write your own Users and Roles Provider with custom data structure. Then, use #Randolf R-F statement.
You need to use IPrincipal to store roles.
GenericIdentity userIdentity = new GenericIdentity((FormsIdentity)HttpContext.Current.User.Identity.Name);
string[] roles = { "rolename1", "rolename2", "rolename3" };
GenericPrincipal userPrincipal = new GenericPrincipal(userIdentity, roles);
Context.User = userPrincipal;
then you can check for user roles
if (User.IsInRole("rolename1")) {
// what you wan't to do.
}
If I understand correctly, you are saying that you have an asp.net web application project open and you want to know how to create a user and assign a role to them.
If you currently have web.config configured to use the default provider for rolemanager and membership provider then asp.net will take care of all the tricky stuff. The only things you have to do is go to your menu bar and select "Project -> ASP.NET Configuration". This will bring up a GUI for creating users and roles then assigning them. It should be a pretty self explanatory tool. That should meet your web.config use requirement too.
As suggested above from here if you have the user logged in you can do things like:
if (User.IsInRole("rolename"))
{ // what you wan't to do. }
Alternatively (and best for you atm) you should try using the logInView control in your toolbox.(I think that's what it's called) Do some googling on using these controls and it'll get you across the line.