Azure AD: How to make tokens have the "hasgroups" claim? - azure

Our application allows assigning permission to groups, which means for every user, we have to reliably determine group membership. The user presents a token regularly obtained with ADAL (some use .NET, others use NodeJS, others use CLI).
Some users seem to be sending a token with the following claim:
"hasgroups": true,
That claim is documented in the Azure AD token reference page.
We would like to add a test case for that, but after following steps here and here, we always end up with a token with the following claims:
"_claim_names": {
"groups": "src1"
},
"_claim_sources": {
"src1": {
"endpoint": "https://graph.windows.net/{redacted}/users/{redacted}/getMemberObjects"
}
},
What is wrong with our setup? Why can't we get the hasgroups claim?
Here are some additional information:
Application type is Native (not WebApi).
Manifest says "oauth2AllowImplicitFlow": true.
The application is given access to Azure Key Vault.
We use the following code to get the token (in C#):
var userCredential = new UserCredential( _userName, _password );
result = context.AcquireToken( _resource, _clientId, userCredential );
Where:
_userName and _password are from a user with lots of groups.
_clientId is the application id of the native application - the one with "oauth2AllowImplicitFlow": true.
_resource is https://vault.azure.net.
The token is emitted correctly. The only issue is that it shows _claim_names and _claims_sources instead of hasgroups.

Where: • _userName and _password are from a user with lots of groups.
As the user is part of lots of groups (assuming 6 or more here).. Azure AD token will come back with a groups overage indicator instead of actual group ids in “groups” claim. I guess you know that and hence doing it intentionally.
var userCredential = new UserCredential( _userName, _password );
result = context.AcquireToken( _resource, _clientId, userCredential );
Since you're acquiring the token in a .NET based application using C# code, the token response is not really limited in length (like in cases for a web SPA, where it is being returned as a URI fragment and URL length has limits)
Looking at the documentation both "hasgroups" and "groups:src1" claims have the same intention of telling that there are too many groups to return as part of the token. Although there is a subtle difference:
in cases where URL limit applies, "hasgroups" will be sent as true (like implicit grant flow for SPA)
in cases where length is not limited (like in your case), Azure AD will still not return all the groups to make sure the token doesn't get too big, but it will send a little more information on how to get to all groups by sending the information on how you can query for all your groups. In this case it's sending the "groups:src1" and "_claim_sources" with source information instead of just the "hasgroups"
Claims in id_tokens

For anyone looking more on this. Please refer Doc saml-tokens
Note
Source : Azure Sample Link

Related

Azure Active Directory v2 - Get Custom Scope Token

I am learning about generating a token for an OAuth service and it will be used in a chatbot. When I use the following code displayed below, I can get a default scope Graph Token successfully, and this token is valid for MS Graph API calls. Now, what I am trying to achieve is generating a custom scope token in a similar way in order to call an external service(Not MS Graph API). This token needs to have a custom scope. I tried to change the dictionary parameter "scope" to the name of my scope configured for a chatbot in Azure but it fails:
private async Task<string> GetGraphTokenAsync()
{
var dict = new Dictionary<string, string>();
dict.Add("client_id", _graphTokenSettings.ClientId);
dict.Add("client_secret", _graphTokenSettings.ClientSecret);
dict.Add("scope", "https://graph.microsoft.com/.default");
dict.Add("grant_type", "client_credentials");
string gUrl = $"https://login.microsoftonline.com/{_graphTokenSettings.Tenant}/oauth2/v2.0/token";
var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Post, gUrl) { Content = new FormUrlEncodedContent(dict) };
var httpResponseFromService = await client.SendAsync(req);
httpResponseFromService.EnsureSuccessStatusCode();
if (httpResponseFromService.Content is object
&& httpResponseFromService.Content.Headers.ContentType.MediaType == "application/json")
{
string stringFromservice = await httpResponseFromService.Content.ReadAsStringAsync();
JObject tokenresponse = JsonConvert.DeserializeObject<JObject>(stringFromservice);
string token = tokenresponse["access_token"].Value<string>();
return token;
}
else
{
_logger.LogError($"Cannot get token for Microsoft Graph. httpResponseFromService.Content:{httpResponseFromService.Content}" );
throw new Exception("Cannot get token for Microsoft Graph.");
}
}
The provider configuration in my Bot is the following, is it using as Service Provider: Azure Active Directory v2:
This is an example of a custom token generated with an OAuth tool (tenant id and other values changed to just illustrate the data, but all these values match and are correct when working with them), it is calling to the same url "login.microsoftonline.com" that I am trying to call to generate the custom scope token:
This generated custom scope token works. It has been configured at my Tenant Azure level as "api://botid-GUID/access_as_user" but I would like to generate it via http client as my code example. Would you know how can I get a token using this custom scope with a similar httpClient approach? It seems the scope parameter that I am sending ("api://botid-GUID/access_as_user") is not correct for client_credentials grant type call:
Default scope:
dict.Add("client_id", _graphTokenSettings.ClientId);
dict.Add("client_secret", _graphTokenSettings.ClientSecret);
dict.Add("scope", "https://graph.microsoft.com/.default");
dict.Add("grant_type", "client_credentials");
Replaced by:
dict.Add("client_id", _graphTokenSettings.ClientId);
dict.Add("client_secret", _graphTokenSettings.ClientSecret);
dict.Add("scope", "api://botid-GUID/access_as_user");
dict.Add("grant_type", "client_credentials");
Any help will be very appreciated.
I tried to reproduce the same in my environment and got below results:
I have one Azure AD application where I created one custom scope by exposing the API like below:
I registered another application named ClientApp and added above custom scope by granting consent like below:
In my Azure Bot, I added one connection setting with Service Provider as Azure Active Directory v2 like below:
When I ran Test connection, I got the token successfully like below:
When I decoded the above token, I got claims with scope as below:
When you create custom scope by exposing an API, it comes under Delegated permissions that involves user interaction like below:
Note that, client credential flow only works with Application
permissions that does not involve user interaction.
You need to create App role instead of exposing the API in the application with different unique value access-as-user like below:
You can add above App role to your client application that comes under Application permissions and make sure to grant consent as below:
In addition to that, client credentials grant type supports scope that ends with only /.default while using v2 endpoint. Otherwise, it will throw exception like below:
To resolve the above error, you need to replace scope with /.default at end like below while generating token:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
client_id:appID
grant_type:client_credentials
client_secret:secret
scope: api://87xxxa-6xex-4dxa-9xaf-b1dxxxx9819/.default
Response:
When I decoded the above token, I got claims with roles as below:
Note that, decoded token contains Application permissions in roles claim whereas Delegated permissions in scp claim.
In your scenario, if you want to use custom scope with client credentials grant type, you need to create App role with unique value that comes under Application permissions.
Make sure to change scope with /.default at end.

ADB2C and MSAL with separate signup and signin policies causing issues

I'm trying to use ADB2C custom policies with the MSAL.js library on a static web app
Our policies are reasonably complex, so we've split the 'signup' into it's own flow, but now I'm having trouble with the handover from signup to signin. From what I'm reading it sounds like each IEF policy assigns its own tokens, and so a token generated from a signup flow cannot be used to signin? Even if they're both associated with the same ADB2C tenant?
Is this correct? It seems odd if so, as the keys (behind the discovery document/jwks_uri) are identical on both policies, as is the issuer.
The error that msal reports is during an 'acquireTokenFromNetworkStart' request which returns a 400 Bad Request Silent SSO could not be completed - insufficient information was provided. Please provide either a loginHint or sid.
So perhaps I just need to adjust the session management on the ADB2C policies? Do I need to emit the sid (session id) and then use that with msal when re-acquiring tokens?
Any advice would be most welcome, I cannot find a well documented example that puts all of this together.
Please check if it can be worked around like below .
Try to include your scope in the initial loginRedirect or loginPopup or while calling acquireTokenRedirect or acquireTokenPopup before calling acquireTokenSilent.
Scopes that is created in expose an api like "user.read", "mail.send" in your login request in code and grant consent for the same .
That means we need to Call acquireTokenSilent with your resource scope.
var loginRequest = {
scopes: ["openid","user.read", "mail.send"]
};
try {
msalInstance.loginRedirect(loginRequest);
} catch (err) {
// handle error
}
In B2C ,your tenant will need to be configured to return the emails claim on idTokens .Reference
Also try to include application Id or app id uri in the scopes .
References:
microsoft-authentication-library-for-js/issues
cannot-get-access-token-in-react-app/SO Reference

.Net Core 3.1 Azure Web App - Failed to acquire token silently as no token was found in the cache. Call method AcquireToken

I have an Azure Web App that authenticates a user which then navigates to a page where some Sharepoint documents are retrieved and displayed in the app.
Most of the time the application works fine, but ocassionally App Insights will highlight that Failed to acquire token silently as no token was found in the cache. Call method AcquireToken. Some users report issues from time to time on this page (it's inconsistent so it might happen a few times a day with a somewhat large user base). The problem is that currently the error isn't handled and I'm trying to figure out how to make the call to AcquireTokenAsync.
The following is the method that returns the token (or doesnt):
private async Task<string> GetUserAccessToken()
{
try
{
// Credentials for app
// _clientId and _clientSecret represent the app info - not shown here in code
ClientCredential credential = new ClientCredential(_clientId, _clientSecret);
//Construct token cache
ITokenCacheFactory cacheFactory = Request.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
TokenCache cache = cacheFactory.CreateForUser(Request.HttpContext.User);
AuthenticationContext authContext = new AuthenticationContext(_authority, cache);
// guid of the user currently logged into the app
string objectID = _userObjectId;
UserIdentifier userIdentifier = new UserIdentifier(objectID, UserIdentifierType.UniqueId);
string resource = "https://test.sharepoint.com";
AuthenticationResult result = await authContext.AcquireTokenSilentAsync(resource, credential, userIdentifier);
return result.AccessToken;
}
catch (Exception ex)
{
throw ex;
}
}
If I understand the flow correctly, the web app here will request a token using it's own credentials on behalf of the user currently logged in. (Am I right in understanding this based on the method signature which states - Identifier of the user token is requested for. This parameter can be Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier.Any.)
Now when this fails, I would need to make a call to AcquireTokenAsync. There are a number of these methods available and I can't seem to find the one that will fulfill this requirement.
Before the suggestion comes, I can't use AcquireTokenAsync(resource, clientId, redirectUri,new PlatformParameters(PromptBehavior.Auto)); because the constructor on PlatformParameters has changed and requires an implementation of a ICustomWebUi and this flow isn't supported on .Net Core 3.1 as far as I'm aware which makes this unusable.
AcquireTokenAsync(resource, credentials) works and returns a token, however, when using that token I get a 401 Unauthorized when accessing the Sharepoint resources, most likely because the token is different and it is now requested on behalf of the application and not the user logged into the application (if I'm following this train of thought correctly...).
My question is - which method do I call? Is there something I would need to add before making the call to AcquireTokenAsync and if so, which of the 10 or so overloads should I use? I tried using AcquireTokenAsync(resource, credenetial, userAssertion) and passed in the AccessToken that I retrieved on the User logged in, but then I got Assertion failed signature validation or variations on that. If I understood correctly, the UserAssertion can be initialized with 1,2 or 3 parameters and I tried providing the AccessToken currently on the user that is logged in the app, but with no success.
Any help is greatly appreciated as I've been looking at this for two days now.
I spent more time investigating this, but none of the methods available would have worked in my case. The auth flow wasn't an on-behalf-of flow, but an auth-code flow. The link is to the newer MSAL library, but the concept is there. The application, a .net core web app, directs the user to sign in. When they sign in, an auth-code is passed into the response once they successfully authenticate.
The auth-code is then used to call AcquireTokenByAuthorizationCodeAsync(AuthCode, Uri, ClientCredential, UserIdentifier). This returns the valid access token that can be stored in the distributed token cache and then used to authenticate in order to access a given resource.
My biggest issue was that the error says you need to use AcquireTokenAsync to retrieve a new token. This is correct to a certain point, because in order to make any calls to any of the 14 or so methods you will need different bits of information, which will be dependent on the way you have setup your authentication flow in your application.
Because the application I worked on used auth code flow, I would need to get a new auth code. This would mean redirecting the user to login, capture the auth code in the response if the login was successful and then call the appropriate AcquireTokenAsync method that takes in an auth code as parameter along with app info, uri and so on.
To solve this, I used the information provided by the Microsoft Github page on Acquiring tokens with auth codes in web apps. Here I found samples on how auth flow is setup, but most importantly, how to trigger a new authentication flow if the user needs to be re-authenticated.
I wrapped the code that would throw the AdalSilentTokenAcquisitionException, catch the error and return a RedirectToAction.
return RedirectToAction("ActionName", "Controller", new RouteValues);
The above redirects the user to a given action, in a particular controller and passes through an object that can hold additional parameters. In my case it's a new { redirectUri = redirectUriString}, which is a string object that holds the URL the user would try to navigate this. I constructed this with a little method that uses the current HttpRequest to find the url the user was trying to get to.
Next, the controller that I had setup which responds to that redirect:
[HttpGet("/SignIn")]
public IActionResult SignIn([FromQuery(Name ="redirectUri")]string redirectUri)
{
return Challenge
(
new AuthenticationProperties { RedirectUri = WebUtility.UrlDecode(redirectUri) },
OpenIdConnectDefaults.AuthenticationScheme
);
}
Here, a Challenge is returned. The challenge triggers a call to the authentication flow that was setup in the Startup class. I think the entire flow here is that the method will send people to go through whatever is in that startup, which, in the case of the application I worked on, it prompts the user to sign in, captures the auth code, requests a new access token and once this is received and saved in the distributed token cache, the user is redirected to the redirectUri that I passed through.
I hope this helps or at least gives a starting point to anyone who might encounter a similar issue.

Authorization_IdentityNotFound while calling https://graph.microsoft.com/v1.0/organization API

I want, when user login he should get list of tenant, from that list, user decide in which tenant he want to redirect
I want to call https://graph.microsoft.com/v1.0/organization API but when I write code to call it , it will returned error, I have get token using below code, it is worked for users API of Graph, but not working for organization api
B2BGraphClient.AccessToken = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", credential).ConfigureAwait(false);
I have checked it using Postman, when I have pass token generated using https://login.microsoftonline.com/common/oauth2/token this api, organization api returns correct output, but logically it is not possible in code to pass userid and password to api and get token, below is image of postman call
I want correct way to do this
Your code is using Client_credentials flow but in Postman you are using ROPC flow.
The two flows use different permission type. Client_credentials flow uses Application permission while ROPC flow uses Delegated permission.
So for Client_credentials flow, if the app belongs to a work or school (organization) context then for https://login.microsoftonline.com/common/oauth2/token replace common with a tenantId or domain name. If you don't do this, you will get the Authorization_IdentityNotFound error.
Specify the tenant in the code (modify the sample code based on your needs).
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tenant = tenant;
// The AuthenticationContext is ADAL's primary class, in which you indicate the direcotry to use.
this.authContext = new AuthenticationContext("https://login.microsoftonline.com/" + tenant);
// The ClientCredential is where you pass in your client_id and client_secret, which are provided to Azure AD in order to receive an access_token using the app's identity.
this.credential = new ClientCredential(clientId, clientSecret);
B2BGraphClient.AccessToken = await this.authContext.AcquireTokenAsync("https://graph.microsoft.com/", this.credential).ConfigureAwait(false);
And don't forget to add one of the following Application permissions Organization.Read.All, Directory.Read.All, Organization.ReadWrite.All, Directory.ReadWrite.All in your app registration as per Permissions.
UPDATE:
In fact this endpoint https://graph.microsoft.com/v1.0/organization can't return the tenants which the user is member of.
You have found the correct Azure rest API to list the tenants.
But this API also doesn't support client_credentials flow.
In another word, you cannot use authContext.AcquireTokenAsync to get the token. You should consider AcquireTokenByAuthorizationCodeAsync. And specify the scope as https://management.azure.com/ instead of https://graph.microsoft.com.
Don't forget to add the Azure Rest permission in app registration.

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.)

Resources