Get Azure AD Groups Before Building Authorization Policies - azure

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.

Related

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.

Azure AD Claims in IdentityServer4

I have taken this sample from github to attempt to use IdentityServer4 and Azure AD for authentication.
While I have it working and returning a token, it seems that the claims that I would have expected to receive from Azure AD are not included in the token(s) issued through IdentityServer.
It could be that this is intentional and that I have misunderstood this flow, but I was hoping that the roles that a user is assigned through Azure AD (plus the tenant ID and other useful 'bits' from the Azure token) would be able to be included in the tokens issued to the client.
Would anyone be able to shed some light on this for me? I can paste code in here but the link to the github code is pretty much the same as what I am using.
I was trying to do the same thing, and managed to eventually piece bits together from looking at the IS4 docs, Github and StackOverflow.
You need to configure a new instance of IProfileService (Docs) in order to tell IdentityServer4 which additional claims for a user's identity (obtained from Azure AD in your case) you want to be passed back to the client.
An example might look like this:
public class CustomProfileService : IProfileService
{
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// Get the 'upn' claim out from the identity token returned from Azure AD.
var upnClaim = context.Subject.FindFirst(c => c.Type == ClaimTypes.Upn);
// Get 'firstname' and 'givenname' claims from the identity token returned from Azure AD.
var givenNameClaim = context.Subject.FindFirst(c => c.Type == ClaimTypes.GivenName);
var surNameClaim = context.Subject.FindFirst(c => c.Type == ClaimTypes.Surname);
// Add the retrieved claims into the token sent from IdentityServer to the client.
context.IssuedClaims.Add(upnClaim);
context.IssuedClaims.Add(givenNameClaim);
context.IssuedClaims.Add(surNameClaim);
}
public Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
return Task.CompletedTask;
}
}
You will then need to register this service in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddDeveloperSigningCredential()
// Register the new profile service.
.AddProfileService<CustomProfileService>();
}
Finally, inside of your AccountController.cs (within the IdentityServer4 project - I'm assuming you already have this, see here for a starter setup if not), you need to add the following to ExternalLoginCallback():
[HttpGet]
public async Task<IActionResult> ExternalLoginCallback()
{
//...
// this allows us to collect any additonal claims or properties
// for the specific protocols used and store them in the local auth cookie.
// this is typically used to store data needed for signout from those protocols.
var additionalLocalClaims = new List<Claim>();
// ADD THIS LINE TO TELL IS4 TO ADD IN THE CLAIMS FROM AZURE AD OR ANOTHER EXTERNAL IDP.
additionalLocalClaims.AddRange(claims);
//...
}
Hope this helps.

Custom authorization with Azure AD Authentication in OWIN Web API

We are using Azure AD authentication for one of our client application. We want to implement claims based authorization along with it.
Our application set up is Angular Based client app connecting with Web API (both client server secured using Azure AD Bearer Authentication). Server application is hosted using OWIN.
We need to provide custom authorization on server side. There is a provision in Azure AD for adding users and roles. However, that is not enough for us. Our user management is through AD & Security Groups. To gain access to application, users need to part of a base group and further rights (access particular section of application, edit a specific entity etc.) are assigned based on additional groups or given directly to users in the application. Essentially, not all users will be registered in the application and we may have to query the AD using graph API to check which all application specific groups they belong.
OWIN authentication and authorization model is based on Authentication Server and Resource server. We can separate them on need basis. However, in our case, we need to split the authentication and authorization. When the client presents the bearer token, we need to verify if the token is valid and then add claims to user profile. We also need to cache the user claims so that we do not hit the database frequently. (Our client app make multiple Web API calls in one user action.)
What is the location in Identity 2.0 where
I can verify the token &
insert application specific claims
If my entire application revolves around the user authorization and all queries need to be filtered on what data the user can access, which is a more suitable design pattern for the Web API application?
I believe what you're looking for are the Authentication and Authorization filters in the ASP.NET Web API 2.0 stack.
You can implement per-web method authorization by implementing System.Web.Http.Filters.IAuthorizationFilter on an attribute class, then decorate the web action methods of your service controller with that attribute. Web API 2.0 will select a method based on URL routing, notice that there is an attribute on that method implementing IAuthorizationFilter, and will call the ExecuteAuthorizationFilterAsync method on that attribute instance before calling the web method. Placing the authorization step before the web method invocation allows invalid requests to be discarded quickly, before getting into the heavy lifting of parameter binding.
The incoming token is validated by an IAuthenticationFilter implementation which executes before the authorization step.
Documentation and examples are extremely hard to find. Here's one of the few search results that are actually relevant: http://thegrumpycoder.com/post/105427070626/secure-web-services-with-web-api-and-sitecore
you can check if this helps...
UserProfile profile = new UserProfile(); //To deserialize the response stream (JSON)
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
AuthenticationResult result = null;
try
{
// Get the access token from the cache
string userObjectID =
ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
.Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
//use ClientID, ClientSecret
ClientCredential credential = new ClientCredential("b557ceed-xxxx-xxxx-xxxx-xxxxxxxbc240", "AXFxx//xxxxxxxxxxxxxjVFz4sqYm8NDAPEOLkU=");
result = authContext.AcquireTokenSilent("https://graph.windows.net", credential,
new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// AcquireTokenSilent may throw exception if the cache is empty. In that case, logout the user and make him login.
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
"https://graph.windows.net/cdmsdev.onmicrosoft.com/groups/b40xxxx-14a8-xxxx-9559-xxxxxxca90c8/members/?api-version=1.6");
//Above grap API url is for getting list of users who belong to a specific group (with GUID b40xxxx-1....)
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var upn = ClaimsPrincipal.Current.Identity.Name;
string responseString = response.Content.ReadAsStringAsync().Result;
profile = JsonConvert.DeserializeObject<UserProfile>(responseString);
if (profile.Users.Contains(upn)) //check if the current user is in the list of users of the Admin group
return true;
}
}
catch (Exception e)
{
//handle authorization exception here
}
The graph API URL can be replaced with a function to check for membership of a specific group which will directly return a bool value instead of getting all users of that group.

Getting username and group info from Azure using adal4j

I am developing a mobile app in which I need to authenticate a user against Azure AD. Basically the user will be prompted their organisational email and password, which the mobile phone app sends to the backend server which will authenticate.
I have the 'public-client-app-sample' of 'azure-activedirectory-library-for-java' working, and can authenticate against 'graph.windows.net':
private static AuthenticationResult getAccessTokenFromUserCredentials(
String username, String password) throws Exception {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
"https://graph.windows.net", CLIENT_ID, username, password,
null);
result = future.get();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException(
"authentication result was null");
}
return result;
}
However, this does not return any userInfo (is null), and I can't figure out at this moment how to query to get a list with groups the user belongs to?
Do I just do manual lookups using the API using the tokens obtained from Adal4j, or is there a provided function within the library?
I am only starting with Azure, so it might be obvious, I might just be looking in the wrong places. I tried e.g. 'https://graph.windows.net/xxx.com/groups?api-version=1.5' but get 'Resource 'https://graph.windows.net/xxx.com/groups?api-version=1.5' is not registered for the account.'
First, you're absolutely right, adal4j was failing to return UserInfo. The reason for this was that the token request did not include the scope=openid parameter, which is required if the caller wants an id_token in the response. I opened an issue, and it has already been resolved. So, an updated copy of adal4j will fix your first issue of not getting UserInfo.
Now, regarding group membership for the current user: Normally, I would recommend that you simply configure you application to return the groups claim. This can be done very easily by changing the application manifest (downloaded and uploaded via the Azure portal, under the Application's configuration page) to include:
"groupMembershipClaims": "All",
Unfortunately, adal4j does not yet include the groups claim in the result of getUserInfo(), so that probably won't work much for you (issue opened, it really depends on how quickly it gets implemented, or if you want to implement youself).
Regardless, because it is possible for there to be too many groups to include in the token (indicated by , your application should always be able to use the AAD Graph API to retrieve the full set of groups the user is a member of.
And that brings me to the last point: adal4j does not implement a client of the Azure AD Graph API. So yes, you would have to implement that yourself (or perhaps use/modify an existing OData client).
(You can read more about Group Claims in this Azure AD blog post.)

Where should I plugin the Authorization in Asp.net WebAPI?

As I see I have 3 possible places to plug my stuff in the pipeline
1) AuthorizationFilters
2) Action Filters
3) DelegatingHandler
The most obvious one is AuthorizationFilters , where I can decorate my actions/ controllers with my custom authorization attribute . say .. MyCustomAuthorizationAttribute .
Since HTTP message handlers are in the first stage in the processing pipeline. Does it make any sense to put it in there ?
Authorization for me right now simply means checking a token in the header which is given to the client after authentication.
Update July 2014
My original answer covered WebApi 1. with WebApi 2 there were some changes i.e. there is now an IAuthenticationFilter meaning you can move authentication logic out of the DelegatingHandler which is a little more elegant.
There is a Nuget project here that offers an implementation of IAuthenticationFilter and also explains some background to its introduction.
OWIN middleware is now perhaps the best place to implement your authentication logic - there is an example of Certificate Authentication here and Basic Authentication OWIN Middleware here in this blog post the former example is the preferred one as it demonstrates the use of the base AuthenticationHandler class.
The advice on AuthorizationFilters remains largely unchanged.
End Update
Typically...
Use DelegatingHandler to carry out Authentication... i.e. who someone is. Use this to set the Principle of the Thread and User context, add claims etc. You can place authorisation logic here too but on a fairly global scale. I would personally always use AuthorizationFilters for authorisation.
Use AuthorizationFilters to restrict controllers and actions to specific people. These are used when you can extrapolate their permission with the information in claims, principal, url or the http request parameters. The default authorisation filter can be used to restrict access to anonymous users or by roles (if set in something like a delegating handler) - obviously you can implement your own AuthorizationFilters too if you need it.
Occasionally use ActionFilters when you need to make the decision over authorisation using the message content e.g. you need access to a property on the entity to decide whether they have access (obviously be careful with this(!)).
Note:
The AuthorizationFilters are called before the content of the body is read therefore they do not have access to the message body to make authorization decisions this is why the ActionFilters specifically the OnActionExecuting is used to occasional raise authentication errors.
So
In your scenario I would put a simple DelegatingHandler to take your header and set the principal.
public class CustomAuthenticationMessageHandler : DelegatingHandler
{
public CustomAuthenticationMessageHandler ()
{
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
Authenticate(request);
return base.SendAsync(request, cancellationToken);
}
protected virtual void Authenticate(HttpRequestMessage request)
{
var authorisationHeader = request.Headers.Authorization;
if (authorisationHeader == null)
{
return;
}
//Ensure you are happy with the header contents then
{
var principal = new GenericPrincipal(//new Identity , //Roles);
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
}
}
}
Then use AuthorizationFilters to restrict access:
[Authorize]
public string Get()
{
}
[Authorize(Roles = "Admin")]
public string GetAdminOnly()
{
}
To register the global Authentication
config.MessageHandlers.Add(new CustomAuthenticationMessageHandler());
This will mean that in every request the principal will be set to either null or a valid identity. It won't handle authorisation i.e. wont deny access to any controllers or actions.
To start protecting resources
Either target protected controllers and actions with the standard or custom [Authorize] attributes. Or register globally:
config.Filters.Add(new AuthorizeAttribute());
And only white list the controllers and actions you want unsecured using the [AllowAnonymous] attribute.
If you only want authentication on some routes
Then you can modify your DelegatingHandler a little to set the InnerHandler to route to the correct controller e.g.
public CustomAuthenticationMessageHandler(HttpConfiguration configuration)
{
InnerHandler = new HttpRoutingDispatcher(configuration);
}
And then you can specify this handler on your routes like so:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "myurl",
defaults: new {},
constraints: new {},
handler: new CustomAuthenticationHandler(config)
);

Resources