Using existing AAD token to request token for Kusto - oauth

I'm trying to have a user log in to my bot (Microsoft bot framework) and make Kusto queries. I have been successful in getting a Graph JWT but I can't figure out how to then request Kusto access as well.
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-*********");
var clientAppID = "974c5166-f8b5-466e-9552-*********";
var secret = "[*SxczFa7qU*******************";
var tokenForKusto = await authContext.AcquireTokenAsync(
"https://aznw.kusto.windows.net",
new ClientCredential(clientAppID, secret),
new UserAssertion(userToken));
I expect the tokenForKusto to be a valid token but instead it throws an exception.
{"AADSTS500131: Assertion audience does not match the Client app presenting the assertion. The audience in the assertion was '00000002-0000-0000-c000-000000000000' and the expected audience is '974c5166-f8b5-466e-9552-********' or one of the Application Uris of this application with App ID '974c5166-f8b5-466e-9552-********'(testbotbuilderAAD).\r\nTrace ID: 38515779-b858-4efc-8b03-e9f8ec810c00\r\nCorrelation ID: a136600d-66e0-44fc-8f24-d1fda6b4ba56\r\nTimestamp: 2019-07-19 17:23:27Z"}

This is because you are trying to use the token issued for Graph, but your application is not Graph. This flow will only work if and only if the client app ID (from the ClientCredentials) matches the audience of the token you are attempting to use.
Why would you be retrieving tokens for Graph anyway?
If you want to issue Kusto queries, you should use the cluster URI as the resource (e.g., "https://cluster.region.kusto.windows.net")

Related

Error when querying Microsoft Graph API Shifts: "MS-APP-ACTS-AS header needs to be set for application context requests"

We are trying to query shifts in the Microsoft Graph API using a C# app, now that StaffHub got deprecated , in the past we were getting an Unknown Error which looked like a permissions issue.
In the docs I noticed permissions for Schedule.ReadAll and Schedule.ReadWriteAll so I added them to the application permissions in our App Registration in Azure.
Now when we send the request to https://graph.microsoft.com/beta/teams/{teamid}/schedule we get this error:
Microsoft.Graph.ServiceException: 'Code: Forbidden Message: {"error":{"code":"Forbidden","message":"MS-APP-ACTS-AS header needs to be set for application context requests.","details":[],"innererror":{"code":"MissingUserIdHeaderInAppContext"}}}
The documentation says the Schedule permissions are in private preview, are these required for querying a schedule & shifts, and if so, is it possible to request access to the private preview?
I'm in the same situation. It's possible to request private preview access (we have), but I'm guessing that it's primarily granted to Microsoft partners or at least have a connection at Microsoft.
The workaround for me has been getting access on behalf of a user. It does however require the user to enter username and password in order to get an access token, so it might not be a perfect solution for you. But it works. You need to add (and, I believe, grant admin consent for) delegated permissions for this to work, either Group.Read.All or Group.ReadWrite.All.
Edit:
I've got it working now. We have private preview access, so I'm not sure this will help you unless you do too, but as I understand it will be available eventually. Given your question, I presume you already have an access token.
Add MS-APP-ACT-AS as a header with the user ID of the user you want the Graph client to act as.
If you're using the Graph SDK for .NET Core you can just add a header to the authentication provider:
public IAuthenticationProvider GetAuthenticationProviderForActingAsUser(string userId, string accessToken)
{
return new DelegateAuthenticationProvider(
requestMessage =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
// Get event times in the current time zone.
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
requestMessage.Headers.Add("MS-APP-ACTS-AS", userId);
return Task.CompletedTask;
});
}
Then you call the graph service client:
var authenticationProvider = GetAuthenticationProviderForActingAsUser(userId, accessToken);
var graphClient = new GraphServiceClient(authenticationProvider);
You should then be able to fetch the shifts:
var shifts = await graphClient.Teams[teamId].Schedule.Shifts
.Request()
.AddAsync(shift);

Call Graph API from MVC App

SUMMARY UPDATE:
I got a sample working today thanks to the many good replies. Thanks all. My primary goal was to get current user information (ME) without using secret key. First I just used the secret key from the App Reg and this will authenticate the App and not the user. This does of course not work when calling ME. My next finding was if you want the users token, you still need the App Reg token, and then you request the users token. This requires less permissions on the App Reg, but requires to request two tokens. I ended up skipping ME and just requesting information for a specified user (through the APp Reg permissions):
$"https://graph.microsoft.com/v1.0/users/{email}/$select=companyName"
Both both approaches should be viable. I updated code below with working sample.
I am trying to do a very simple call to graph API to get companyName from current user. Found some samples but they seemed to be very complicated. The MVC app is authenticated trough an Application Registration in AAD.
I guess the application registration needs to be authorized to access Graph API. Or is more needed here? Getting company name should be fairly simple:
https://graph.microsoft.com/v1.0/me?$select=companyName
Does anyone have a snippet for calling the graph API, my best bet would be you need to extract a bearer token from the controller? ALl help is appreciated.
Working snippet:
public async Task<ActionResult> Index()
{
string clientId = "xxx";
string clientSecret = "xxx";
var email = User.Identity.Name;
AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/xxx.onmicrosoft.com/oauth2/token");
ClientCredential creds = new ClientCredential(clientId, clientSecret);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", creds);
HttpClient http = new HttpClient();
string url = $"https://graph.microsoft.com/v1.0/users/{email}/$select=companyName";
//url = "https://graph.windows.net/xxx.onmicrosoft.com/users?api-version=1.6";
// Append the access token for the Graph API to the Authorization header of the request by using the Bearer scheme.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
HttpResponseMessage response = await http.SendAsync(request);
var json = response.Content.ReadAsStringAsync();
return View();
}
To add one last item, here is a link to an MVC sample on Git that uses an MVC application to send email. It illustrates how to call the MS Graph API to get various pieces of information. Keep in mind, if you are using an application only scenario, ME will not work, the sample illustrates how to obtain a delegated token for a user and use that toke to do work:
https://github.com/microsoftgraph/aspnet-connect-rest-sample
If I am reading this code snippet correctly, You are requesting a application only token for the Graph.Microsoft.Com resource, then attempting to use that toke with this URI:
url = "https://graph.windows.net/thomaseg.onmicrosoft.com/users?api-version=1.6"
This will not work because you are mixing resources, AAD Graph and MS Graph. The ME endpoint does not make since in this scenario because you are using the application only flow. This flow does not support the ME endpoint. ME is designed for use with a delegated token. the ME endpoint represents the signed in user, since and application is not a user, ME is meaningless.
You will need to target the user specifically:
https://graph.microsoft.com/v1.0/Users/[UPN or ID of user]?$select=companyName
Should work if your application has been granted the appropriate permission scopes.

App-Only Microsoft Graph authentication with Microsoft.Graph library

I am successfully retrieving an access token for the Microsoft Graph API with the App-Only flow, but the produced token can't seem to access anything.
Here is the authentication code I'm using:
var clientApp = new ConfidentialClientApplication(
identifier,
authority,
"urn:ietf:wg:oauth:2.0:oob",
new ClientCredential(secret), null, null);
var scopes = new string[] { $"{identifier}/.default" };
AuthenticationResult authResult = await clientApp.AcquireTokenForClientAsync(scopes);
return authResult.AccessToken;
From that, I do indeed get a token, but when I try to use it, it throws Access token validation failure. Here's the test query I've been using:
var users = service.Users.Request()
.Filter($"mail eq '{resourceIdentifier}'")
.Top(1)
.GetAsync();
users.Wait();
For the API baseUrl, I was providing: https://graph.windows.net/{appId}. I did add api-version=1.6 to the query string (manually, as I don't see an option exposed through the Microsoft.Graph NuGet library). I had earlier tried https://graph.microsoft.com/v2.0, also to no avail.
Anyway, given the error messages about validation failure, I have come to believe that our (possibly tenant-specific?) API URI might be wrong. Could that be it? What am I not seeing?
Update
The solution had two components. The first was as mentioned in the accepted answer. The second was that the scope should be, simply, https://graph.microsoft.com/.default, despite my API calls being tenant-specific.
You're conflating two different APIs here.
The graph.windows.net URI is for the Azure AD Graph which is an entirely different API. For Microsoft Graph the URI should be graph.microsoft.com.
There is also isn't a /v2.0 of Microsoft Graph today. The publicly available versions are /v1.0 and /beta. Also note that when using the Microsoft Graph Client Library for .NET you shouldn't need to provide a baseUrl as it already defaults to https://graph.microsoft.com/v1.0.

Microsoft Graph API access token's signature verification is failed

I have a Angular 4 site that I’m trying to use Microsoft Graph implicit flow to authenticate users then use token to call our APIs at another endpoint, so I use msal.js to get the access token.
After I bring the access token to my API endpoint and try to valid it, the token cannot be valid. I got a SignatureVerificationFailedException.
My understanding is that the access token is for Microsoft Graph API, not for my APIs, so I cannot valid it. (I can use it to call Graph API without problem)
How can I get a access token(not id token) using msal.js that can be used for my APIs but not Microsoft Graph? Thanks!
The reason I'm sending access token instead of id token to the API endpoint is that I want to get the puid claim from the token, which is not available for id token.
Here is what I was trying to valid the access token I got from client which is using msal.js
const string authority = "https://login.microsoftonline.com/common";
const string audience = "https://graph.microsoft.com";
string issuer = null;
string stsDiscoveryEndpoint = $"{authority}/v2.0/.well-known/openid-configuration";
List<SecurityToken> signingTokens = null;
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
var config = await configManager.GetConfigurationAsync();
issuer = config.Issuer;
signingTokens = config.SigningTokens.ToList();
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidAudience = audience,
ValidIssuer = issuer,
ValidateIssuer = false,
IssuerSigningTokens = signingTokens,
CertificateValidator = X509CertificateValidator.None
};
try
{
// Validate token.
SecurityToken validatedToken = new JwtSecurityToken();
var claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out validatedToken);
var claimsIdentity = claimsPrincipal.Identity as ClaimsIdentity;
return ExtractAuthenticatedUserFromClaimsIdentity(claimsIdentity);
}
catch (SignatureVerificationFailedException)
{
throw;
}
Thanks,
If you want to get an access token for your API rather than the Microsoft Graph API, you must specify your API as the resource in the token request.
Make sure that:
Your Web API has configured OAuth2Permission Scopes. See here. Configuring a resource application to expose web APIs
Your Client Application has selected permissions to those exposed APIs. Configuring a client application to access web APIs
Finally, make sure you use your Web API's App ID URI or App ID GUID as the resource value in your token request.
Let me know if this helps!

Microsoft Graph API access token validation failure

I use this URL to get id_token:
https://login.microsoftonline.com/common/oauth2/authorize?
response_type=id_token%20code&
client_id=MY_CLIENT_GUID_ID_IN_HERE&
redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fopenid%2Freturn&nonce=alfaYYCTxBK8oypM&
state=6DnAi0%2FICAWaH14e
and this return result like this
http://localhost:3000/auth/openid/return?
code=AAA_code_in_here&
id_token=eyJ0eXAi_xxxx_yyyy_in_here&
state=6DnAi0%2FICAWaH14e&
session_state=xxxx_guid_xxxxx
and then i use the id_token to query Graph (use POST man)
i have see this post InvalidAuthenticationToken and CompactToken issues - Microsoft Graph using PHP Curl but make no sense.
OATH 2.0 requires multiple steps. The first request returns an OAUTH Code. The next step is converting that OATUH code into a Bearer Token. This is the step you are missing here.
I would also recommend using the v2 Endpoint which is a lot easier to work with (particularly with Graph). I wrote a v2 Endpoint Primer that walks through the process and may be helpful as well.
You can't use the token directly, there is one more step to exchange the code you get from the response url into token.
Here is my C# code (using Microsoft.IdentityModel.Clients.ActiveDirectory)
public static AuthenticationResult ExchangeCodeForToken(string InTenantName, string InUserObjId, string InRedirectUri, string InApplicationAzureClientID, string InApplicationAzureClientAppKey)
{
Check.Require(!string.IsNullOrEmpty(InTenantName), "InTenantName must be provided");
Check.Require(!string.IsNullOrEmpty(InUserObjId), "InUserObjId must be provided");
if (CanCompleteSignIn) //redirect from sign-in
{
var clientCredential = new ClientCredential(InApplicationAzureClientID, InApplicationAzureClientAppKey);
var authContext = new AuthenticationContext(Globals.GetLoginAuthority(InTenantName), (TokenCache)new ADALTokenCache(InUserObjId)); //Login Authority is https://login.microsoftonline.com/TenantName
return authContext.AcquireTokenByAuthorizationCode(VerificationCode, new Uri(InRedirectUri), clientCredential, Globals.AZURE_GRAPH_API_RESOURCE_ID); //RESOURCE_ID is "https://graph.microsoft.com/"
}
return null;
}
I had this issue today when I was playing with graph API, the problem in my case was how I was generating the token.
I used postman for generating the token wherein the Auth URL section I was adding the resource = client_id whereas it should be the graph URL. After making that change I was able to make the call via postman.
In order for the above to work, please make sure your application in Azure has delegated permissions to access the Graph API.
To receive the access token and use it for profile requests, you don't need anything from server-side, you can implement the oAuth2 just from the client side.
Use the following URL for login:
https://login.microsoftonline.com/common/oauth2/authorize?client_id=YOUR_CLIENT_ID&resource=https://graph.microsoft.com&response_type=token&redirect_uri=YOUR_REDIRECT_URI&scope=User.ReadBasic.All
After successful login, user will redirected to the page with access_token parameter. Then use the following AJAX call to fetch user info:
var token = login_window.location.href.split('access_token=').pop().split('&')[0];
$.ajax({
url: "https://graph.microsoft.com/v1.0/me",
type: "GET",
beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Bearer '+token);},
success: function(data) {
alert('Hi '+data.displayName);
console.log(data);
}
});
Note that you may need to enable oauth2AllowImplicitFlow:true setting from your Azure Active Directory application manifest file.
Set "oauth2AllowImplicitFlow": false to "oauth2AllowImplicitFlow": true.
Lastly, ensure that your app has required permissions for Microsoft Graph which are sign in users and View users' basic profile
An updated answer to get access with new applications:
Register your app in the app registration portal.
Authorization request example:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?client_id=6731de76-14a6-49ae-97bc-6eba6914391e&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F&response_mode=query&scope=offline_access%20user.read%20mail.read&state=12345
Authorization response will look like this:
https://localhost/myapp/?code=M0ab92efe-b6fd-df08-87dc-2c6500a7f84d&state=12345
Get a token
POST /{tenant}/oauth2/v2.0/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&scope=user.read%20mail.read
&code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr...
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&grant_type=authorization_code
&client_secret=JqQX2PNo9bpM0uEihUPzyrh // NOTE: Only required for web apps
Use the access token to call Microsoft Graph
GET https://graph.microsoft.com/v1.0/me
Authorization: Bearer eyJ0eXAiO ... 0X2tnSQLEANnSPHY0gKcgw
Host: graph.microsoft.com
Source:
https://learn.microsoft.com/en-us/graph/auth-v2-user?context=graph/api/1.0
You can also get an access token without a user, see here:
https://learn.microsoft.com/en-us/graph/auth-v2-service

Resources