Route user to correct area - c#

When a user logs in. I'm looking for a way to route a user to the correct area based on their role in identity.
I've tried:
You have the default controller that you can redirect to that area return RedirectToAction("Index", "Dashboard", new { area = "Admin" });,
but I have multiple roles.
Add a rewrite option in the StartUp Configure method. Which works in the beginning, but if you have a link to another area - it doesn't work.
On the default controller I created a view. Added some razor #if (User.Identity.IsAuthenticated && User.IsInRole("Admin")) else if(). Then used <text>and <script> tags in between the if statements to call a function that would redirect the user. It didn't provide an error, just didn't work. I figured it wouldn't, but... trying to think outside the box
Thanks ahead for the ideas!

If you are using asp.net core identity,in controller, you could directly use var isInRole = User.IsInRole("Admin") to check whether current user has an Admin role.
Or use UserManager to get current user and all his roles:
private readonly UserManager<IdentityUser> _userManager;
public HomeController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
[Authorize(Roles = "Admin")]
public async Task<IActionResult> TestView()
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var roles = await _userManager.GetRolesAsync(user);
var matchingvalues = roles.SingleOrDefault(stringToCheck => stringToCheck.Equals("Admin"));
if(matchingvalues != null)
{
return RedirectToAction("Index", "Dashboard", new { area = "Admin" });
}
return View();
}

Related

Blazor role based authentication

There are a lot of resources online for using roles to authenticate users in a blazor application. (example : https://visualstudiomagazine.com/articles/2019/10/25/authorizing-users-in-blazor.aspx)
What's frustrating me is that none of them cover how to add users to specific role groups. If I wanted to say, authenticate everyone under a specific domain .. say all google logins with the address #example.ca
Does anyone know how to do this? Or even where to explicitly type out admin emails to add them to a specific Role group?
What's frustrating me is that none of them cover how to add users to
specific role groups.
This question has nothing to do with Blazor.
Here is how you can add a registering user to a role:
[Route("api/[controller]")]
[ApiController]
public class AccountsController : ControllerBase
{
private static UserModel LoggedOutUser = new UserModel { IsAuthenticated = false };
private readonly UserManager<IdentityUser> _userManager;
public AccountsController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]RegisterModel model)
{
var newUser = new IdentityUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(newUser, model.Password);
if (!result.Succeeded)
{
var errors = result.Errors.Select(x => x.Description);
return BadRequest(new RegisterResult { Successful = false, Errors = errors });
}
// Add all new users to the User role
await _userManager.AddToRoleAsync(newUser, "User");
// Add new users whose email starts with 'admin' to the Admin role
if (newUser.Email.StartsWith("admin"))
{
await _userManager.AddToRoleAsync(newUser, "Admin");
}
return Ok(new RegisterResult { Successful = true });
}
}
}
See source and more here
If I wanted to say, authenticate everyone under a specific domain ..
say all google logins with the address #example.ca
Again, this question has nothing to do with Blazor. This is a candidate for using a policy-based authentication with the requirement you've mentioned above. See the docs how to implement this, and don't hesitate to ask for help if needed.
Hope this helps...

ASP.Net Core MVC Identity - Add temporary (session) claim

For starters, this question was also asked here (Temporary Session Based Claims in ASP.NET Core Identity) and here (How to add claim to user dynamically?), but neither question received an answer so I am trying to revive it...
I am creating a multi-tenant web app. A user logs in, and they can see data related to their own company (but not the data of other companies who use the app). Because of franchise groups with multiple storefronts, etc, it is not uncommon for a single user to require access to several different companies, but in this case, they must choose a single company when logging in.
Nearly all data queries require a company ID as a parameter, so I need a convenient way of checking which company the user is currently logged into. If they log out and log into a different company, I need to see a different company ID. I would like to store it as an identity claim that I can see for the duration of the session, but I don't necessarily want to store it to the database since it may change with every login.
Here is my Login action (based on the standard ASP.Net Core MVC template):
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
// BEGIN CUSTOM CODE - Check which companies a user can access
IEnumerable<int> allCompanies = _myDbService.GetUserCompanies(model.Email);
if (allCompanies.Count() == 1)
{
// then they can only access one company - log them into it automatically.
// I need easy access to its ID with the User since it is used with almost every db query.
int companyID = allCompanies[0];
((ClaimsIdentity)User.Identity).AddClaim(new Claim("CompanyID", companyID.ToString())); // this shows as null on future controller actions.
Debug.WriteLine("Set breakpoint here to examine User"); // I see 1 claim (my custom claim) in the debugger, but it is not in the database yet.
// future controller actions show me 3 claims (nameidentifier, name, and security stamp) but User.Claims.First(c => c.Type == "CompanyID").Value throws error.
return RedirectToLocal(returnUrl);
}
else
{
// the user has access to several companies - make them choose one to complete login process
RedirectToAction("ChooseCompany", new { companyList = allCompanies });
}
// END CUSTOM CODE
}
// REMAINING CODE OMITTED FOR BREVITY
}
// If we got this far, something failed, redisplay form
return View(model);
}
So my questions are: why doesn't the custom claim "stick" so that it can be seen in future controller actions? Why isn't it saved to the database if that is the standard behavior? Is there a way to keep this value around for the duration of the session without using AddSession()? If I implement this as a claim, am I making a round trip to the database every time I access the value?
For anyone else with this issue, here's where I am so far... To permanently store the claim in the database and automatically load it at login, you must use the following and the change will not take effect until the next login:
await userManager.AddClaimAsync(user, new Claim("your-claim", "your-value"));
Using the following will only store the claim for the current session, so I was on the right track with that:
((ClaimsIdentity)User.Identity).AddClaim(new Claim("your-claim", "your-value"));
The only way I have found to get the claim to "stick" between controller actions is to override UserClaimsPrincipalFactory so that ASP.Net is using your custom code as part of the login process. The problem here is that you only have access to the ApplicationUser in the relevant method, and you can't really pass in any custom parameters AFAIK. So I first modified by ApplicationUser class like this:
public class ApplicationUser : IdentityUser
{
public long ActiveDealershipID { get; set; }
}
(You must apply the EF migration). Now I create a class that derives from UserClaimsPrincipalFactory like this:
public class MyClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public MyClaimsPrincipalFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options) : base(userManager, roleManager, options)
{
}
public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
long dealershipID = user.ActiveDealershipID;
((ClaimsIdentity)principal.Identity).AddClaim(new Claim("DealershipID", dealershipID.ToString()));
return principal;
}
}
(You must register the service in startup.cs ConfigureServices):
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, MyClaimsPrincipalFactory>();
And then in my Login action, I set the new user property from the model data BEFORE the sign-in so that it will be available to the UserClaimsPrincipalFactory when it is setting the claims:
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
ApplicationUser user = await _userManager.FindByEmailAsync(model.Email);
user.ActiveDealershipID = model.ChosenDealership
await _userManager.UpdateAsync(user);
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
...
I am still open to better suggestions or other ideas, but this seems to be working for me.

ASP.NET MVC: How to default to a page given certain condition?

I have a class called PasswordChangeChecker.csthat has a method that returns from the database whether or not a user has changed their password. The signature for that method is:
public bool IsPasswordChangedFromInitial(string IdOfUser)
where IdOfUser is the Id field from the Identity framework User. If it returns true, that means the change password page should not be displayed, otherwise, it should navigate to the change password form. Once the user successfully changes their password, the database flag gets set appropriately, and they should not be prompted for a password change again (unless manually forced to by an admin). How can I put this method in the RouteConfig.cs file, where currently I have:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace IdentityDevelopment
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
How can I add a conditional construct into the defaults parameter so I can use the IsPasswordChangedFromInitial method to decide whether or not to go to the password change page? That page is at /Account/ChangePassword.
EDIT
As per the comments, the appropriate action methods for my specific need are (I have omitted irrelevant code):
Login post action:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginModel details, string returnUrl)
{
if (ModelState.IsValid)
{
AppUser user = await UserManager.FindAsync(details.Name,
details.Password);
if (user == null)
{
AppUser userByName = await UserManager.FindByNameAsync(details.Name);
if(userByName == null)
{
ModelState.AddModelError("", "Invalid username.");
}
else
{
//If this else is reached, it means the password entered is invalid.
//code for incrementing count of failed attempts and navigation to lock out page if needed
}
}
else
{
if(user.LockedOut)
{
//navigate to locked out page
}
else
{
PasswordChangeChecker PassCheck = new PasswordChangeChecker();
string userId = user.Id.ToString();
bool proceed = PassCheck.IsPasswordChangedFromInitial(userId);
//Authorize the user
ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
ident.AddClaims(LocationClaimsProvider.GetClaims(ident));
ident.AddClaims(ClaimsRoles.CreateRolesFromClaims(ident));
AuthManager.SignOut();
AuthManager.SignIn(new AuthenticationProperties
{
IsPersistent = false
}, ident);
//persist login into db code
if (proceed)
{
//reset failed logins count
return Redirect(returnUrl);
}
else
{
return ChangePassword();
}
}
}
}
ViewBag.returnUrl = returnUrl;
return View(details);
}
ChangePassword() get action:
[HttpGet]
[Authorize]
public ActionResult ChangePassword()
{
return View();
}
Somehow the view returned is the view in the RouteConfig.cs instead of the ChangePassword.cshtml page.
Thank you.
I would do it with global action filters
you can make a action filter with method
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (showChagePwPage)
{
//redirect to the change password page
filterContext.Result = new RedirectToActionResult("ChangePassword", "Account");
}
base.OnActionExecuting(filterContext);
}
and then adding it into global action filter by
GlobalFilters.Filters.Add(yourFilterContext);
After several days of this hellish exercise similar to yours, where I was trying to route users at login, I realized that I wasn't going to be able to get the value of the UserId while in the login of the Account controller. I did some experimenting and came up with this approach that solved my problem.
I create an ActionResult in my Home controller and called it Purgatory (of course I renamed it to something more suitable once it proved functional). There, I stuffed all my login logic for routing the logged-in user to their respective page upon login.
Then, in the RedirectToLocal in Account controller, I changed the
return RedirectToAction("Index", "Home");
to
return RedirectToAction("Purgatory", "Home");
So now when a user logs in, if the returnTo param isn't set to a particular page, the returnTo param will be null and when it gets to the RedirectToLocal, it'll drop to what used to be the redirect to the home page, which will now go into purgatory.
This sounds like a good time to use an action filter, which you can apply globally or per controller/action.
Here's a simple example:
using System;
using System.Web.Mvc;
using System.Web.Routing;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class VerifyPasswordChangedAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(!filterContext.ActionDescriptor.ActionName.Equals("changepassword", StringComparison.OrdinalIgnoreCase))
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
var userName = filterContext.HttpContext.User.Identity.Name;
PasswordChangeChecker PassCheck = new PasswordChangeChecker();
if (!PassCheck.IsPasswordChangedFromInitial(userName))
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "account", action = "changepassword", area = string.Empty }));
}
}
}
base.OnActionExecuting(filterContext);
}
}
I would modify your IsPasswordChangedFromInitial method to simply use the authenticated user's username, rather than trying to figure out how to get access to a UserManager instance in an action filter. Otherwise, assuming you're using the OWIN-based ASP.NET Identity, add a claim to store that user.Id field when you create your ClaimsIdentity, so that you don't have to keep looking it up.
The outermost conditional handles the case of this being a global filter - without it, you would get an infinite redirect.

How to get current user for all actions in a controller?

This is a two-parter
Question 1 (the real question)
I have a DashboardController that is not tied to a model. A User must be logged in before they can access the dashboard. How can I run a check to see if a user is authenticated before every action is executed, and redirect them to the login view if not? I think OnActionExecuted is what I want, but I am not sure what the exact implementation should be. Am I on the right track here?
public class DashboardController : Controller
{
private ApplicationContext db = new ApplicationContext();
//
// GET: /Admin/
public ActionResult Index()
{
var categories = db.Categories.ToList();
return View(categories);
}
public ActionResult Product(int id)
{
var product = db.Products.Find(id);
return View(product);
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if(Session["current_user"] == null)
{
// This (obviously) doesn't work - what should go here?
return RedirectToAction("Create", "Session");
}
base.OnActionExecuted(filterContext);
}
}
Question 2
If the user IS logged in, what is the right way to make the user accessible in all of these views? I have been told ViewBag is generally a bad idea - what should I use?
I can authorize controllers and actions by follow this link:
It's in brazilian portuguese originally, but the link below is translated to english.
https://translate.google.com.br/translate?sl=pt&tl=en&js=y&prev=_t&hl=pt-BR&ie=UTF-8&u=http%3A%2F%2Fdevbrasil.net%2Fprofiles%2Fblogs%2Fautentica-o-e-permiss-es-de-usu-rios-em-asp-net-mvc-4&edit-text=&act=url
You can get the logged user in views by
#HttpContext.Current.User.Identity.Name
PS: Sorry my bad english
Use [Authorize] atrribute.
For example:
[AcceptVerbs(HttpVerbs.Get)]
[Authorize]
public ActionResult add()
{
}
Then in the web.config
<authentication mode="Forms">
<forms name="my_cookie_name" loginUrl="~/login" defaultUrl="~/" timeout="2880"/>
</authentication>
If the user is not authenticated, it will get redirected automatically to the login page.
If you want something simple to control the identity of your users check the highest rated answer here: ASP.NET MVC - Set custom IIdentity or IPrincipal. It's a brilliant example. I use somthing similar in all my projects.
In my login action:
var user = _userService.system_login(systemlogin_model_post.email, systemlogin_model_post.password); // my user model
//... doing all sorts of validations
// once everyone is happy I create a cookie
Response.Cookies.Add(UserCookie.GetCookie(user));
Than using the code from the link above I create cookie:
public static class UserCookie
{
public static HttpCookie GetCookie(User user)
{
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel { user_id = user.UserId, username = user.Username, roles = user.Roles ,session_token = GUIDGenerator.ToAlphaNumerical() };
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,
user.UserId.ToString(),
DateTime.Now,
DateTime.Now.AddMinutes(30),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
return new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
}
}
When [Authorize] is fired this code takes care of it:
Global.asax
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.user_id = serializeModel.user_id;
newUser.username = serializeModel.username;
newUser.roles = serializeModel.roles;
newUser.form_token = serializeModel.form_token;
HttpContext.Current.User = newUser;
}
}
1) Authorize attribute of ASP.NET MVC is totally focused on your first problem. You may even go for customization but not suggested for most of scenarios.
2) To assign the currently logged in user and visible in all the views, you may bind the user name using ViewBag/ViewModel property to Layout(_Layout.cshtml) so that it appears on top of every page where the layout is used.
Note: If you want to perform any pre action-invoke logic, then OnActionExecuting filter is the correct place, before entering that action method.
Precisely, you have to create a class and that class inherits the Controller class.
public class MyAuthentication : Controller
{
public MyAuthentication()
{
isAuthenticated();
}
private void isAuthenticated()
{
// do your authentication
//if user authenticated keep user details within a cookie, so that
// when the next request, program logic will search for cookie,
//if it is found then assume user was authenticated else redirect to login page.
}
}
And then inherits this MyAuthentication class in your project for all controllers
public class DashboardController : MyAuthentication
{
public ActionResult Index()
{
var categories = db.Categories.ToList();
return View(categories);
}
// rest of the codes
}
So that the authentication remains in single place. You can inherit this where ever you want.
If you need current user anywhere in the controller/action then better way is to set the user data when you perform authorization.
In your authorization filter , you can use
System.Web.HttpContext.Current.Items["userdata"]=userDataObject;
For authentication , this article can help.
http://www.dotnet-tricks.com/Tutorial/mvc/G54G220114-Custom-Authentication-and-Authorization-in-ASP.NET-MVC.html
First Put the Form Authentication Cookie when the user is logged in. like
[HttpPost]
public ActionResult Login(Acccount obj){
// check whether the user login is valid or not
if(UseIsValid){
FormsAuthentication.SetAuthCookie(obj.username, obj.RememberMe);
return redirectToAction("Index","DashBoard");
}
return View(obj);
}
*
and Use [Authorize] attribute. like*
[Authorize]
public class DashboardController : Controller
{
private ApplicationContext db = new ApplicationContext();
public ActionResult Index()
{
var categories = db.Categories.ToList();
return View(categories);
}
}

Caching user information once user logs-in

I have users with different Roles. I want to deliver restricted view according to roles of the users. I have something in my Code which checks roles:
bool isAdmin = UserManager.IsInRole(currentUser.Id,"admin");
bool isEmployee = UserManager.IsInRole(currentUser.Id,"employee");
For above code to work, I need to instantiate currentUser. In other words I need to catch the information of the current user that is logged in. I tried something like var user = User.Identity.GetUserId(); and many other but can't find the working code. I would appreciate any help. Thank you!
Update:
here's my complete method code. y'all might wanna see until i check the above example.
public ActionResult Index()
{
var user = User.Identity.GetUserId();
bool isAdmin = UserManager.IsInRole(user, "admin");
bool isEmployee = UserManager.IsInRole(user, "employee");
if (isAdmin)
{
return View(db.Customers.ToList());
}
else
{
var restricted = db.Customers.Where(c => c.FirstName == "John");
return View(restricted);
}
}
[Authorize] attribute should be implemented in the desired restricted actionController method. The example is below.
[Authorize(Roles="Admin")]
public ActionResult Index()
{
return View();
}
This controller method is limited to the User with Role Admin. Furthermore, same action controller method could be included twice with different authorize tag.
I somehow figured out the solution. Here's the working code.
if(User.IsInRole("Admin"))
{
return View(db.Customers.ToList());
}
else
{
return View(db.MyUserInfo.ToList());
}

Categories