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.
Related
I can log a user out of my MVC Core application as follows:
public IActionResult Logout()
{
return SignOut("Cookies", "oidc");
}
Identity server is called and the user is logged out as expected. Is it possible to log a user in by specifying a link? I realise the user will be automatically forwarded if they access a secure area of the website. However, is it possible for the user to say themselves that they wish to login (the return url would be the homepage i.e. home/Index).
I have spent the last hour researching this. I have found lots of examples explaining how to logout explicitly, however I have not found an example, which shows how to login explicitly.
Your question isn't really clear, but if I understand you properly, you're looking for the returnUrl query param. For example, if you want to send a user to /foo, but you want them to login in first, you can create a link like:
Go to Foo, but login first
The question is really unclear, but I will try:
At your Account controller (which is the secure area, right?) create a secured "Login" action.
[Authorized] //actually you don't need this attribute here when have it on controller level
//All this action does is authenticating the user and redirecting to home/Index
public ActionResult AutoLogin()
{
return RedirectToAction("Index", "Home");
}
Then in your layout add:
#Html.ActionLink(
"Login", "AutoLogin", "Account", routeValues: new{}, htmlAttributes: new {id = "loginLink"})
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);
}
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 find lots of information about Identity but nothing specifically addressing this very common scenario.
I have a controller named ShowAccount() that should display the account data of the currently logged in user, and prevent him from seeing anything but its own account.
Also unauthenticated users should not be able to access this functionality at all.
How do I achieve this?
Thanks
Unauthenticated Users
K, I'll start with the simpler request, to block unauthenticated user from having access at all to your controller just add this attribute:
[Authorize]
above your controller, or if you want to allow some\disable some functions in the controller you can place it above the specific function.
In case you want to block your entire controller and allow just a few functions you can use this attribute:
[AllowAnonymous]
Limit user access to his own data
I'm doing something similar in one of my project so I thought it might help, nothing fancy, I would love to hear a better option myself.
For your 2nd issue, I assume that you have a model that stores data and that data has some kind relation to the UserID (foreign key maybe?).
What you can do is in your controler - filter the data you send back to the user, i.e on the view instead of returning:
return View(db.MyDB.ToList());
return:
MyDBClass data = db.MyDB.Where(u => u.UserID == GetUserID()).ToList();
return View(data);
Assume GetUserID() is a function that gives you the current user ID, in case you use the default authentication in MVC I can share it here as well.
This solution tho is not complete, you need to continue enforcing it in any other actions such as edit\delete\create or what ever other actions you support, you need to always check that the user is accessing only his data by comparing between the userID saved in the DB to the one in the request.
Hope this helps.
I had a similar challenge but I got mine
public ActionResult Create()
{
return View();
}
// POST: ArtistGig/Create
[HttpPost]
public ActionResult Create(ArtistGig artistGig)
{
var userid = User.Identity.GetUserId();
///
var artist = db.ArtistHubs.SingleOrDefault(a => a.ApplicationUserId == userid).Id;
artistGig.ArtistHubId = artist;
db.ArtistGigs.Add(artistGig);
db.SaveChanges();
return RedirectToAction("Index");
}
User.Identity.GetUseId is to query for the loged in user's Id according to the DbContext you are using
I want to give the user access according to the user role.
I have two user roles. they are Admin and user.
I write my controller like this.
[Authorize(Roles = "Admin")] // my Problem is here. I don't know how to set the current user role
public ActionResult Index()
{
var query = from company in db.tblCompanies
select company;
return View(query.ToList());
}
But I don't know how to set the Roles = "Admin" after cutomer login.
In my user tale I have Roles coloum and it can save Admin or user.
But I don't know how to set and where should I set Roles = "Admin".
Based on your question what I get is you want to set the currently logged user to some role. So here is my answer to that.
To Add a User to a Role:
Roles.AddUserToRole(userName, roleName);
To Remove a User from a Role:
Roles.RemoveUserFromRole(userName, roleName);
Reference Links:
SO - How to Assign roles to User while creating their account
MSDN - Implementing a Custom Role Provider
MSDN - Roles.AddUserToRole Method
MSDN - Roles.RemoveUserFromRole Method
Take a look at this :
http://www.asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-7
You basically assign roles to your users via the ASP.NET Configuration website. Once done the user - role mapping is handled by default.