I don't know what I am missing, and I don't know what else to read to get it right. I will try this gray question to see if I get closer to the solution. I am building a .NET MVC application.
This application is authenticating with OpenID using DotNetOpenAuth Library, all that is working ok. Once a user is authenticate I rebcord the openid token in the database and create call the forms authentication like below.
FormsAuthentication.SetAuthCookie(confirmedUser.OpenID, false);
After that this user pass all authorize attribute in my code. Like below:
[Authorize]
public ActionResult About()
{
return View();
}
I don't know where to set the roles for a specific user. I am not using the Membership services.
I need to get working the attributes like below:
[Authorize(Roles="Administrator")]
public ActionResult About()
{
return View();
}
First of all, good for you for not using a membership provider. That just doesn't work well with OpenID.
To make roles work without a membership provider, you need to implement your own class that derives from System.Web.Security.RoleProvider. It's completely departed from authentication, which makes it easy for you. You just need to store with each of your users in your database which roles they belong to, and then your RoleProvider interacts with that database.
Once you write your role provider class, wire it up with this in your web.config file. This snippet should appear within your system.web section.
<roleManager enabled="true" defaultProvider="Database">
<providers>
<add name="Database" type="MyRoleProvider" />
</providers>
</roleManager>
Here's one role provider I wrote for an OpenID web application. It's written using Linq to Entities, but you can get the idea and implement it to work against your database.
public class MyRoleProvider : RoleProvider {
public override string ApplicationName {
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames) {
var users = from token in Global.DataContext.AuthenticationToken
where usernames.Contains(token.ClaimedIdentifier)
select token.User;
var roles = from role in Global.DataContext.Role
where roleNames.Contains(role.Name, StringComparer.OrdinalIgnoreCase)
select role;
foreach (User user in users) {
foreach (Role role in roles) {
user.Roles.Add(role);
}
}
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) {
var users = from token in Global.DataContext.AuthenticationToken
where usernames.Contains(token.ClaimedIdentifier)
select token.User;
var roles = from role in Global.DataContext.Role
where roleNames.Contains(role.Name, StringComparer.OrdinalIgnoreCase)
select role;
foreach (User user in users) {
foreach (Role role in roles) {
user.Roles.Remove(role);
}
}
}
public override void CreateRole(string roleName) {
Global.DataContext.AddToRole(new Role { Name = roleName });
}
/// <summary>
/// Removes a role from the data source for the configured applicationName.
/// </summary>
/// <param name="roleName">The name of the role to delete.</param>
/// <param name="throwOnPopulatedRole">If true, throw an exception if <paramref name="roleName"/> has one or more members and do not delete <paramref name="roleName"/>.</param>
/// <returns>
/// true if the role was successfully deleted; otherwise, false.
/// </returns>
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) {
Role role = Global.DataContext.Role.SingleOrDefault(r => r.Name == roleName);
if (role == null) {
return false;
}
if (throwOnPopulatedRole && role.Users.Count > 0) {
throw new InvalidOperationException();
}
Global.DataContext.DeleteObject(roleName);
return true;
}
/// <summary>
/// Gets an array of user names in a role where the user name contains the specified user name to match.
/// </summary>
/// <param name="roleName">The role to search in.</param>
/// <param name="usernameToMatch">The user name to search for.</param>
/// <returns>
/// A string array containing the names of all the users where the user name matches <paramref name="usernameToMatch"/> and the user is a member of the specified role.
/// </returns>
public override string[] FindUsersInRole(string roleName, string usernameToMatch) {
return (from role in Global.DataContext.Role
where role.Name == roleName
from user in role.Users
from authTokens in user.AuthenticationTokens
where authTokens.ClaimedIdentifier == usernameToMatch
select authTokens.ClaimedIdentifier).ToArray();
}
public override string[] GetAllRoles() {
return Global.DataContext.Role.Select(role => role.Name).ToArray();
}
public override string[] GetRolesForUser(string username) {
return (from authToken in Global.DataContext.AuthenticationToken
where authToken.ClaimedIdentifier == username
from role in authToken.User.Roles
select role.Name).ToArray();
}
public override string[] GetUsersInRole(string roleName) {
return (from role in Global.DataContext.Role
where string.Equals(role.Name, roleName, StringComparison.OrdinalIgnoreCase)
from user in role.Users
from token in user.AuthenticationTokens
select token.ClaimedIdentifier).ToArray();
}
public override bool IsUserInRole(string username, string roleName) {
Role role = Global.DataContext.Role.SingleOrDefault(r => string.Equals(r.Name, roleName, StringComparison.OrdinalIgnoreCase));
if (role != null) {
return role.Users.Any(user => user.AuthenticationTokens.Any(token => token.ClaimedIdentifier == username));
}
return false;
}
public override bool RoleExists(string roleName) {
return Global.DataContext.Role.Any(role => string.Equals(role.Name, roleName, StringComparison.OrdinalIgnoreCase));
}
}
I'm still learning this stuff as well, but you probably need to create a custom authorization attribute. Check this out.
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 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
}
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've set up my menu using MVCSiteMap and I have this node:
<mvcSiteMapNode title="Courses Form" controller="Booking" action="Course" roles="CORLIC, VIEWCOBO"/>
I'm trying to enforce that this node must have roles "CORLIC" AND "VIEWCOBO" for it to be visible but of course this means that it will be displayed if the user has either of the above.
Is this possible?
Thanks.
The roles attribute is for interoperability with ASP.NET and should not be used in an MVC-only application.
For MVC, if you are already defining the AuthorizeAttribute on your controller actions, MvcSiteMapProvider will automatically pick them up and hide the matching nodes accordingly if security trimming is enabled.
[Authorize]
public ActionResult Course()
{
return View();
}
[Authorize]
[HttpPost]
public ActionResult Course(CourseModel model)
{
if (ModelState.IsValid)
{
// Implementation omitted
}
// If we got this far, something failed, redisplay form
return View(model);
}
The default AuthorizeAttribute accepts roles, but it works in the same way as the roles attribute - that is, any role that the user is in will cause it to succeed.
However, you could inherit AuthorizeAttribute yourself and override the IsAuthorized method to change the logic as needed.
public class SpecialAuthorizeAttribute : AuthorizeAttribute
{
private string _requiredRoles;
private string[] _requiredRolesSplit = new string[0];
/// <summary>
/// Gets or sets the required roles. The user must be a member of all roles for it to succeed.
/// </summary>
/// <value>
/// The roles string.
/// </value>
/// <remarks>Multiple role names can be specified using the comma character as a separator.</remarks>
public string RequiredRoles
{
get { return _requiredRoles ?? String.Empty; }
set
{
_requiredRoles = value;
_requiredRolesSplit = SplitString(value);
}
}
/// <summary>
/// Determines whether access for this particular request is authorized. This method uses the user <see cref="IPrincipal"/>
/// returned via <see cref="HttpRequestContext.Principal"/>. Authorization is denied if the user is not authenticated,
/// the user is not in the authorized group of <see cref="Users"/> (if defined), or if the user is not in any of the authorized
/// <see cref="Roles"/> (if defined).
/// </summary>
/// <param name="actionContext">The context.</param>
/// <returns><c>true</c> if access is authorized; otherwise <c>false</c>.</returns>
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw new ArgumentNullException("actionContext");
}
IPrincipal user = actionContext.ControllerContext.RequestContext.Principal;
if (user == null || user.Identity == null || !user.Identity.IsAuthenticated)
{
return false;
}
// Ensure all of the roles in RequiredRoles are present.
if (_requiredRolesSplit.Length > 0 && !_requiredRolesSplit.All(user.IsInRole))
{
return false;
}
// Call the base class to check the users and roles there.
return base.IsAuthorized(actionContext);
}
/// <summary>
/// Splits the string on commas and removes any leading/trailing whitespace from each result item.
/// </summary>
/// <param name="original">The input string.</param>
/// <returns>An array of strings parsed from the input <paramref name="original"/> string.</returns>
internal static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
Then you can specify which roles are required by using the new property.
[SpecialAuthorize(RequiredRoles = "CORLIC, VIEWCOBO")]
public ActionResult Course()
{
return View();
}
[SpecialAuthorize(RequiredRoles = "CORLIC, VIEWCOBO")]
[HttpPost]
public ActionResult Course(CourseModel model)
{
if (ModelState.IsValid)
{
// Implementation omitted
}
// If we got this far, something failed, redisplay form
return View(model);
}
Another possible option is to use FluentSecurity as shown here. For FluentSecurity v2.0 to work with MvcSiteMapProvider, you need to copy the HandleSecurityAttribute code into your project and change it to inherit from AuthorizeAttribute instead of Attribute, then use it as specified in the FluentSecurity documentation.
I have a controller and I want two roles to be able to access it. 1-admin OR 2-moderator
I know you can do [Authorize(Roles="admin, moderators")] but I have my roles in an enum. With the enum I can only authorize ONE role. I can't figure out how to authorize two.
I have tried something like [Authorize(Roles=MyEnum.Admin, MyEnum.Moderator)] but that wont compile.
Someone once suggested this:
[Authorize(Roles=MyEnum.Admin)]
[Authorize(MyEnum.Moderator)]
public ActionResult myAction()
{
}
but it doesn't work as an OR. I think in this case the user has to be part of BOTH roles. Am I overlooking some syntax? Or is this a case where I have to roll my own custom authorization?
Here is a simple and elegant solution which allows you to simply use the following syntax:
[AuthorizeRoles(MyEnum.Admin, MyEnum.Moderator)]
When creating your own attribute, use the params keyword in your constructor:
public class AuthorizeRoles : AuthorizeAttribute
{
public AuthorizeRoles(params MyEnum[] roles)
{
...
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
...
}
}
This will allow you to use the attribute as follows:
[AuthorizeRoles(MyEnum.Admin, MyEnum.Moderator)]
public ActionResult myAction()
{
}
Try using the bit OR operator like this:
[Authorize(Roles= MyEnum.Admin | MyEnum.Moderator)]
public ActionResult myAction()
{
}
If that doesn't work, you could just roll your own. I currently just did this on my project. Here's what I did:
public class AuthWhereRole : AuthorizeAttribute
{
/// <summary>
/// Add the allowed roles to this property.
/// </summary>
public UserRole Is;
/// <summary>
/// Checks to see if the user is authenticated and has the
/// correct role to access a particular view.
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
// Make sure the user is authenticated.
if (!httpContext.User.Identity.IsAuthenticated)
return false;
UserRole role = someUser.Role; // Load the user's role here
// Perform a bitwise operation to see if the user's role
// is in the passed in role values.
if (Is != 0 && ((Is & role) != role))
return false;
return true;
}
}
// Example Use
[AuthWhereRole(Is=MyEnum.Admin|MyEnum.Newbie)]
public ActionResult Test() {}
Also, make sure to add a flags attribute to your enum and make sure they are all valued from 1 and up. Like this:
[Flags]
public enum Roles
{
Admin = 1,
Moderator = 1 << 1,
Newbie = 1 << 2
etc...
}
The left bit shifting gives the values 1, 2, 4, 8, 16 and so on.
Well, I hope this helps a little.
I combined a few of the solutions here to create my personal favorite. My custom attribute just changes the data to be in the form that SimpleMembership expects and lets it handle everything else.
My roles enum:
public enum MyRoles
{
Admin,
User,
}
To create roles:
public static void CreateDefaultRoles()
{
foreach (var role in Enum.GetNames(typeof(MyRoles)))
{
if (!Roles.RoleExists(role))
{
Roles.CreateRole(role);
}
}
}
Custom attribute:
public class AuthorizeRolesAttribute : AuthorizeAttribute
{
public AuthorizeRolesAttribute(params MyRoles[] allowedRoles)
{
var allowedRolesAsStrings = allowedRoles.Select(x => Enum.GetName(typeof(MyRoles), x));
Roles = string.Join(",", allowedRolesAsStrings);
}
}
Used like so:
[AuthorizeRoles(MyRoles.Admin, MyRoles.User)]
public ActionResult MyAction()
{
return View();
}
Try
public class CustomAuthorize : AuthorizeAttribute
{
public enum Role
{
DomainName_My_Group_Name,
DomainName_My_Other_Group_Name
}
public CustomAuthorize(params Role[] DomainRoles)
{
foreach (var domainRole in DomainRoles)
{
var domain = domainRole.ToString().Split('_')[0] + "_";
var role = domainRole.ToString().Replace(domain, "").Replace("_", " ");
domain=domain.Replace("_", "\\");
Roles += ", " + domain + role;
}
Roles = Roles.Substring(2);
}
}
public class HomeController : Controller
{
[CustomAuthorize(Role.DomainName_My_Group_Name, Role.DomainName_My_Other_Group_Name)]
public ActionResult Index()
{
return View();
}
}
Here's my version, based on #CalebHC and #Lee Harold's answers.
I've followed the style of using named parameters in the attribute and overridden the base classes Roles property.
#CalebHC's answer uses a new Is property which I think is unnecessary, because AuthorizeCore() is overridden (which in the base class uses Roles) so it makes sense to use our own Roles as well. By using our own Roles we get to write Roles = Roles.Admin on the controller, which follows the style of other .Net attributes.
I've used two constructors to CustomAuthorizeAttribute to show real active directory group names being passed in. In production I use the parameterised constructor to avoid magic strings in the class: group names are pulled from web.config during Application_Start() and passed in on creation using a DI tool.
You'll need a NotAuthorized.cshtml or similar in your Views\Shared folder or unauthorized users will get an error screen.
Here is the code for the base class AuthorizationAttribute.cs.
Controller:
public ActionResult Index()
{
return this.View();
}
[CustomAuthorize(Roles = Roles.Admin)]
public ActionResult About()
{
return this.View();
}
CustomAuthorizeAttribute:
// The left bit shifting gives the values 1, 2, 4, 8, 16 and so on.
[Flags]
public enum Roles
{
Admin = 1,
User = 1 << 1
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly string adminGroupName;
private readonly string userGroupName;
public CustomAuthorizeAttribute() : this("Domain Admins", "Domain Users")
{
}
private CustomAuthorizeAttribute(string adminGroupName, string userGroupName)
{
this.adminGroupName = adminGroupName;
this.userGroupName = userGroupName;
}
/// <summary>
/// Gets or sets the allowed roles.
/// </summary>
public new Roles Roles { get; set; }
/// <summary>
/// Checks to see if the user is authenticated and has the
/// correct role to access a particular view.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <returns>[True] if the user is authenticated and has the correct role</returns>
/// <remarks>
/// This method must be thread-safe since it is called by the thread-safe OnCacheAuthorization() method.
/// </remarks>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
if (!httpContext.User.Identity.IsAuthenticated)
{
return false;
}
var usersRoles = this.GetUsersRoles(httpContext.User);
return this.Roles == 0 || usersRoles.Any(role => (this.Roles & role) == role);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
filterContext.Result = new ViewResult { ViewName = "NotAuthorized" };
}
private IEnumerable<Roles> GetUsersRoles(IPrincipal principal)
{
var roles = new List<Roles>();
if (principal.IsInRole(this.adminGroupName))
{
roles.Add(Roles.Admin);
}
if (principal.IsInRole(this.userGroupName))
{
roles.Add(Roles.User);
}
return roles;
}
}
To add to CalebHC's code and answer ssmith's question about handling users who have multiple roles...
Our custom security principal returns a string array representing all the groups/roles that a user is in. So first we have to convert all the strings in the array that match items in the enum. Finally, we look for any match - if so, then the user is authorized.
Note that we're also redirecting an unauthorized user to a custom "NotAuthorized" view.
The whole class looks like this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
/// <summary>
/// Add the allowed roles to this property.
/// </summary>
public Roles Is { get; set; }
/// <summary>
/// Checks to see if the user is authenticated and has the
/// correct role to access a particular view.
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
if (!httpContext.User.Identity.IsAuthenticated)
return false;
var iCustomPrincipal = (ICustomPrincipal) httpContext.User;
var roles = iCustomPrincipal.CustomIdentity
.GetGroups()
.Select(s => Enum.Parse(typeof (Roles), s))
.ToArray();
if (Is != 0 && !roles.Cast<Roles>().Any(role => ((Is & role) == role)))
{
return false;
}
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
filterContext.Result = new ViewResult { ViewName = "NotAuthorized" };
}
}
Or you could concatenate like:
[Authorize(Roles = Common.Lookup.Item.SecurityRole.Administrator + "," + Common.Lookup.Item.SecurityRole.Intake)]