How to check caller is part of AD Security group in WebAPI layer? - asp.net-web-api

I have a website (WebApp) from where I make Ajax calls to my WebAPI layer that accepts JWT Bearer Token auth. I have integrated Adal.js into my front end script layer and the config values look like this.
var config = {
instance: "https://login.microsoftonline.com/",
tenant: "tenant.com",
clientId: "CLIENT_ID OF THE PORTAL APP", // Web-Portal app
redirectUri: "http://localhost:8241/",
popUp: true,
cacheLocation: "localStorage" };
The WebApiConfig looks like this:
var webApiConfig = {
resourceId: "CLIENT_ID OF API APP", // Web-API app
resourceBaseAddress: "http://localhost:9020/"
};
It all works fine, I am able to authenticate/authorize and call my webapi via an access token via Implicit oauth flow.
Now I would like to be able to retrieve the User's security group membership values for the logged in user so that at the WebAPI layer, I can make sure the user belongs to a particular security group as part of the authorization logic. So I set the GroupMembershipClaims: "SecurityGroup" in the App's manifest xml in Azure AD (I did this for the WebApi App manifest first, but then also in the WebApp app manifest).
However, looks like GroupMembershipClaims are only included in the idtoken and not the accessToken. And given it is the accessToken that is sent to the WebAPI, I am unable to do this check at that layer. I guess I could make the check at the WebApp layer, but given this WebApi layer will be called from many other frontend apps (which are not owned by me, I am primarily an "API provider"), that is not a secure soln.
So how do I solve this? I guess one way is to use the "on-behalf-of" flow on the WebApi layer to make calls to Graph API to find this out? I am afraid that will require more permissions than available at User scope.
Thanks!

It's a good question.
My immediate first thought was "well you can get them from Graph API", but I see you already thought of that :)
If you want to do that, you can use On-behalf-of like you said, and use this operation on MS Graph API: https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/user_getmembergroups.
You would need these delegated permissions: User.Read and Group.Read.All.
So you would need the permission to read all groups in addition to the basic User.Read.
There are a couple other options.
You can define roles in your API.
I wrote an article on how to do this: Defining permission scopes and roles offered by an app in Azure AD.
So if you define a role like this in the API's manifest:
{
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"displayName": "Administrator",
"id": "179c1dc0-4801-46f3-bc0d-35f059da1415",
"isEnabled": true,
"description": "Administrators can access advanced features.",
"value": "admin"
}
]
}
You'll need to make the id a unique GUID for each role, you can use online generators or PowerShell for that [System.Guid]::NewGuid().
These roles can then be assigned to users, and if you have at least Azure AD Basic, you can assign them to groups.
Roles are included in access tokens, so you can check these quite easily in your API.
The other idea I had was to have the API and front-end use the same registered app in Azure AD.
This way you could pass the Id token to the API instead of an access token, and you'd get the groups.
But since you might have other front-ends too, I doubt this would work for you.

Related

How to use the on-behalf-of flow to call Microsoft Graph from Spring resource server

I'm trying to complete a fairly simple process:
A web application, authenticated with Azure AD via a personal Microsoft account (i.e. an #hotmail account), makes a call to a second microservice.
The microservice is secured via a JWT, obtains a second token using the on-behlaf-of flow, and called the Microsoft Graph API to retrieve calendar events.
I can log into the web frontend OK, and call the second microservice OK. The second microservice can obtain an on-behalf-of (obo) token OK, but the problem I run into is that the obo access token provided to me fails to call the Graph API. The error I receive is this:
{"error":{"code":"NoPermissionsInAccessToken","message":"The token contains no permissions, or permissions can not be
understood.","innerError":{"oAuthEventOperationId":"7499efa7-932b-425d-8ad4-43206630f961","oAuthEventcV":"ed1BzGz2t/H/wK7JnZB6lQ.1.1.1","errorUrl":"https://aka.ms/autherrors#error-InvalidGrant","requestId":"3ca5fa79-423b-460e-9130-b7d1172ec841","date":"2021-09-13T09:24:17"}}}
My problem is similar to the one described here, where my obo JWT does not contains a roles claim. This is the decoded JWT sent to the Graph API:
{
"typ": "JWT",
"nonce": "A3IzBCOGrnE53ukPqb2jHjWYT0grwFbHo_OkzcUhSRc",
"alg": "RS256",
"x5t": "l3sQ-50cCH4xBVZLHTGwnSR7680",
"kid": "l3sQ-50cCH4xBVZLHTGwnSR7680"
}.{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/26375159-666a-4217-adfe-c06427b7798c/",
"iat": 1631522998,
"nbf": 1631522998,
"exp": 1631526898,
"acct": 1,
"acr": "1",
"aio": "AUQAu/8TAAAA1HEurZSE5YS0ADYs3oKeEEy6qhWwyXBZtoHtBPbIS/jo0OD5BTlQptuXZ3ZLtDczWZQw7b0+dUoCNdpN2mY4ew==",
"altsecid": "1:live.com:00014B90C0D373BC",
"amr": [
"pwd"
],
"app_displayname": "SpringBootMicroserviceDemo",
"appid": "5907efc0-f0b5-45db-b4cf-725f655009c3",
"appidacr": "1",
"email": "matthewcasperson#hotmail.com",
"family_name": "Casperson",
"given_name": "Matthew",
"idp": "live.com",
"idtyp": "user",
"ipaddr": "45.132.224.55",
"name": "matthewcasperson",
"oid": "e377a23b-1b88-4d14-9c99-fc6ecd4a41c7",
"platf": "3",
"puid": "1003200180F4FB20",
"rh": "0.AVAAWVE3JmpmF0Kt_sBkJ7d5jMDvB1m18NtFtM9yX2VQCcNQAKM.",
"scp": "Calendars.Read openid profile User.Read email",
"signin_state": [
"kmsi"
],
"sub": "vhbBIoJqEHoEaJLMVsG5sh0C4FjoJiAfAKOFCzrC8hQ",
"tenant_region_scope": "NA",
"tid": "26375159-666a-4217-adfe-c06427b7798c",
"unique_name": "live.com#matthewcasperson#hotmail.com",
"uti": "ufe_nSFv_UuiYCOur72mAQ",
"ver": "1.0",
"wids": [
"13bd1c72-6f4a-4dcf-985f-18d3b80f208a"
],
"xms_st": {
"sub": "PCUskXV6aCsNgHz9Yug42-WhS-iea1gy5GI5trkTZ4E"
},
"xms_tcdt": 1631479794
}.[Signature]
The JWT has Calendars.Read in the scp, but no roles, which appears to be an issue.
The JWT above will complete a call to the "me" endpoint OK (i.e. https://graph.microsoft.com/v1.0/me). Unfortunately, calling the "me" endpoint is as far as any example provided by Microsoft goes.
The sample application here provides a controller that shows calling the "me" endpoint on the Graph API, and if I modify the Graph API client to include the https://graph.microsoft.com/Calendars.Read scope, it also fails the Graph API request.
The sample application here appears to be a slightly older example in that it builds the token manually rather than injecting something like #RegisteredOAuth2AuthorizedClient("graph") OAuth2AuthorizedClient client (which is described in more detail on the Spring blog here). But again the sample only goes so far as to call the "me" endpoint.
As far as I'm aware I have enabled all the correct settings. My Azure AD Application has requested Calendars.Read both as a delegated and application permission, and the permissions have been granted consent:
My web based application can be found here, and the Calendar API microservice can be found here.
Further reading:
Azure AD v2 roles not included in Access Token: This described how to add custom roles to the JWT, but I did not see how it could be used to add Graph API roles.
Graph API access token for calendar read reports No Permissions: This talked about adding application permissions, which did not resolve the issue for me.
Microsoft Graph API outlook task folder : NoPermissionsInAccessToken: This issue talks about different OAuth flows, but this doesn't appear to apply to a resource server that accepts a JWT.
How to solve "NoPermissionsInAccessToken" returned by Microsoft Graph when reading calendar or sending mail: The answer here talks about "Adding guest users is meaningless", which makes no sense to me.
Spring Boot Starter for Azure Active Directory developer's guide: This is a reasonably detailed tutorial that even speaks about resource servers, but I could not find any tips that addressed the NoPermissionsInAccessToken error.
OAuth 2.0 Sample for Azure AD Spring Boot Starter client library for Java - This is another reasonably detailed tutorial that talks about accessing a second resource server, but again I didn't find any clues to the NoPermissionsInAccessToken error.
List events and Get calendar: This indicates that a Delegated (personal Microsoft account) can use the Calendars.Read permission.
The crux of my issue is that using the on-behalf-of flow appears to be the recommended solution for calling the Graph API from a resource server that accepts a JWT from a front end application. Defining the delegated permissions in the Azure AD Application, consenting to them, defining them in an authorization-clients: in the application.yaml file, and getting a client via #RegisteredOAuth2AuthorizedClient("graph") OAuth2AuthorizedClient client are the only consistent instructions from the Microsoft documentation and sample applications. And yet the resulting JWT can only call the "me" endpoint.
Can anyone shed some light on how to call the Graph API with on-behalf-of token in Spring boot?

Securing .NET Framework Web API with Azure AD (Client credentials flow)

I have a .NET 4.7 Web API project (not .NET CORE).
I am trying to setup authentication with an Azure AD directory, I setup an application in my AD, and I got the client id (application id)
I would like to use the Client Credentials grant type. So I went ahead and retrieved a token via the access token URL https://login.microsoftonline.com/HIDDEN/oauth2/v2.0/token I am passing in the client id, and secret, for this I am using Postman
Now in my project I've implemented the following logic in my web api project:
var clientId = "AZURE APPLICATION ID";
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AllowedAudiences = new List<string> { clientId },
TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidAudience = clientId
}
});
On my controller, I applied the [Authorize] attribute
When calling the API controller endpoint (making sure I am passing in the Authorization header with the value "Bearer MYTOKEN") I get the error returned in Postman:
"Message": "Authorization has been denied for this request."
Is there a way I can dive deeper to figure out what might be wrong?
I don't see anything in my output window in visual studio, are there some events I can hook into why it is failing?
EDIT: Adding more information per Carl:
The token seems to be valid, here are the results from jwt.ms, i even setup an "admin" role via the manifest:
Here is my code, I am not specifying the public signature (not sure how to do that yet), but I've even turned off IssueSignature validation.
This is what my controller looks like:
My fiddler request and response (does having an http endpoint instead of https for local development make a difference?) I don't believe it does:
Inspect your access token and ensure the aud claim value equals the clientId. Usually the aud claim will be something like api://clientId which is not what you have setup in your code. If that's the case set it as "api://" + clientId
You should get a 401 error, which means that the aud of your token is not your api. The cause of the error is usually that you set the wrong scope when requesting the token. I used the client credential flow Make a demo for you:
You need to create two applications in Azure ad, one representing the client application and the other representing the api application, and then use the client application to call the Web api application.
First, you need to expose the api of the application representing the web api, you can configure it according to the following process:
Azure portal>App registrations>Expose an API>Add a scope>Add a client application
Next, you need to define the manifest of api applications and grant application permissions to your client applications (this is the role permissions you define yourself, you can find it in My APIs when you add permissions)
This is the process of defining the manifest.
This is to grant permissions for the client application (You can find your expose api permissions in My APIs.):
Request access token:
Parse the token:

Keycloak: API to check if user token has roles for a particular resource

I have a keycloak server setup. I am using the token endpoint: http://localhost:8080/auth/realms/demo/protocol/openid-connect/token to authenticate a user and generate a token. This token I understand can be used in subsequent calls to verify if it is a valid user.
However, I am not sure how do I use this to authorize the user? ie verify if this user has the roles to access a resource.
I see that it is possible to configure a resource URI under the client section. But once that is done, I want to be able to read the token and verify if this user has the roles to access this resource.
Right now, this is what I am doing:
I have used spring boot here.
doSomething(String token)
{
1. get token info using: http://localhost:8080/auth/realms/demo/protocol/openid-connect/userinfo
2. from this get the roles the user has
3. Manually check the roles required for the above function. (Right now, this is set in a simple switch statement)
4. If the role obtained from step 2 matches what we get in step 3, go ahead. Else return failure.
}
I want to know if step 3 above can be done in a better way. I know taht you can set a resource in clients from the keycloak console. What I was hoping is we could replace the 4 steps above with something like:
keycloakAPIToAuthorizeToken(token,resource)
which would tell me whether this user has the roles (obtained from token) to access this resource.
Please suggest if this is doable.
Thanks in advance.
Om
There are MANY ways to do this. One of them is to use Keycloak's roles, and assign those roles to users. That way, in your server/api you can check if the user has that role and proceed or reject the call.
Example, in my api project, I have some endpoints that are exclusive for system administrators, so I have a role SystemAdministrators:
and then, if you go to Users, Role Mappings, you can add that role to the users to set as admins:
Then, any api call should include a bearer token (obtained from the Keycloak login), in your code, you can decode this jwt (it will depend what language you are using, I use python so its pretty simple), it will have an element "realm_access" and inside it, a "role" element, example:
"realm_access": {
"roles": [
"offline_access",
"uma_authorization",
"SystemAdministrators"
]
},
If that element contains the role SystemAdministrators, you know is a system administrator.
I found this is the simplest way to do it. You can get fancy and use role attributes to determine individual options inside screens, etc (attributes are key/value pairs so the way I implemented this is, the key is an option name and the value is the permission level, for example: "screen1": "read", "screen2": "write", and so on. For this, you would need to use the keycloak admin api: https://www.keycloak.org/docs-api/6.0/rest-api/index.html#_roles_resource which has many endpoints that can help you.

Using Azure Active Directory token in ASP.Net Core Web API with UseJwtBearerAuthenticiation

I have a native client application written in Ionic Framework 3. I have a Web API written in ASP.NET Core 1.1. I want to use Azure Active Directory to manage access to the Web API.
I have registered two applications with Azure Active Directory: Mobile App and Web API. The Mobile App has the required permission of granting access to the Web API. Below are screen shots of the permissions from our Azure Admin Portal:
Mobile App Permissions
This is configured as a Native App in AAD. I have an Application ID and an Object ID given by AAD. Additionally, I added an arbitrary Redirect URI, which I thought based on several tutorials did not need to resolve, that URI is http://mobileCRMApp. Looking at the Properties in AAD, the Home page URL is blank and the Logout URL is blank.
API Permissions
BOLD UPDATED 10/03/2017:
This is configured as a Web App/API in AAD. I have an Application ID and an Object ID given by AAD. Additionally, I set both the Home Page URL and the App ID URI to match the root of my Web API (https://crm.mycompany.com).
My Ionic client application successfully authenticates against AAD roughly in the following way:
authenticate(userID, authCompletedCallback) : any {
let parent = this;
//this.context = new AuthenticationContext(this.config.authority);
let context = this.msAdal.createAuthenticationContext("https://login.microsoftonline.com/myTenantId");
console.log(context);
context.acquireTokenAsync(parent.config.resourceUri, parent.config.clientId, parent.config.redirectUri, userID, "")
.then(authCompletedCallback)
.catch((e: any) => console.log('Authentication failed', e));
}
The login process goes fine in the app, and the callback receives a token, and I can translate its payload using jwt.io into roughly the following:
{
"aud": "https://crm.mycompany.com/",
"iss": "https://sts.windows.net/someID/",
"iat": 1506539211,
"nbf": 1506539211,
"exp": 1506543111,
"acr": "1",
"aio": "someOtherID",
"amr": [
"pwd"
],
"appid": "appID",
"appidacr": "0",
"e_exp": 262800,
"family_name": "Walter",
"given_name": "Philip",
"ipaddr": "someAddress",
"name": "Philip Walter",
"oid": "someOtherID",
"onprem_sid": "someOtherID",
"puid": "stuff",
"scp": "user_impersonation",
"sub": "e_X7WlAoVS2vzXm1pr3kcDOrET7czcC0f8-YRU_2DJ8",
"tenant_region_scope": "NA",
"tid": "ourTenantID",
"unique_name": "pwalter#advtis.com",
"upn": "pwalter#advtis.com",
"uti": "RLvLlibQHESwmujVBBdlAA",
"ver": "1.0"
}
So then I take the token and send it along with an http request to the API from the Ionic client app, roughly like so:
let headers = new Headers();
headers.append('Authorization', 'Bearer ' + this.authToken.accessToken);
let options = new RequestOptions({ headers : headers });
this.data.http.get(url, options).map(res => res.json()).subscribe((data) => {
console.log(data);
});
The API then has the following in Startup.cs
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = Configuration["Authentication: AzureAd:AADInstance"] + Configuration["Authentication: AzureAd:TenantId"],
Audience = Configuration["Authentication: AzureAd:Audience"]
});
So, I can log in through AAD in the client app, and I receive a token, but I still get a 401 unauthorized response from the web api when I send a request to a route with the [Authorize] tag above it.
I am obviously doing something wrong in configuring the API or the permissions. I put this together using several different tutorials, because I could not find anything that specifically addressed my use case. Any ideas as to what I'm doing wrong, or how I might troubleshoot?
Why does your audience say "aud": "https://graph.windows.net"?
It should say "aud": "https://yourWebApi.azurewebsites.net" or something similar.
The token you included above is only good for the Graph API, any other Azure AD protected resource will refuse it, including your Web API, since it expects the audience to match self.
parent.config.resourceUri is probably where you source the desired audience from:
context.acquireTokenAsync(
parent.config.resourceUri,
...
According to RFC 7519:
The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
Sometimes it's called resource, sometimes audience, sometimes aud... it is what it is :)

IdentityServer3 Sync users with client app

I have the following setup:
IdenetityServer3 for auth (OAUth / OpenID)
ASP.NET WebApi back end
Ember-cli UI
I have the auth flow working nicely - I haven't managed to get the admin UI working yet but I can prepopulate users, scopes and clients so that's fine.
When the user auths against IdentityServer3 they are redirected back to the UI and the UI uses the oidc-client to retrieve the users info from the JWT - the client also uses the bearer token to send to the API to auth requests - all good.
My problem is that the IddentityServer is in charge of authentication / authorization - but the API doesn't yet have any notion of a user - but it needs that.
What is the best way of syncing user info between IdentityServer and my API? How can I best manage things like roles and user hierarchy? Is there a way for the API to query IdentityServer for this? It seems silly holding a copy of the user info locally to the API when we have an identity server that manages all of this.
IdentityServer exposes a UserInfo endpoint (https://identityserver.github.io/Documentation/docsv2/endpoints/userinfo.html) which you can call to retrieve additional information about a user.
However, wherever possible, try to achieve what you need to by passing a token that has the relevant amount of claims so that you can make AuthZ decisions without requiring a call to Identity Server. This reduces coupling, and means you have less outbound calls from your API.
E.g. When you sign in, a JWT token could be created that contains the roles the user is a member of plus the users unique id (sub claim)
{
"iss": "https://my.api.com/trust",
"aud": "https://my.api.com",
"exp": 1512748805,
"nbf": 1481212805,
"scope": "openid",
"sub": "83b0451a718b4d54b930d6fe9cb7b442",
"idp": "site",
"roles": [
"role1",
"role2"
]
}
Your API can now just check the claims presented to it and say 'To call this API endpoint, the token presented to me must have role2 in the roles claim'.
You can also do this with the scopes, using the scope attribute
A well designed JWT token will contain the right amount of information to make AuthZ decisions without requiring lots of additional calls, whilst keeping the overall size of the token as small as possible - remember, it's included on every request.

Resources