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
}
Related
I have an intranet application where all user operations are conducted by API calls to a remote system (no local tables). A couple of the API calls require the user's password. I can't really ask users to keep reentering their password as they use the site (sometimes seconds after they've just logged in).
So without saving their password to a database, where can I safely cache the password for the duration of the user's login (note: "login", not "session"). I tried storing them in the Session state, but the problem is the session only lasts 20 minutes but the login token is valid for 24 hours.
Ideally I want it linked (somehow) directly to the .AspNet.ApplicationCookie so the login and the cached password cannot get out of sync, but it doesn't see like it's possible to add custom values to that cookie. It can be encrypted if this cookie isn't already encrypted.
EDIT:
Due to the "remember me" function, logins can last much longer than the Session.TimeOut value, so I don't want to use the Session for this.
I had a project where I had to implement exactly the same and ended up with a custom implementation of the ASP.NET Identity interfaces. (In my case the usernames and passwords were managed by an external system with an API.)
I'll explain the idea and main parts of the code.
The required userinfo (eg. username and password) gets stored in memory in a ConcurrentDictionary within a custom IUserStore, by definition the place by which userinfo gets retrieved.
Note; I am going to skip security best practices.
The only place to have access to the password of a user is via the PasswordSignInAsync method of a custom SignInManager.
Here things get different!
In the default/regular flow, the SignInManager uses the IUserStore to retrieve userinfo in order to do the password check. But because the IUserStore's role changed into a passive memory store that isn't possible anymore; this initial lookup must be done via eg. a database lookup.
Then the SignInManager does the password check.
If valid, the userinfo gets added or updated into the custom IUserStore (via a custom method on the CustomUserStore.)
It is important also to do an update every time the user signs in, otherwise the password stays stale, as it is being kept in memory for the duration of the application.
In case the web application gets recycled and the userinfo in the Dictionary gets lost, the ASP.NET identity framework takes care of this by redirecting the user again to the login page, by which the above flow starts again.
Next requirement is a custom UserManager, as my IUserStore does not implement all interfaces required by ASP.NET Identity; see the comments in the code. This may be different for your case.
With all this in place you retrieve a CustomUser via the UserManager; with the user object holding the password:
CustomUser user = this._userManager.FindById(userName);
Here below are some extracts of the implementation.
The data that gets stored in memory:
public class UserInfo
{
String Password { get; set; }
String Id { get; set; }
String UserName { get; set; }
}
The custom IUser:
public class CustomUser : IUser<String>
{
public String Id { get; }
public String Password { get; set; }
public String UserName { get; set; }
}
The custom IUserStore with a method to write to it:
public interface ICustomUserStore : IUserStore<CustomUser>
{
void CreateOrUpdate(UserInfo user);
}
The custom UserStore:
public class CustomUserStore : ICustomUserStore
{
private readonly ConcurrentDictionary<String, CustomUser> _users = new ConcurrentDictionary<String, CustomUser>(StringComparer.OrdinalIgnoreCase);
public Task<CustomUser> FindByIdAsync(String userId)
{
// UserId and userName are being treated as the same.
return this.FindByNameAsync(userId);
}
public Task<CustomUser> FindByNameAsync(String userName)
{
if (!this._users.ContainsKey(userName))
{
return Task.FromResult(null as CustomUser);
}
CustomUser user;
if (!this._users.TryGetValue(userName, out user))
{
return Task.FromResult(null as CustomUser);
}
return Task.FromResult(user);
}
public void CreateOrUpdate(UserInfo userInfo)
{
if (userInfo != null)
{
this._users.AddOrUpdate(userInfo.UserName,
// Add.
key => new CustomUser { Id = userInfo.Id, UserName = userInfo.UserName, Password = userInfo.Password) }
// Update; prevent stale password.
(key, value) => {
value.Password = userInfo.Password;
return value
});
}
}
}
The custom UserManager:
public class CustomUserManager : UserManager<CustomUser>
{
public CustomUserManager(ICustomUserStore userStore)
: base(userStore)
{}
/// Must be overridden because ICustomUserStore does not implement IUserPasswordStore<CustomUser>.
public override Task<Boolean> CheckPasswordAsync(CustomUser user, String password)
{
return Task.FromResult(true);
}
/// Must be overridden because ICustomUserStore does not implement IUserTwoFactorStore<CustomUser>.
public override Task<Boolean> GetTwoFactorEnabledAsync(String userId)
{
return Task.FromResult(false);
}
/// Must be overridden because ICustomUserStore does not implement IUserLockoutStore<CustomUser>.
public override Task<Boolean> IsLockedOutAsync(String userId)
{
return Task.FromResult(false);
}
/// Must be overridden because ICustomUserStore does not implement IUserLockoutStore<CustomUser>.
public override Task<IdentityResult> ResetAccessFailedCountAsync(String userId)
{
Task.FromResult(IdentityResult.Success);
}
}
The custom SignInManager:
public class CustomSignInManager : SignInManager<CustomUser, String>
{
private readonly ICustomUserStore _userStore;
public CustomSignInManager(
CustomUserManager userManager,
IAuthenticationManager authenticationManager
ICustomUserStore userStore
)
: base(userManager, authenticationManager)
{
this._userStore = userStore;
}
/// Provided by the ASP.NET MVC template.
public override Task<ClaimsIdentity> CreateUserIdentityAsync(CustomUser user)
{
return user.GenerateUserIdentityAsync(this.UserManager);
}
public override Task<SignInStatus> PasswordSignInAsync(String userName, String password, Boolean isPersistent, Boolean shouldLockout)
{
UserInfo userInfo = // Call method the retrieve user info from eg. the database.
if (null == userInfo)
{
return Task.FromResult(SignInStatus.Failure);
}
// Do password check; if not OK:
// return Task.FromResult(SignInStatus.Failure);
// Password is OK; set data to the store.
this._userStore.CreateOrUpdate(userInfo);
// Execute the default flow, which will now use the IUserStore with the user present.
return base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout);
}
}
Disclaimer: Here you are putting a password into a cookie. An encrypted cookie, yet a password. It is not the best practice from security point of view. So make a decision yourself if this is acceptable for your system or not.
I think the best way for this would be to store the password as a claim on the authentication cookie. Auth cookie is encrypted when transmitted but you don't have to deal with the encryption yourself - this is done by OWIN for you. And this requires a lot less plumbing.
First rewrite your login action as follows:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindAsync(model.Email, model.Password);
if (user == null)
{
// user with this username/password not found
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
// BEWARE this does not check if user is disabled, locked or does not have a confirmed user
// I'll leave this for you to implement if needed.
var userIdentity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
userIdentity.AddClaim(new Claim("MyApplication:Password", model.Password));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, userIdentity);
return RedirectToLocal(returnUrl);
}
This takes password on login and adds it as a claim on the Identity that in turn gets serialised and encrypted into a cookie.
Note that a lot of logic has been omitted here - if you need to check if user is disabled, locked or without a confirmed email, you'll need to add that yourself. I suspect you won't need that as you mentioned that this is an internal only site.
Next you'll need an extension method to extract the password out:
using System;
using System.Security.Claims;
using System.Security.Principal;
public static class PrincipalExtensions
{
public static String GetStoredPassword(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
throw new Exception("Expecting ClaimsPrincipal");
}
var passwordClaim = claimsPrincipal.FindFirst("MyApplication:Password");
if (passwordClaim == null)
{
throw new Exception("Password is not stored");
}
var password = passwordClaim.Value;
return password;
}
}
That is pretty much it. Now in every action you can apply that method on User property:
[Authorize]
public ActionResult MyPassword()
{
var myPassword = User.GetStoredPassword();
return View((object)myPassword);
}
And corresponding view will be like this:
#model String
<h2>Password is #Model</h2>
However, depending on your requirements this password claim can be killed over time or preserved. Default Identity template enables SecurityStampInvalidator that is executed every 30 minutes on the cookie and rewrites it fresh from the DB. Usually ad-hoc claims added like this do not survive this rewrite.
To preserve the password value past 30 minutes of cookie age take this class:
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Security.Cookies;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
// This is mostly copy of original security stamp validator, only with addition to keep hold of password claim
// https://github.com/aspnet/AspNetIdentity/blob/a24b776676f12cf7f0e13944783cf8e379b3ef70/src/Microsoft.AspNet.Identity.Owin/SecurityStampValidator.cs#L1
public class MySecurityStampValidator
{
/// <summary>
/// Can be used as the ValidateIdentity method for a CookieAuthenticationProvider which will check a user's security
/// stamp after validateInterval
/// Rejects the identity if the stamp changes, and otherwise will call regenerateIdentity to sign in a new
/// ClaimsIdentity
/// </summary>
/// <typeparam name="TManager"></typeparam>
/// <typeparam name="TUser"></typeparam>
/// <param name="validateInterval"></param>
/// <param name="regenerateIdentity"></param>
/// <returns></returns>
public static Func<CookieValidateIdentityContext, Task> OnValidateIdentity<TManager, TUser>(
TimeSpan validateInterval, Func<TManager, TUser, Task<ClaimsIdentity>> regenerateIdentity)
where TManager : UserManager<TUser, string>
where TUser : class, IUser<string>
{
return OnValidateIdentity(validateInterval, regenerateIdentity, id => id.GetUserId());
}
/// <summary>
/// Can be used as the ValidateIdentity method for a CookieAuthenticationProvider which will check a user's security
/// stamp after validateInterval
/// Rejects the identity if the stamp changes, and otherwise will call regenerateIdentity to sign in a new
/// ClaimsIdentity
/// </summary>
/// <typeparam name="TManager"></typeparam>
/// <typeparam name="TUser"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <param name="validateInterval"></param>
/// <param name="regenerateIdentityCallback"></param>
/// <param name="getUserIdCallback"></param>
/// <returns></returns>
public static Func<CookieValidateIdentityContext, Task> OnValidateIdentity<TManager, TUser, TKey>(
TimeSpan validateInterval, Func<TManager, TUser, Task<ClaimsIdentity>> regenerateIdentityCallback,
Func<ClaimsIdentity, TKey> getUserIdCallback)
where TManager : UserManager<TUser, TKey>
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
if (getUserIdCallback == null)
{
throw new ArgumentNullException("getUserIdCallback");
}
return async context =>
{
var currentUtc = DateTimeOffset.UtcNow;
if (context.Options != null && context.Options.SystemClock != null)
{
currentUtc = context.Options.SystemClock.UtcNow;
}
var issuedUtc = context.Properties.IssuedUtc;
// Only validate if enough time has elapsed
var validate = (issuedUtc == null);
if (issuedUtc != null)
{
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
validate = timeElapsed > validateInterval;
}
if (validate)
{
var manager = context.OwinContext.GetUserManager<TManager>();
var userId = getUserIdCallback(context.Identity);
if (manager != null && userId != null)
{
var user = await manager.FindByIdAsync(userId);
var reject = true;
// Refresh the identity if the stamp matches, otherwise reject
if (user != null && manager.SupportsUserSecurityStamp)
{
var securityStamp =
context.Identity.FindFirstValue(Constants.DefaultSecurityStampClaimType);
if (securityStamp == await manager.GetSecurityStampAsync(userId))
{
reject = false;
// Regenerate fresh claims if possible and resign in
if (regenerateIdentityCallback != null)
{
var identity = await regenerateIdentityCallback.Invoke(manager, user);
if (identity != null)
{
var passwordClaim = context.Identity.FindFirst("MyApplication:Password");
if (passwordClaim != null)
{
identity.AddClaim(passwordClaim);
}
// Fix for regression where this value is not updated
// Setting it to null so that it is refreshed by the cookie middleware
context.Properties.IssuedUtc = null;
context.Properties.ExpiresUtc = null;
context.OwinContext.Authentication.SignIn(context.Properties, identity);
}
}
}
}
if (reject)
{
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
}
}
}
};
}
}
Note that this is a direct copy of original Identity code with minor modification to preserve the password claim.
And to activate this class, in your Startup.Auth.cs do this:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// use MySecurityStampValidator here
OnValidateIdentity = MySecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(10), // adjust time as required
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
Here is a working sample code
I am familiar with roles and authentication attributes in MVC, but as I am adding more and more information onto my database I think I ma going to run into a problem with primary keys being unencrypted or accessible.
I am using identity 2.1, so when a user is logged in I have access of their UserId and their CustomerID but my concern is that any user can go to /Customers/Delete/3 or any CustomerID and have access. Even if I created a GUID id or other encryption it could still be vulnerable to brute force attacks.
Is there a way in MVC to implement a check to only allow the current user to load pages that are related to them?
You can add extra field say "CreatedByUserId" to database table and when user access page check if CreatedByUserId matches with user id of logged in user or not.
You should be checking if the current logged in user has access to any of the information before you try and manipulate data. For example...
public async Task<HttpResponseMessage> DeleteCustomer(string customerId)
{
var appUser = await _authRepository.FindUser(User.Identity.GetUserName());
if(!_customerRepository.CanDeleteCustomer(appUser.Id, customerId){
return BadRequest();
}
// they have access so do what you need to do down here..
}
You can create a custom Authorize Attribute and a table in the database in which you store which user is allowed what Pages (Actions) or Controllers and then check that table while authorizing that whether the user is authorized for that Page/Controller. I have created an example for you in which I used Custom Authorize Attribute named MyAuthorizeAttribute and a database table named PageRoles.
Custom Authorize Attribute:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
readonly ApplicationDbContext _db = new ApplicationDbContext();
string _pageName;
public MyAuthorizeAttribute(string pageNameFromController)
{
_pageName = pageNameFromController;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var userId = httpContext.User.Identity.GetUserId();
var pageRoles = db.PageRoles.Where(m => m.UserId == userId);
foreach (var item in pageRoles)
{
if (item.PageName == _pageName && item.UserId == userId)
{
return base.AuthorizeCore(httpContext);
}
}
return false;
}
}
Model used:
public class PageRole
{
public int Id { get; set; }
public string UserId { get; set; }
public string PageName { get; set; }
public virtual ApplicationUser User { get; set; }
}
and then you will just have to use the attribute on your controllers just like you use Authorize attribute:
[MyAuthorize("Home")]
public class HomeController : Controller
{ }
In ASP.NET MVC Identity,the relations data for Users and Roles is saved in AspNetUserRoles table, this table has two field:UserId,RoleId, but i want to add other fields to this table, such as department field.
So if an user logins in different departments,he will have different roles.
Anyone knows how to do it? Thanks in advance!
I Would Suggest you investigate ASPNet User Claims. You can assign different claims to a user with the identity manager, and based on the claim type of the user you will allow him access or not. Create a custom Claims Attribute which will be placed on top of the various controller to authenticate the user. this must be implemented based on your needs. the custom attribute will then fire before the controller gets executed and if the uses is allowed he will pass. else return to error page of you choice.
Sample Attribute usage
[ClaimsAuthorize(ClaimsData.EditAddress)]
public ActionResult CitiesPartial()
Attribute Authentication
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _claimType;
public ClaimsAuthorizeAttribute(string type)
{
_claimType = type;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
if (user.HasClaim(_claimType, "True"))
{
base.OnAuthorization(filterContext);
}
else
{
HandleUnauthorizedRequest(filterContext, _claimType + " Not Allowed ");
}
}
protected void HandleUnauthorizedRequest(AuthorizationContext filterContext, string message)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "ClaimNotAuthorized" },
{ "controller", "Home" },
{"errorMessage", message }
});
}
public static bool AuthorizedFor(string claimType)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
return user.HasClaim(claimType, "True");
}
}
hope this helps.
I'm building a Asp.net MVC3 aplication (with Razor) and I have a Data Base that have information about users and roles.
This is simplified scheme of my DB.
User(IDUser, Login, Password);
Role(IDRole, Name);
UserInRole(IDUser, IDRole); //Many to Many
Looks like this:
I read about use AuthorizeAttribute, to control pages for loged users, and with specific roles and I research about use My DB to control users and roles. So my questions is:
Is possible use my DB to manage users and roles and use [Authorize] in my actions? [If yes how i do that?]
Is possible use session in the place of cookie to manage login and use the Authorization native Asp.net MVC3? [if yes, how i do that? if no how use session otherwise?]
If possible please post code examples.
Not sure if I understood correctly, but you want to use the [Authorize] attribute to work with your custom users database?
If that's the case, there are somethings to check:
To simply allow/deny based whether the user is authorized or not, the stock [Authorize] attribute will work just fine. The custom logic goes in your Login action, where you will check the database with the given credentials and issue the cookie accordingly. Something like:
public ActionResult Login(string username, string password)
{
bool isValid = //check the database with the given username and password
if(isValid)
{
FormsAuthentication.SetAuthCookie(username, false);
return RedirectToAction("...");
}else
{
return View();
}
}
If you want to also control access based on roles, I would say there are 2 immediate ways:
Implement a custom Membership and Role providers, something I don't like as I find them pretty useless, and always end up redoing the logic in my respositories
Implement a custom AuthorizeAttribute, like
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Check based on these 2 properties:
// this.Roles
// this.Users
//against httpContext.User
//return true or false to indicate access or deny
}
}
Thanks Pedro. Based in your post I build this to use SESSION:
public class CustomAutorizeAttribute : AuthorizeAttribute
{
public List<string> Roles { get; set; }
public CustomAutorizeAttribute()
{
}
public CustomAutorizeAttribute(params string[] Roles)
{
this.Roles = new List<string>();
this.Roles.AddRange(Roles);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
User user = (User)httpContext.Session["user"];
if (user != null)
{
if (Roles != null)
{
foreach (var role in user.Roles)
{
if (Roles.Exists(e => e == role)) return true;
}
return false; // User don't have any hole
}
else
{
return true; // The Attribute is used without roles.
}
}
else return false; // Not logged.
}
}
Post here to hope others.
I'm looking to secure different areas of my MVC application to prevent standard user's from accessing admin type views. Currently, if any user is logged in and they attempt to view the About page (out of the box template in visual studio), it will simply redirect them to the login page. I'd prefer the user is informed that they do not have permission to view the page.
[Authorize(Roles="Admin")]
public ActionResult About()
{
return View();
}
It seems redundant to send an already authenticated user to the login page when they don't have permission.
Here is an attribute that I've created that can be used to direct to an unauthorized security action. it also allows you to specify a Reason which will be passed to the Unauthorized action on the Security controller, which you can then use for the view.
You can create any number of properties to customize this to fit your particular application, just make sure to add it to the RouteValueDictionary.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public sealed class ApplySecurityAttribute : ActionFilterAttribute
{
private readonly Permission _permission;
public ApplySecurityAttribute(Permission permission)
: this(permission, string.Empty) {}
public ApplySecurityAttribute(Permission permission, string reason)
{
_permission = permission
Reason = reason;
}
public string Reason { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!PermissionsManager.HasPermission(_permission)) // Put security check here
{
var routeValueDictionary = new RouteValueDictionary
{
{ "controller", "Security" }, // Security Controller
{ "action", "Unauthorized" }, // Unauthorized Action
{ "reason", Reason } // Put the reason here
};
filterContext.Result = new RedirectToRouteResult(routeValueDictionary);
}
base.OnActionExecuting(filterContext);
}
}
Here is the security controller
public class SecurityController : Controller
{
public ViewResult Unauthorized(string reason)
{
var vm = new UnauthorizedViewModel { Reason = reason };
return View(vm);
}
}
Here is the attribute declaration on a controller you wish to secure
[ApplySecurity(Permission.CanNuke, Reason = "You are not authorized to nuke!")]
Here is how PermissionsManager does the check to see if the user has the permissions
public static class PermissionsManager
{
public static bool HasPermission(EZTracPermission permission)
{
return HttpContext.Current.GetCurrentUser().Can(permission);
}
}