MVC 3, Ninject 2.2.
I have a scenario where I need to temporarily override a binding. The override is just for the duration of an Action in a Controller.
What I need is something like this:
[HttpGet, Authorize(Users="MySpecialAccount")]
public ActionResult Report(string userName) {
var reportViewModel = new ReportViewModel();
using(var block = Kernel.BeginBlock() {
var principal = //load principal info based on userName;
block.Rebind<IMyPrincipal>().ToConstant(principal);
reportViewModel = GetViewModel(); //calls bunch of repos to hydrate view model that reference IMyPrincipal
}
return View(reportViewModel);
}
Background:
The application uses windows authentication. I have a a custom provider that loads a custom principal. We inject this custom principal into our repos/services/etc and help us load the appropriate data based on the authenticated user. That has all worked great for a long time. Now I have a scenario where I am using impersonation in one action. The reason is probably beyond scope, but basically I am using an HTMLToPDF writer that launches a separate process to load the HTML/Action under a different account. Anyways, because I am impersonating in this one action, all of my repos can't load the correct info since it is not the user that made the request, makes sense. So I send in a parameter of "who" we need to run the report for, and I need to Rebind the custom principal temporarily.
Hope this makes sense. Here are snippets of the current code that loads the custom principal.
In Global.asax:
protected void WindowsAuthentication_OnAuthenticate(Object source, WindowsAuthenticationEventArgs e)
{
if (e.Identity.IsAuthenticated)
{
//goes to db and loads additional info about logged on user. We use this info in repos/services to load correct data for logged on user.
var principal = new PrincipalFactory().GetPrincipal(e.Identity);
e.User = principal;
}
}
//Ninject Binding
Bind<IMyPrincipal>().ToProvider(new MyPrincipalProvider());
//Provider
public class MyPrincipalProvider : Provider<IMyPrincipal>
{
protected override IMyPrincipal CreateInstance(IContext context)
{
var principal = HttpContext.Current.User as IMyPrincipal;
return principal ?? new UnauthenticatedPrincipal(new GenericIdentity("Not Authenticated"));
}
}
Thanks for your help!
One possibility that comes to mind is to use a custom authorize attribute:
public class ImpersonateAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
return false;
}
string username = httpContext.User.Identity.Name;
// or if you wanted to load the username from the request:
// string username = httpContext.Request["username"];
IPrincipal principal = // load principal info based on username;
// Swap the principal for this action
httpContext.User = principal;
return true;
}
}
and then:
[HttpGet]
[ImpersonateAuthorize(Users="MySpecialAccount")]
public ActionResult Report(string userName)
{
// Here this.User will be the custom principal you loaded in
// the authorize attribute
var reportViewModel = new ReportViewModel();
return View(reportViewModel);
}
Another approach is to do this at your DI framework configuration level:
public class MyPrincipalProvider : Provider<IPrincipal>
{
protected override IPrincipal CreateInstance(IContext context)
{
var httpContext = HttpContext.Current;
var rd = httpContext.Request.RequestContext.RouteData;
var currentAction = rd.GetRequiredString("action");
var currentController = rd.GetRequiredString("controller");
if (string.Equals("report", currentAction, StringComparison.OrdinalIgnoreCase) &&
string.Equals("users", currentController, StringComparison.OrdinalIgnoreCase))
{
IPrincipal principal = // load principal info based on username;
return principal;
}
var principal = httpContext.User as IPrincipal;
return principal ?? new UnauthenticatedPrincipal(new GenericIdentity("Not Authenticated"));
}
}
Related
I'm struggling to inject a service (AuthenticationStateProvider) in a class in Blazor server. If I do it in a razor component, it is pretty simple:
#inject AuthenticationStateProvider AuthenticationStateProvider
and then
private async Task LogUsername()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
ClientMachineName = $"{user.Identity.Name}";
}
else
{
ClientMachineName = "Unknown";
}
}
However I need to do this, i.e. retrieve the authenticated user machine name, in a class instead of a razor component.
I tried for instance:
[Inject]
AuthenticationStateProvider AuthenticationStateProvider { get; set; }
public async Task LogUsername()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
ClientMachineName = $"{user.Identity.Name}";
}
else
{
ClientMachineName = "Unknown";
}
}
But this does not seem to work.
Any help would be much appreciated.
with Blazor server (.Net Core 3), this worked for me:
public class AuthTest
{
private readonly AuthenticationStateProvider _authenticationStateProvider;
public AuthTest(AuthenticationStateProvider authenticationStateProvider)
{
_authenticationStateProvider = authenticationStateProvider;
}
public async Task<IIdentity> GetIdentity()
{
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
return user.Identity;
}
}
You need to register this with the ASP.Net Core DI in Startup.ConfigureServices:
services.AddScoped<AuthTest>();
And then inject it on your .razor page:
#page "/AuthTest"
#inject AuthTest authTest;
<button #onclick="#LogUsername">Write user info to console</button>
#code{
private async Task LogUsername()
{
var identity= await authTest.IsAuthenticated();
Console.WriteLine(identity.Name);
}
You should see the logged-in username written to the ASP.Net output console.
Update
If you want to get the currently logged in user from within a separate class and you're not injecting that onto a blazor page, then follow the guidance here
Thanks again both #StephenByrne and #Dan - I'm almost there now with my requirements. This is my user service class and it works as expected:
public class AuthUser
{
private readonly AuthenticationStateProvider _authenticationStateProvider;
public AuthUser(AuthenticationStateProvider authenticationStateProvider)
{
_authenticationStateProvider = authenticationStateProvider;
var username = _authenticationStateProvider.GetAuthenticationStateAsync().Result;
FetchMyUser(username.User.Identity.Name);
}
public User MyUser { get; set; }
public void FetchMyUser(string machineName = "Unknown")
{
using (IDbConnection connection = new System.Data.SqlClient.SqlConnection(SettingsService.DBConnectionString2016))
{
MyUser = connection.QueryFirstOrDefault<User>($"SELECT FirstName FROM MyTable WHERE MachineName = '{machineName}' ;");
}
}
}
And then in Startup.cs I add this service as Scoped (this is important, as Dan pointed out below);
services.AddScoped<AuthUser>();
I can then use this service from a .razor component as follows:
#inject AuthUser authUser
Hello #authUser.MyUser.FirstName
The only remaining issue I have is that I don't know how to consume this service in another .cs class. I believe I should not simply create an object of that class (to which I would need to pass the authenticationStateProvider parameter) - that doesn't make much sense. Any idea how I could achive the same as I mentioned in the .razor file but in a .cs class instead ?
Thanks!
Check out the solution I had to this problem here:
Accessinging an authenticated user outside of a view in Blazor
This should solve your problem.
Edit: If you would like to get the information about the authentication state, what you should do is create a claim on the authentication state with the username or whatever detail you require in it, instead of creating a class and assigning the name to that. That way, in classes that need this information you can just inject a service class that gets all of the claims on the current authentication state. This really should all be done in a custom authentication state provider.
Example:
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
MyUser = //DB call to get user information
var claimsIdentity = new ClaimsIdentity(new[] { new
Claim(ClaimTypes.Name, MyUser.Name) }, "Authenticated");
var user = new ClaimsPrincipal(identity);
return new AuthenticationState(user);
}
Then in another service you would get the claims with the user information in it and inject that into any other service/class the information is needed.
public ApplicationUser(AuthenticationStateProvider authenticationStateProvider)
{
_authenticationStateProvider = authenticationStateProvider;
}
public async Task<string> GetLogin()
{
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
return authState.User.Claims.Where(c => c.Type == ClaimTypes.Name).FirstOrDefault().Value;
}
if you in your startup.cs add some services
services.AddScoped<TokenProvider>();
services.AddTransient<TokenRefreshService>();
services.Add<GraphServiceService>();
you can in a razor page inject them by their type
#inject TokenProvider _token
#inject TokenRefreshService _tokenrefresh
#inject GraphServiceService _graphservice
These service classes, you inject them in throught the constructor
public GraphServiceClass(AuthenticationStateProvider _AuthenticationStateProvider, TokenProvider _token)
{
AuthenticationStateProvider = _AuthenticationStateProvider;
token = _token;
}
I recommend this: ASP.NET Core Blazor dependency injection
I have created an enum with security access levels, an example:
public enum AccessLevel
{
Total,
DeletionPrivileges,
MaintainUsers,
MaintainInventory,
QueriesOnly,
None
}
I can manage the site so certain features eg delete, are not presented to someone without deletion privileges. But I am also wanting to use some kind of authorisation within the code.
Within the default framework, there is the facility to prevent access to certain areas of a project using [Authorize], how can I create differing levels of authority to tag each method?
You could use claim based authentication feature of Identity to aim this purpose easily. First you need add proper claim per user in log in action method to do this change your log in action method like this:
[HttpPost]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var userManager=HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
var user = userManager.Find(model.UserName, model.Password);
if (user != null)
{
var ident = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
// imaging you have a custom class which return user access levels
var userAccessLevels=_accessLevelManager.GetAccessLevels(user.Id);
// now we are going to add our custom claims
ident.AddClaims(new[]
{
// add each access level as a separate claim
new Claim("AccessLevel",userAccessLevels[0].ToString()),
new Claim("AccessLevel",userAccessLevels[1].ToString()),
// and so on
});
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);
// authentication succeed do what you want
return Redirect(login.ReturnUrl ?? Url.Action("Index", "Home"));
}
}
ModelState.AddModelError("", "Invalid username or password");
return View(login);
}
Now we have successfully injected our claims to Identity. But you need a custom authorize attribute to check your claims like this:
public class ClaimsAccessAttribute : AuthorizeAttribute
{
public string ClaimType { get; set; }
public string Value { get; set; }
protected override bool AuthorizeCore(HttpContextBase context)
{
return context.User.Identity.IsAuthenticated
&& context.User.Identity is ClaimsIdentity
&& ((ClaimsIdentity)context.User.Identity).HasClaim(x =>
x.Type == ClaimType && x.Value == Value);
}
}
Now you could easily use your attribute in your action methods:
[ClaimsAccess(CliamType="AccessLevel",Value="DeletionPrivileges")]
public ActionResult MyAction()
{
// also you have access the authenticated user's claims
// simply by casting User.Identity to ClaimsIdentity
// ((ClaimsIdentity)User.Identity).Claims
}
I am trying to implement a role authorization mechanism which checks the roles of the current logged in user, if the user is in the right role, he/she is allowed, else display error view.
The problem is that when the user tries to access the below method in the controller, he does get into the RoleAuthorizationAttribute class and gets verfied but then the method in the controller is not executed.
Note : the user has the Client role
Controller method
[RoleAuthorization(Roles = "Client, Adminsitrator")]
public ActionResult addToCart(int ProductID, string Quantity)
{
tempShoppingCart t = new tempShoppingCart();
t.ProductID = ProductID;
t.Username = User.Identity.Name;
t.Quantity = Convert.ToInt16(Quantity);
new OrdersService.OrdersClient().addToCart(t);
ViewData["numberOfItemsInShoppingCart"] = new OrdersService.OrdersClient().getNoOfItemsInShoppingCart(User.Identity.Name);
ViewData["totalPriceInSC"] = new OrdersService.OrdersClient().getTotalPriceOfItemsInSC(User.Identity.Name);
return PartialView("quickShoppingCart", "Orders");
}
Role Authentication class
[System.AttributeUsage(System.AttributeTargets.All,AllowMultiple = false, Inherited = true)]
public sealed class RoleAuthorizationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
List<String> requiredRoles = Roles.Split(Convert.ToChar(",")).ToList();
List<Role> allRoles = new UsersService.UsersClient().GetUserRoles(filterContext.HttpContext.User.Identity.Name).ToList();
bool Match = false;
foreach (String s in requiredRoles)
{
foreach (Role r in allRoles)
{
string rName = r.RoleName.Trim().ToString();
string sName = s.Trim();
if (rName == sName)
{
Match = true;
}
}
}
if (!Match)
{
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
base.OnAuthorization(filterContext);
}
}
Could you please tell me what I am doing wrong
Since I had the roles of the users in the database I had to check against the database so I included this method in the global.asax
protected void Application_AuthenticateRequest(object sender, EventArgs args)
{
if (Context.User != null)
{
IEnumerable<Role> roles = new UsersService.UsersClient().GetUserRoles(
Context.User.Identity.Name);
string[] rolesArray = new string[roles.Count()];
for (int i = 0; i < roles.Count(); i++)
{
rolesArray[i] = roles.ElementAt(i).RoleName;
}
GenericPrincipal gp = new GenericPrincipal(Context.User.Identity, rolesArray);
Context.User = gp;
}
}
Then I could use the normal
[Authorize(Roles = "Client, Administrator")]
On top of the actionResult methods in the controllers
This worked.
Your original code was close, but the problem lies here:
base.OnAuthorization(filterContext);
Unconditionally calling the base class means you are requiring the decorated roles to be found in BOTH the UsersService and the built-in Role provider. If the role provider isn't configured to return the same set of roles (which they wouldn't if the default AuthorizeAttribute isn't sufficient for you) then this will obviously result in the Authorization test always returning false.
Instead you could add a separate property to the derived Attribute such as
public string RemoteRoles { get; set; }
and replace
List<String> requiredRoles = Roles.Split(Convert.ToChar(",")).ToList();
with:
List<String> requiredRoles = RemoteRoles.Split(Convert.ToChar(",")).ToList();
And decorate your controller like such:
[RoleAuthorization (RemoteRoles = "Client, Administrator")]
If you're using MVC 5 you have to enable lazy loading in your DbContext by putting the following line in your DbContext initialisation.
this.Configuration.LazyLoadingEnabled = true;
In MVC 5 default project you'll add it to ApplicationDbContext.cs file.
I'm not sure if this is particular to MVC 5, to Identity 2.0, or affect other versions. I'm using this setup and enabling lazy loading make all the default role schema works. See https://stackoverflow.com/a/20433316/2401947 for more info.
Additionally, if you're using ASP.NET Identity 2.0 default permission schema, you don't have to implement Application_AuthenticateRequest as Darren mentioned. But if you're using custom authorisation tables, then you have to implement it as well.
I was wondering whether any people could help me please? I'm trying to create a site where the user logs in, it retrieves their chosen language from the database, and it uses that when setting the culture. There are also a number of settings about the user that would be retrieved at the same time as the user's language.
The culture/translations are handled via a base controller below (it's still a test version, but you will get the idea).
public abstract class BaseController : Controller
{
//public UserRegistrationInformation UserSession;
//public void GetUserInfo()
//{
// WebUsersEntities db = new WebUsersEntities();
// UserSession = db.UserRegistrationInformations.Where(r => r.uri_UserID == WebSecurity.CurrentUserId).FirstOrDefault();
//}
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
//GetUserInfo();
string cultureName = null;
// Change this to read from the user settings rather than a cookie
/// Attempt to read the culture cookie from Request
//HttpCookie cultureCookie = Request.Cookies["_culture"];
//if (cultureCookie != null)
// cultureName = cultureCookie.Value;
//else
cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages
//cultureName = "es-es";
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
return base.BeginExecuteCore(callback, state);
}
}
This was largely taken from http://afana.me/post/aspnet-mvc-internationalization-part-2.aspx
I've been searching for how to pass the user's settings to the _layout rather than just the view. I found an interesting post here Pass data to layout that are common to all pages that works for me, I've created a base ViewModel, and any other ViewModels are inheriting it.
public abstract class ViewModelBase
{
public string BrandName { get; set; }
public UserRegistrationInformation UserSession;
public void GetUserInfo()
{
WebUsersEntities db = new WebUsersEntities();
UserSession = db.UserRegistrationInformations.Where(r => r.uri_UserID == WebSecurity.CurrentUserId).FirstOrDefault();
}
}
To test with I've altered the existing change password model and control to:
public class LocalPasswordModel : ViewModelBase
{..........}
and
public ActionResult Manage(ManageMessageId? message)
{
//ViewModelAccounts vma = new ViewModelAccounts();
//vma.GetUserInfo();
LocalPasswordModel l = new LocalPasswordModel();
l.GetUserInfo();
l.BrandName = "blue";
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
: "";
ViewBag.HasLocalPassword = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
ViewBag.ReturnUrl = Url.Action("Manage");
return View(l);
}
Again this works perfectly, however I only want to retrieve the user's information the once. Currently I can do what I want by calling it in the BeginExecuteCore, and then again in the controller as above. How can I call this the once to be used everywhere? i.e. populate the BaseViewModel.
Thanks for any help or pointers you may be able to give!
Ok. I've finally solved this.
I'm, creating a base model that all of my other view-models are going to inherit from. It can also be called directly in case any view doesn't require its own view-model.
public class ViewModelBase
{
public UserSettings ProfileSettings;
// Create a new instance, so we don't need to every time its called.
public ViewModelBase()
{
ProfileSettings = new UserSettings();
}
}
public class UserSettings // UserSettings is only used here and consumed by ViewModelBase, its the name there that is used throughout the application
{
public string BrandName { get; set; }
public UserRegistrationInformation UserSession;
}
This is being generated in the basecontroller.
public abstract class BaseController : Controller
{
public ViewModelBase vmb = new ViewModelBase();
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
string cultureName = null;
int userid = 0;
if (System.Web.Security.Membership.GetUser() != null)
{
//logged in
userid = (int)System.Web.Security.Membership.GetUser().ProviderUserKey;
WebUsersEntities db = new WebUsersEntities();
vmb.ProfileSettings.UserSession = db.UserRegistrationInformations.Where(r => r.uri_UserID == userid).FirstOrDefault();
vmb.ProfileSettings.BrandName = "test";
cultureName = "es-es";
}
else
{
// not logged in
cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages
}
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
return base.BeginExecuteCore(callback, state);
}
}
The other controllers all inherit from this controller. If any screen has a dedicated view-model it can retrieve the information from the model populated in the controller like this:
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
LoginModel v = new LoginModel();
v.ProfileSettings = vmb.ProfileSettings;
ViewBag.ReturnUrl = returnUrl;
return View(v);
}
I hope that helps someone in the future.
I'd like [Authorize] to redirect to loginUrl unless I'm also using a role, such as [Authorize (Roles="Admin")]. In that case, I want to simply display a page saying the user isn't authorized.
What should I do?
Here is the code from my modified implementation of AuthorizeAttribute; I named it SecurityAttribute. The only thing that I have changed is the OnAuthorization method, and I added an additional string property for the Url to redirect to an Unauthorized page:
// Set default Unauthorized Page Url here
private string _notifyUrl = "/Error/Unauthorized";
public string NotifyUrl {
get { return _notifyUrl; } set { _notifyUrl = value; }
}
public override void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext)) {
HttpCachePolicyBase cachePolicy =
filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null);
}
/// This code added to support custom Unauthorized pages.
else if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (NotifyUrl != null)
filterContext.Result = new RedirectResult(NotifyUrl);
else
// Redirect to Login page.
HandleUnauthorizedRequest(filterContext);
}
/// End of additional code
else
{
// Redirect to Login page.
HandleUnauthorizedRequest(filterContext);
}
}
You call it the same way as the original AuthorizeAttribute, except that there is an additional property to override the Unauthorized Page Url:
// Use custom Unauthorized page:
[Security (Roles="Admin, User", NotifyUrl="/UnauthorizedPage")]
// Use default Unauthorized page:
[Security (Roles="Admin, User")]
Extend the AuthorizeAttribute class and override HandleUnauthorizedRequest
public class RoleAuthorizeAttribute : AuthorizeAttribute
{
private string redirectUrl = "";
public RoleAuthorizeAttribute() : base()
{
}
public RoleAuthorizeAttribute(string redirectUrl) : base()
{
this.redirectUrl = redirectUrl;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
string authUrl = this.redirectUrl; //passed from attribute
//if null, get it from config
if (String.IsNullOrEmpty(authUrl))
authUrl = System.Web.Configuration.WebConfigurationManager.AppSettings["RolesAuthRedirectUrl"];
if (!String.IsNullOrEmpty(authUrl))
filterContext.HttpContext.Response.Redirect(authUrl);
}
//else do normal process
base.HandleUnauthorizedRequest(filterContext);
}
}
Usage
[RoleAuthorize(Roles = "Admin, Editor")]
public class AccountController : Controller
{
}
And make sure you add your AppSettings entry in the config
<appSettings>
<add key="RolesAuthRedirectUrl" value="http://mysite/myauthorizedpage" />
</appSettings>
The easiest way I've found is to extend and customize the AuthorizeAttribute so that it does something different (i.e., not set an HttpUnauthorizedResult) when the Role check fails. I've written an article about this on my blog that you might find useful. The article describes much what you are wanting, though it goes further and allows the user who "owns" the data to also have access to the action. I think it should be fairly easy to modify for your purposes -- you'd just need to remove the "or owner" part.