MvcSiteMapProvider not picking up roles for menu - asp.net-mvc-5

I am using MVC5, windows authentication, structuremap DI and custom role provider.
MvcSiteMapProvider for MVC5 is not picking up the roles to show menu based on user role. When security trimming is enabled it shows only menu items which do not have any roles attribute defined.
I have ActionFilterAttribute implemented and used on controller for authorization. Controller correctly redirects the user to unauthorized page based on roles but menu is not picking the role attributes to hide the menu.
Custom RoleProvider has implementation for GetRolesForUser and GetUsersInRole.
Any suggestion will be helpful.
Also want to know where to look for roles attributes in SiteMapNodeModel. I am thinking of customizing to look for permission in the HtmlHelper while building the menu.
Note: Same implementation is working fine in MVC4. Once upgraded to MVC5 and it does not work.
Thanks

MvcSiteMapProvider for MVC5 is not picking up the roles to show menu based on user role. When security trimming is enabled it shows only menu items which do not have any roles attribute defined.
As per the documentation, the roles attribute is not for use with MVC. It is for interop with ASP.NET pages.
It only functions when the security framework implements IPrincipal and IIdentity as Membership and Identity do.
I have ActionFilterAttribute implemented and used on controller for authorization. Controller correctly redirects the user to unauthorized page based on roles but menu is not picking the role attributes to hide the menu.
This is most likely your issue. Security trimming only looks for AuthorizeAttribute and subclasses of AuthorizeAttribute. If you have subclassed ActionFilterAttribute, it will not be used to hide links from navigation.
Of course, for the standard AuthorizeAttribute to work, you need to implement IPrincipal and IIdentity or use one of the pre-built security frameworks that does the same.
Alternatively, you could build your own IAclModule or ISiteMapNodeVisibilityProvider if you have completely custom security.
Also want to know where to look for roles attributes in SiteMapNodeModel. I am thinking of customizing to look for permission in the HtmlHelper while building the menu.
You would not need to look for roles from the SiteMapNodeModel. Instead, you should get the roles from the current context and make the changes to the menu templates in /Views/Shared/DisplayTemplates/ accordingly.
If you are using a framework that supports IPrincipal and IIdentity, you could just use:
#if (User.IsInRole("SomeRole"))
{
...
}
Also see the following:
ASP.NET MVC 5 Customise Bootstrap navbar based on User Role
Implementing Role Based Menu in ASP.NET MVC 4
If you want to get the current roles that are configured for an action method, you can build an extension method to read the roles from the current AuthorizeAttribute. Again, the roles attribute is only for interoperability with ASP.NET and should not be used for pure MVC, since it means you need to duplicate your roles on AuthorizeAttribute anyway.
public static class ControllerContextExtensions
{
public static IEnumerable<string> Roles(this ControllerContext controllerContext)
{
var controllerType = controllerContext.Controller.GetType();
var controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
var actionName = controllerContext.RouteData.Values["action"] as string;
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
var authorizeAttribute = FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor)
.Where(f => typeof(AuthorizeAttribute).IsAssignableFrom(f.Instance.GetType()))
.Select(f => f.Instance as AuthorizeAttribute).FirstOrDefault();
string[] roles = { };
if (authorizeAttribute != null && authorizeAttribute.Roles.Length > 0)
{
roles = Array.ConvertAll(authorizeAttribute.Roles.Split(','), r => r.Trim());
}
return roles;
}
}
Usage
In view:
{ var roles = this.ViewContext.Controller.ControllerContext.Roles(); }
In controller:
var roles = this.ControllerContext.Roles();
To get the roles from the SiteMapNodeModel:
var siteMap = MvcSiteMapProvider.SiteMaps.Current;
var siteMapNode = siteMap.FindSiteMapNodeFromKey(SiteMapNodeModel.Key);
var roles = siteMapNode.Roles;

Related

Inline equivalent of AuthorizeForScopesAttribute

We have a controller that we have been developing that uses the Microsoft.Identity.Web.AuthorizeForScopes attribute for the controller for example
[AuthorizeForScopes(Scopes = new string[]
{
"https://ourdomain.co.uk/us.theapi/access",
})]
public class BrowseController : Controller
This has worked well until we now. We need to deploy the set of web apis to production. The apis will have a new Azure AD application registration for production. That will mean we'll need to "Add a scope" in the "Expose an API" section of the Azure Portal. The new Azure AD application has a new Application ID URI (Azure does not allow it to use the one that was used by the devlopment application registration).
That App ID URI forms part of the scope string so if our new app id is "https://ourdomain.co.uk/us.prod-theapi/" and the scope is still "access" then the attribute in the code would need to be "https://ourdomain.co.uk/us.prod-theapi/" however that is no good for development.
Is there a way of checking for scopes that gets a similar result to the one obtained by AuthorizeForScopes attribute but without using an Attribute? We need to be able to drive the scope string used by a configuration property
Based on the source code, you can actually define a configuration section key instead of defining the scopes inline.
E.g.:
[AuthorizeForScopes(ScopeKeySection = "Auth:Scopes")]
public class BrowseController : Controller
And in appsettings JSON:
{
"Auth": {
"Scopes": "https://ourdomain.co.uk/us.theapi/access"
}
}
You can then have a different value for that configuration value depending on the environment or through environment variables etc.

Get Azure AD Groups Before Building Authorization Policies

We're developing an application that uses a back-end built on .Net Core 2.2 Web API. Most of our controllers merely require the [Authorize] attribute with no policy specified. However, some endpoints are going to require the user to be in a particular Azure AD Security Group. For those cases, I implemented policies like this in the Startup.cs file:
var name = "PolicyNameIndicatingGroup";
var id = Guid.NewGuid; // Actually, this is set to the object ID of the group in AD.
services.AddAuthorization(
options =>
{
options.AddPolicy(
name,
policyBuilder => policyBuilder.RequireClaim(
"groups",
id.ToString()));
});
Then, on controllers requiring this type of authorization, I have:
[Authorize("PolicyNameIndicatingGroup")]
public async Task<ResponseBase<string>> GroupProtectedControllerMethod() {}
The problem is that our users are all in a large number of groups. This causes the Graph API to return no group claims at all, and instead a simple hasGroups boolean claim set to true. Therefore, no one has any groups, and thus cannot pass authorization. This no-groups issue can be read about here.
This string-based policy registration, lackluster as it may be, seems to be what the .Net Core people are recommending, yet it falls flat if the groups aren't populated on the User Claims. I'm not really seeing how to circumnavigate the issue. Is there some special way to set up the AppRegistration for my API so that it does get all of the groups populated on the User Claims?
Update:
In the solution, I do have a service that calls Graph to get the user's groups. However, I can't figure out how to call it before it's too late. In other words, when the user hits the AuthorizeAttribute on the controller to check for the policy, the user's groups have not yet been populated, so the protected method always blocks them with a 403.
My attempt consisted of making a custom base controller for all of my Web API Controllers. Within the base controller's constructor, I'm calling a method that checks the User.Identity (of type ClaimsIdentity) to see if it's been created and authenticated, and, if so, I'm using the ClaimsIdentity.AddClaim(Claim claim) method to populate the user's groups, as retrieved from my Graph call. However, when entering the base controller's constructor, the User.Identity hasn't been set up yet, so the groups don't get populated, as previously described. Somehow, I need the user's groups to be populated before I ever get to constructing the controller.
I found an answer to this solution thanks to some tips from someone on the ASP.NET Core team. This solution involves implementing an IClaimsTransformation (in the Microsoft.AspNetCore.Authentication namespace). To quote my source:
[IClaimsTransformation] is a service you wire into the request pipeline which will run after every authentication and you can use it to augment the identity as you like. That would be where you’d do your Graph API call [...]."
So I wrote the following implementation (see an important caveat below the code):
public class AdGroupClaimsTransformer : IClaimsTransformation
{
private const string AdGroupsAddedClaimType = "adGroupsAlreadyAdded";
private const string ObjectIdClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
private readonly IGraphService _graphService; // My service for querying Graph
private readonly ISecurityService _securityService; // My service for querying custom security information for the application
public AdGroupClaimsTransformer(IGraphService graphService, ISecurityService securityService)
{
_graphService = graphService;
_securityService = securityService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = principal.Identity as ClaimsIdentity;
var userIdentifier = FindClaimByType(claimsIdentity, ObjectIdClaimType);
var alreadyAdded = AdGroupsAlreadyAdded(claimsIdentity);
if (claimsIdentity == null || userIdentifier == null || alreadyAdded)
{
return Task.FromResult(principal);
}
var userSecurityGroups = _graphService.GetSecurityGroupsByUserId(userIdentifier).Result;
var allSecurityGroupModels = _securityService.GetSecurityGroups().Result.ToList();
foreach (var group in userSecurityGroups)
{
var groupIdentifier = allSecurityGroupModels.Single(m => m.GroupName == group).GroupGuid.ToString();
claimsIdentity.AddClaim(new Claim("groups", groupIdentifier));
}
claimsIdentity.AddClaim(new Claim(AdGroupsAddedClaimType, "true"));
return Task.FromResult(principal);
}
private static string FindClaimByType(ClaimsIdentity claimsIdentity, string claimType)
{
return claimsIdentity?.Claims?.FirstOrDefault(c => c.Type.Equals(claimType, StringComparison.Ordinal))
?.Value;
}
private static bool AdGroupsAlreadyAdded(ClaimsIdentity claimsIdentity)
{
var alreadyAdded = FindClaimByType(claimsIdentity, AdGroupsAddedClaimType);
var parsedSucceeded = bool.TryParse(alreadyAdded, out var valueWasTrue);
return parsedSucceeded && valueWasTrue;
}
}
Within my Startup.cs, in the ConfigureServices method, I register the implementation like this:
services.AddTransient<IClaimsTransformation, AdGroupClaimsTransformer>();
The Caveat
You may have noticed that my implementation is written defensively to make sure the transformation will not be run a second time on a ClaimsPrincipal that has already undergone the procedure. The potential issue here is that calls to the IClaimsTransformation might occur multiple times, and that might be bad in some scenarios. You can read more about this here.
You can use the Microsoft Graph API to query the user's groups instead:
POST https://graph.microsoft.com/v1.0/directoryObjects/{object-id}/getMemberGroups
Content-type: application/json
{
"securityEnabledOnly": true
}
Reference: https://learn.microsoft.com/en-us/graph/api/directoryobject-getmembergroups?view=graph-rest-1.0&tabs=http
The scenario will be:
Your client app will acquire access token (A) for accessing your back-end Web API.
Your Web API application will acquire access token (B) for accessing the Microsoft Graph API with the access token (A) using OAuth 2.0 On-Behalf-Of flow. Access token (B) will be used to get the user's groups.
Web API validates the user's group using a policy (recommended) or custom attribute.
The protocol diagram and sample request are listed in this article using the Azure AD V2.0 Endpoint. This article is for the V1.0 endpoint. Here are code samples for .Net Core.

Restricting Azure AD users from accessing web api controller

I have a web api application which I allow an access to only authorized user.
I do it by using attribute [Authorize] with controllers
Can I restrict from accessing the application a particular user with a given username even though he/she's in Azure AD?
Can I restrict from accessing the application a particular user with a given username even though he/she's in Azure AD?
What you need is to create a policy and check current user against this policy whenever you want.
There're two ways to do that.
Use a magic string to configure policy (e.g. [Authorize(policy="require_username=name")]), and then create a custom policy provider to provide the policy dynamically. For more details, see https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-2.2
Create a static policy and use a custom AuthorizeFilter to check whether current user is allowed.
Since this thread is asking "Restricting Azure AD users from accessing web api controller", I prefer to the 2nd way.
Here's an implementation for the 2nd approach. Firstly, let's define a policy of requirename:
services.AddAuthorization(opts =>{
opts.AddPolicy("requirename", pb => {
pb.RequireAssertion(ctx =>{
if(ctx.User==null) return false;
var requiredName = ctx.Resource as string;
return ctx.User.HasClaim("name",requiredName);
});
});
});
And to check against this policy, create a custom AuthorizeFilter as below:
public class RequireNameFilterAttribute : Attribute, IAsyncAuthorizationFilter
{
public string Name{get;set;}
public RequireNameFilterAttribute(string name) { this.Name = name; }
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var user= context.HttpContext.User;
if(user==null){
context.Result = new ChallengeResult();
return;
}
var authZService = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
var result= await authZService.AuthorizeAsync(user, this.Name, "requirename");
if (!result.Succeeded) {
context.Result = new ForbidResult();
}
}
}
Finally, whenever you want to deny users without required names, simply decorate the action method with a RequireNameFilter(requiredName) attribute:
[RequireNameFilter("amplifier")]
public string Test()
{
return "it works";
}
[Edit]
AAD can restrict Azure AD users from accessing web api controller on an Application level. But cannot disallow an user to access a Controller API (API level).
Here's how-to about restricting Azure AD users on an Application Level
Login your Azure portal:
Choose an Activity Directory (e.g. Default Directory)
Click [Enterprise applications]
Choose the application you want to restrict (e.g. AspNetCore-Quickstart)
Select [Properties], Change the [User assignment required] to Yes
Select [Users and groups], Add/Relete users for this application as you need :
Be aware Azure AD is actually an Indentity Provider. This approach only works for the entire application. It's impossible to allow some user to access the App but disallow him to access a specific controller without coding/configuring the Application. To do that, we have no choice but to authorize uses within the application.

swagger - annotation for permissions?

Is there any way to document the permissions required for a Request? If I have annotations like
[Authenticate]
[RequiredRole("Admin")]
[RequiredPermission("CanAccess")]
public object Delete(DeleteAppUser deleteUserRequest)
{
// ....
}
in my service class or alternatively for my RequestDTOs
[Authenticate]
[RequiredRole("Admin")]
[Route("/appusers/{AppUserId}", "DELETE", Summary = "Delete an application user identified by its ID.")]
public class DeleteAppUser : IReturn<AppUserDto>
{
// ....
}
Can I make this somehow available in the swagger-ui documentation for users of my API automatically or do I have to write it in the Notes like:
[Route("/appusers/{AppUserId}", "DELETE", Summary = "Delete an application user identified by its ID.", Notes="Requires an authenticated session and membership in the Admin role.")]
No Swaggers UI doesn't have a concept of roles or permissions. This information is displayed on ServiceStack's /metadata pages but to display it in Swagger's UI you'd need to add it to the API's text description.

MVC 5 & ASP.NET Identity - Implementation Confusion

I'm creating a new web application that will be written using MVC 5 and Entity Framework Database First Approach. I would also like to use ASP.Net Identity to look after membership, authentication, authorisation etc.
I've read a good bit about the ASP.Net Identity on the web and how it works, however, I am still learning about this topic.
When I created my MVC 5 application in Visual Studio 2013 and looked at the Account Controller my first instinct was that I didn't like what I saw, i.e., a DbContext was being referenced named 'ApplicationDbContext'. The reason I didn't like this was because I prefer to keep my DbContext in the appropriate project within my solution, i.e., in my Model layer which adheres to the separation of concerns logic.
Also, the out of the box MVC 5 project uses Entity Framework Code First to create a default database and tables to store the Users, Roles etc.
Because I must use an existing database with an existing User table, this approach does not suit my needs.
I still want to use the latest ASP.Net Identity for my application as it looks to have many benefits, therefore, I found this article which stripped back alot of the Entity Framework code but still got OWIN powered authentication into an ASP.NET MVC.
http://www.khalidabuhakmeh.com/asp-net-mvc-5-authentication-breakdown-part-deux
Using the tutorial above, here is the HttpPost Login method for my Account Controller
[HttpPost]
[AllowAnonymous]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
//Calling my own custom Account Service which validates users login details
var user = _AccountService.VerifyPassword(model.UserName, model.Password, false);
if (user)
{
var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, model.UserName), }, DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.Name, ClaimTypes.Role);
//ToDo: Manually adding Role, but will pull from db later
identity.AddClaim(new Claim(ClaimTypes.Role, "guest"));
AuthenticationManager.SignIn(new AuthenticationProperties
{
IsPersistent = model.RememberMe
}, identity);
return RedirectToAction("Index", "MyDashboard");
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
return View(model);
}
In my previous MVC applications I usually rolled my own custom membership and when a User logged into the site and was authenticated, I would have stored the any additional user details such as userID, DOB etc in the UserData string of the FormsAuthenticationTicket.
As the code above does not use FormsAuthentication, instead it uses OWIN CookieAuthentication, I am not sure how to store this additional user data.
Therefore, I have a few questions about the problems I am experiencing.
How do I store the userID or any other additional piece of user data (DOB etc) the way I used to in FormsAuthentication? Is this done by adding a Claim to the identity?
Does the method of using ASP.Net Identity/ OWIN above seem correct considering I am using Entity Framework Database First with an existing database?
Should I be using the out of the box code that is used in the Account Controller, i.e., UserManager, ApplicationUser, ApplicationDbContext etc and hooking this up to work with my existing database?
I apologise if my question is confusing, I suppose I'm just a little unsure of what approach I should be using whilst attempting to use ASP.Net Identity in my latest project.
Any feedback would be greatly appreciated.
Thanks.
1) The new Katana Cookie middleware supports claims. This is what makes this better than forms auth cookie; claims model any key/value pair and those can be stored in the authentication cookie. See this post for more details:
http://brockallen.com/2013/10/24/a-primer-on-owin-cookie-authentication-middleware-for-the-asp-net-developer/
2 & 3) As far as your storage for identity data, if you need to work with an existing table then you might not be able to use Microsoft's EF provided classes. Instead you'd be left on your own to implement IUserStore and all the other store interfaces your app needs. I'm not certain it's worth changing what you're already using to store the user data.
Keep in mind that the OWIN/Katana part is separate from the identity storage.
Here is the solution
To speed things up you can add sample app to your project and start by modifying the sample app, Samples app includes confirmation email, password recovery, roles admin and user role management etc. NuGet package is at:
Install-Package Microsoft.AspNet.Identity.Samples -Pre
See full details on sample app here: ASP.NET Identity 2.0: Customizing Users and Roles
Controll access to controller or Action by using below attributes
[Authorize] //Anyone with authorization
[Authorize(Roles="Administrator")] //Admin role only
Check if user is in role by
HttpContext.User.IsInRole("Administrator")
UserManager.IsInRole(userID, "Administrator")
Get profile data by
// Create manager
var manager = new UserManager<ApplicationUser>(
new UserStore<ApplicationUser>(new ApplicationDbContext()))
// Find user
var user = manager.FindById(User.Identity.GetUserId());
var profileProperty_1 = user.profileProperty_1

Resources