Authorize a client to browse Office365 Graph API - azure

I am trying to develop a webapp to let a user browse his Active Directory contacts.
I have two accounts: one to register the application (developer account) and the other that would be the general user who have access to Office365 (user account).
I have set up a Javascript client following the Azure AD Graph API documentation.
By now, I am able to prompt the user to login and retrieve an access token, but when I try to make a request, I always get a 401 error.
I am pretty new to Azure, so I don't really understand if the problem is in my application configuration or in my code.
I am able to browse the Graph API explorer with my user account, so I don't think he's missing the authorization to access it.
I am really confused.
I try to add all the steps I am doing, hoping someone could point out the error.
Request 1:
Url: https://login.windows.net/{my tenant id or common (both are working) }/oauth2/authorize
Method: GET
Params:
redirect_uri // my redirect url, the same I registered in my application. It is just a page that returns the content of the URL
client_id // my client id
response_type // code
state // a random generated string, not required, but reccomanded
resource // https://graph.windows.net
Response 1:
code // A long string
state // The string I sent in the request
session_state // Another string
Request 2:
Url: https://login.windows.net/{my tenant id or common (both are working) }/oauth2/token
Method: POST
Params:
redirect_uri // it won't be necessary, but in some post they reccomand to add it
client_id // my client id
client_secret // my client secret
code // the code retrieved from Request 1
grant_type // authorization_code
state // a random generated string
resource // https://graph.windows.net
Response 2:
token_type // Bearer
access_token // a long token
id_token // exploring it with the JWT tool, shows it has the correct app id
refresh_token // a long code
resource // the same I sent in the request
scope // Directory.Read UserProfile.Read
expires_in
expires_on
a couple of other irrelevant keys
Request 3:
Url: https://graph.windows.net/{the domain the logged account belong to}/contacts
Method: GET
Headers:
Authorization: Bearer {the access token retrieved from request 2}
Params:
api-version = 1.5 // The value suggested in the documentation.
Response 3:
{
"odata.error": {
"code": "Authentication_MissingOrMalformed",
"message": {
"lang": "en",
"value": "Access Token missing or malformed."
},
"values": null
}
}
This is the content of my Access Token:
{
typ: "JWT",
alg: "RS256",
x5t: "foofoofoofoo"
}.
{
aud: "https://graph.windows.net",
iss: "https://sts.windows.net/<SOMEGUID>/",
iat: 1418224761,
nbf: 1418224761,
exp: 1418228661,
ver: "1.0",
tid: "<SOMEGUID>",
amr: [
"pwd"
],
idp: "https://sts.windows.net/<SOMEGUID>/",
email: "myuseremail#contoso.com",
unique_name: "myuseremail#contoso.com",
sub: "barbarbarbar",
altsecid: "<an-id>",
family_name: "Last Name",
given_name: "First Name",
appid: "<MY APP ID>",
appidacr: "1",
scp: "Directory.Read UserProfile.Read",
acr: "1"
}
Answer
So, it looks like user must have the "can consent" authorization to authenticate to his own active directory, and this can be provided just by the administrator.
I posted the same request on the MSDN forum and I got the same answer.
I want sincerely thank #Jason Johnston and #Dan Kershaw since this problem was driving me nut and I would never be able to sort it out without their help.
Unfortunately I can award the bounty to just one of them, so I decided to give it #Jason Johnston for the great patience he had in supporting me in this and another discussion.

I believe if you actually want to browse the Active Directory, and not just read the authenticated user's profile, you need administrator consent for a web app. See http://msdn.microsoft.com/en-us/library/azure/b08d91fa-6a64-4deb-92f4-f5857add9ed8#BKMK_Graph
If you already knew that, then maybe it's a problem with how you've registered your app or the token itself. Make sure you've selected the appropriate permissions per that link in your app registration. If those look right, then you can check the token. There's a handy little token parser here: http://jwt.calebb.net/. Just paste in the value of your token and it will show you the decoded JSON. Look at the scope or scp parameters.
{
"typ": "JWT",
"alg": "RS256",
"x5t": "asdfsadfasdfsa"
}
{
"aud": "https://graph.windows.net/",
"iss": "https://sts.windows.net/<SOMEGUID>",
"iat": 1418158549,
"nbf": 1418158549,
"exp": 1418162449,
"ver": "1.0",
"tid": "<SOMEGUID>",
"amr": [
"pwd"
],
"oid": "<SOMEGUID>",
"upn": "admin#contoso.com",
"unique_name": "admin#contoso.com",
"sub": "askdljalsdfs",
"puid": "1003BFFD88937280",
"family_name": "Administrator",
"given_name": "MOD",
"appid": "<YOUR APP ID>",
"appidacr": "0",
"scp": "Directory.Read user_impersonation UserProfile.Read",
"acr": "1"
}

The token you've pasted is missing the OID and UPN, and that'll probably be why you are seeing this error. I'll have to go back and check how this access token could have been issued without those claims.

Related

Microsoft OAuth 2.0 Authentication Failure - token_url Bad Request Error

I want to ensure that my OAuth 2.0 authentication credentials. So, I have client_id, client_secret, tenant_id, scope, token_url and auth_url. I am using third party app in order to send a email but, I get error in OAuth 2.0 part as Bad Request Error for token_url.
In this case, when grant_type='client_credentials', how can I fix token_url domains or subdomains?
-> token_url like that https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/token
If you are dominate the this subject, Could you please mention about OAuth 2.0 process?
I tried to changed grant_type parameter as client_credentials. Default was 'refresh_token'.
In this case, it returned like that error:
(530, b'5.7.57 Client not authenticated to send mail. Error: 535 5.7.3 Authentication unsuccessful [ZR2P278CA0041.CHEP278.PROD.OUTLOOK.COM 2023-02-14T08:10:26.343Z 08DB0DEF6EA5D39B]', 'myemail#myemail.com')
2023-02-14 11:10:26,378 INFO [decorators] [send_email] StatusMessage: An error occurred while sending the email: (530, b'5.7.57 Client not authenticated to send mail. Error: 535 5.7.3 Authentication unsuccessful [ZR2P278CA0041.CHEP278.PROD.OUTLOOK.COM 2023-02-14T08:10:26.343Z 08DB0DEF6EA5D39B]', 'myemail#myemail.com')
2023-02-14 11:10:26,379 INFO [decorators] [send_email] StatusMessage: Done with sending email...
So, what is the main problem in this case?
Thank you for your attention,
I tried to reproduce the same in my environment and got the results as below: 
I created an Azure AD Application and granted admin consent to Mail.Send API permission:
  
Based on your requirement you can make use of Client Credential Flow or Authorization Code Flow.
If you want user interaction then make use of Authorization Code Flow.
If you want to send mail as Application then make use of Client Credential Flow. 
I generated the access token via Client Credential Flow by using parameters like below:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
scope:https://graph.microsoft.com/.default
grant_type:client_credentials
 
 To send the mail, I used below query:
https://graph.microsoft.com/v1.0/users/FromAddress/sendMail 
{
"message": {
"subject": "Test mail",
"body": {
"contentType": "Text",
"content": "Test"
},
"toRecipients": [
{
"emailAddress": {
"address": "****"
}
}
],
"ccRecipients": [
{
"emailAddress": {
"address": "****"
}
}
]
},
"saveToSentItems": "false"
}
 
 Reference: 
user: sendMail - Microsoft Graph v1.0 | Microsoft Learn

Unable to call `/oauth2/v2.0/token` even with the correct redirect/reply URL

I am following this document and currently stuck at 3rd step, Get a token:
I have no issue with the 2nd step, Get authorization as I could sign in (from the browser) and it responded with the code for the next step (3rd step).
BUT when it gave me the response below when sending a POST request (/common/oauth2/v2.0/token):
{
"error": "invalid_client",
"error_description": "AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application: 'afef958a-7070-4b2d-9006-65b28c9aed43'.\r\nTrace ID: 2e478767-2adc-468c-b716-6134ca2d3a04\r\nCorrelation ID: 7117f8d0-2a9f-4110-8257-b32753876afb\r\nTimestamp: 2022-01-06 08:22:48Z",
"error_codes": [
50011
],
"timestamp": "2022-01-06 08:22:48Z",
"trace_id": "2e478767-2adc-468c-b716-6134ca2d3a04",
"correlation_id": "7117f8d0-2a9f-4110-8257-b32753876afb",
"error_uri": "https://login.microsoftonline.com/error?code=50011"
}
My request in Postman here
My Azure config here
My response on browser here
My code (PHP):
$response = $client->request(
"POST",
"/common/oauth2/v2.0/token",
[
"headers" => [
"Content-Type" => "application/x-www-form-urlencoded",
],
"form_params" => [
"tenant" => $tenantId,
"client_id" => $clientId,
"grant_type" => "authorization_code",
"scope" => "User.Read",
"code" => $_GET["code"],
"redirect_uri" => $replyUrl,
"client_secret" => $clientSecret,
]
]
);
Note:
I have tried with Accounts in any organizational directory and Accounts in any organizational directory and personal Microsoft accounts, both of them are giving me the same response
I am sure the client secret is correct
I am sure the redirect/reply URL is correct too
I have done enough research online, but no similar solutions that could solve this issue
This occurs when the login code in your app (js/ts) is not setting the redirectUrl value to something that matches what your app is configured to answer as a redirect Url in your Azure portal. You haven't sent enough code to see what your redirect looks like, but it's not clear why you're doing it by hand (instead of using MSAL), and also why you've tagged this as "microsoft-teams" - this is important because, if you are building a Teams tag, then the usual process is a little different for Teams.
Update: the original question makes it clear now that this is a PHP scenario, which my answer above does not address - fyi to anyone reading this answer.

How to resolve Invalid Audience when I trye to verify if a user exists in Azure Active Directory?

Reading the documentation of Microsoft Graph, I found an example to connect to the Azure Active Directory and verify if a previously registered user exists.
The problem is that the example throws this error when I try to do the request:
Graph service exception Error code: InvalidAuthenticationToken
Error message: Access token validation failure. Invalid audience.
My code is practically the same as the documentation shows how to do it. This is the code:
List<String> scopes = new ArrayList<String>();
String clientId = "XXXXXXX";
String clientSecret = "YYYYYYYY";
String tenantId = "ZZZZZZZZZ";
String permissions = "api://" + clientId + "/.default";
scopes.add(permissions);
final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
.clientId(clientId)
.clientSecret(clientSecret)
.tenantId(tenantId)
.build();
final TokenCredentialAuthProvider tokenCredentialAuthProvider =
new TokenCredentialAuthProvider(scopes, clientSecretCredential);
final GraphServiceClient graphClient =
GraphServiceClient
.builder()
.authenticationProvider(tokenCredentialAuthProvider)
.buildClient();
User resultUser = null;
try {
UserCollectionPage ucp = graphClient.users().buildRequest().filter(
"startsWith(mail,'" + email + "')").get();
List<User> result = ucp.getCurrentPage();
User u = result.get(0);
return new ResponseEntity<>(resultUser, HttpStatus.OK);
}
catch (IndexOutOfBoundsException e) {}
The connection to the Azure Active Directory looks fine, because it shows SUCCESS after login connection with the credentials:
2021-12-16 21:38:10.994 INFO 28072 --- [onPool-worker-1]
c.azure.identity.ClientSecretCredential :
Azure Identity => getToken() result for scopes api://570f77fe-098f-42cd-8a22-a29fa1d9c7c0/.default: SUCCESS
Another thing I want to show you, is the decoded token, may it can helps to bring me a solution:
Token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1yNS1BVWliZkJpaTdOZDFqQmViYXhib1hXMCIsImtpZCI6Ik1yNS1BVWliZkJpaTdOZDFqQmViYXhib1hXMCJ9.eyJhdWQiOiJhcGk6Ly81NzBmNzdmZS0wOThmLTQyY2QtOGEyMi1hMjlmYTFkOWM3YzAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8zOTdlZDAzMS0zOTM1LTQwYjAtOWM2OS0xNGZkMTE2NGRiOGYvIiwiaWF0IjoxNjM5NzA4Mzk5LCJuYmYiOjE2Mzk3MDgzOTksImV4cCI6MTYzOTcxMjI5OSwiYWlvIjoiRTJaZ1lOaTJjRGEveHRHMmZldTAxUTdxVHI1MUFnQT0iLCJhcHBpZCI6IjU3MGY3N2ZlLTA5OGYtNDJjZC04YTIyLWEyOWZhMWQ5YzdjMCIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzM5N2VkMDMxLTM5MzUtNDBiMC05YzY5LTE0ZmQxMTY0ZGI4Zi8iLCJvaWQiOiJiMmRlYTQ3NS1lODlhLTRiNjQtOGM5Mi0yMTg4MGM5ODhmMTYiLCJyaCI6IjAuQVNrQU1kQi1PVFU1c0VDY2FSVDlFV1Rial81M0QxZVBDYzFDaWlLaW42SFp4OEFwQUFBLiIsInN1YiI6ImIyZGVhNDc1LWU4OWEtNGI2NC04YzkyLTIxODgwYzk4OGYxNiIsInRpZCI6IjM5N2VkMDMxLTM5MzUtNDBiMC05YzY5LTE0ZmQxMTY0ZGI4ZiIsInV0aSI6IjNRQ1hJZGhMTVVLUnh3NkxwbndoQUEiLCJ2ZXIiOiIxLjAifQ.SU9kpXWs6fP-9T8QlPOJT8rKihPdtd38B8frOiS1I36T5LjewEyTmHgTEKWKgPhGxUHkmYWQxi6itNsn_4H_XUpgvVU2oNxoYsumQIW8rQZUx7hZeqxPrY3hbl_UfJgCtZ3J_0z6Ekk6QmBA-VBFEueq5lzjlARqYgTyQQ-uaNUtyrih4HyOkSkwcC8rs20UAjguunDVAzVucjweB0B2m9ib-uT1hhJlOihOwNtZ-A28QYNihp4r8HkriMaZMqutrdrVhH_--0OpF1O7lFEGEeDQeDozWi4SjboWJcODgsOGsZ7HxHd3Lx5mv8vJ0MvC8z_GIRWpuQqJuZ7eXQeFWg
Decoded token:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "Mr5-AUibfBii7Nd1jBebaxboXW0",
"kid": "Mr5-AUibfBii7Nd1jBebaxboXW0"
}.{
"aud": "api://570f77fe-098f-42cd-8a22-a29fa1d9c7c0",
"iss": "https://sts.windows.net/397ed031-3935-40b0-9c69-14fd1164db8f/",
"iat": 1639708399,
"nbf": 1639708399,
"exp": 1639712299,
"aio": "E2ZgYNi2cDa/xtG2feu01Q7qTr51AgA=",
"appid": "570f77fe-098f-42cd-8a22-a29fa1d9c7c0",
"appidacr": "1",
"idp": "https://sts.windows.net/397ed031-3935-40b0-9c69-14fd1164db8f/",
"oid": "b2dea475-e89a-4b64-8c92-21880c988f16",
"rh": "0.ASkAMdB-OTU5sECcaRT9EWTbj_53D1ePCc1CiiKin6HZx8ApAAA.",
"sub": "b2dea475-e89a-4b64-8c92-21880c988f16",
"tid": "397ed031-3935-40b0-9c69-14fd1164db8f",
"uti": "3QCXIdhLMUKRxw6LpnwhAA",
"ver": "1.0"
}.[Signature]
I get this token with this URL:
https://graph.microsoft.com/v1.0/
Using the following code:
url = new URL(urlHost);
token = tokenCredentialAuthProvider.getAuthorizationTokenAsync(url).get();
Work around on the error InvalidAuthenticationToken
Error message: Access token validation failure. Invalid audience
aud (audience) : this Identifies the intended recipient of the token - its audience.
Your API must validate this value and reject the token if the value doesn't match.
Based on your given details you are using version v1.0
In v1.0 tokens it can be the client ID or the resource URI used in the request, depending on how the client requested the token
To resolve this error, you need to make sure the audience in the token is https://graph.microsoft.com by using scope: https://graph.microsoft.com/.default during your token acquisition call
make sure below permissions are consented under the application whose client ID you are using during token acquisition call.
1)User.ReadWrite.All
2)Directory.ReadWrite.All
To provide consent, you need to navigate to:
1) Azure Portal > Azure Active Directory > App Registration > search the application using client ID > API Permissions > Add Permission
2) Click on the ADD Permission

Azure Function with Azure AD access token

I'm building a set of custom Azure Functions (Java) to be accessed by iOS and Android native clients using essentially and Oauth2 authentication strategy.
I'm able to successfully acquire a bearer access token from https://login.microsoftonline.com/XXXXX-XXXXXXXXX/oauth2/v2.0/token, but when I present this token to my Azure Function and attempt to validate it in code, I receive the following error. I'm doing something similar to the example provided here when validating the bearer token: https://dev.to/425show/secure-apis-with-azure-functions-java-azure-ad-and-ms-graph-49p1
AADSTS50013: Assertion failed signature validation. [Reason - The
provided signature value did not match the expected signature value.,
Thumbprint of key used by client: 'XXXXXXXXXXXXXXXXXXXXXXXXXX'
Documentation from Microsoft is absurdly hard to use for what should be such a standard use case. Any help would be greatly appreciated.
I have answered several similar questions before. The root cause is whether the access token used in the assertion is for the Microsoft Graph api resource or the web api resource. You can determine the issuer of the token by parsing the token to see the aud claim.
If you use this access token to call the Microsoft Graph api, then you only need to set the scope to: https://graph.microsoft.com/.default.
If you use the access token to call web api (this is the api you expose in the Azure portal), then you need to set the scope to: api://{your api application client id}/{scope name}.
Please note: You can only put one type of api resource in the scope.
I spent quite some time trying to resolve a similar issue, and while some answers point to the solution, none that I have seen offer much insight into the underlying cause. So I thought I would offer some of my own findings.
Here is a sample Spring controller that extracts the access token:
package com.matthewcasperson.onenote.controllers;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
public class Test {
#Autowired
OAuth2AuthorizedClientRepository clientRepository;
#GetMapping("/test")
public String test(HttpServletRequest request) {
final OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) SecurityContextHolder
.getContext()
.getAuthentication();
final OAuth2AuthorizedClient client = clientRepository
.loadAuthorizedClient(oauth2Token.getAuthorizedClientRegistrationId(), oauth2Token, request);
final String accessToken = client.getAccessToken().getTokenValue();
System.out.println(accessToken);
return "test";
}
}
This Spring application has been configured as a web application with the following application.yml file:
azure:
activedirectory:
tenant-id: ${TENANT_ID}
client-id: ${CLIENT_ID}
client-secret: ${CLIENT_SECRET}
The token that is printed by the controller decodes to this:
{
"typ": "JWT",
"nonce": "nx2pG8jshz2m6AY8eBafUJDXVgJNjjg526wpwsVRVoU",
"alg": "RS256",
"x5t": "l3sQ-50cCH4xBVZLHTGwnSR7680",
"kid": "l3sQ-50cCH4xBVZLHTGwnSR7680"
}.{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/2ed832a8-cd8c-4f7d-89b3-935447e260c8/",
"iat": 1633941558,
"nbf": 1633941558,
"exp": 1633945458,
"acct": 0,
"acr": "1",
"aio": "AUQAu/8TAAAAPyU5VrhiE9R+skcJpwWKTVMqg/DfvQs1i0CQFYQgUAAHS5uZI72LdEjZxRn7VjMNfaq8gL1CbRcoD1PYVjWPfQ==",
"amr": [
"pwd",
"mfa"
],
"app_displayname": "OneNote",
"appid": "abfb7030-ff37-48cd-8bd7-1bf5ec0777db",
"appidacr": "1",
"family_name": "Casperson",
"given_name": "Matthew",
"idtyp": "user",
"ipaddr": "45.67.96.131",
"name": "Matthew Casperson",
"oid": "4c037c2f-4b2f-46c1-a25f-d214e2396d47",
"platf": "3",
"puid": "1003200188788F6F",
"rh": "0.AUEAqDLYLozNfU-Js5NUR-JgyDBw-6s3_81Ii9cb9ewHd9tBACU.",
"scp": "openid profile User.Read email",
"signin_state": [
"kmsi"
],
"sub": "56Tq2KFJPnykNSAwOCFPSjq1s9kqybnOmJ99Nfnn53M",
"tenant_region_scope": "OC",
"tid": "2ed832a8-cd8c-4f7d-89b3-935447e260c8",
"unique_name": "matthewcasperson#matthewcasperson.onmicrosoft.com",
"upn": "matthewcasperson#matthewcasperson.onmicrosoft.com",
"uti": "WWzRGjCqAEWNwcH4le9TAQ",
"ver": "1.0",
"wids": [
"62e90394-69f5-4237-9190-012177145e10",
"b79fbf4d-3ef9-4689-8143-76b194e85509"
],
"xms_st": {
"sub": "gnpeDIbUpAnTYqfXYe3lNOwC2_MV5UvEIm3zvZK6LVc"
},
"xms_tcdt": 1632346078
}.[Signature]
Note the aud of https://graph.microsoft.com (I've also seen an aud of 00000003-0000-0000-c000-000000000000, which is the client id of the Microsoft Graph API).
Despite being a JWT, and thus something we can inspect, this particular token should be treated as opaque:
You should not be looking at the access token, as the access token is
meant for the resource (Graph in your case), and not the client (your
app). You can just use the claims in the id token in your case.
If you try to use this JWT to generate an OBO token, you will recieve a
similar error as the OP:
{
"error": "invalid_grant",
"error_description": "AADSTS50013: Assertion failed signature validation. [Reason - The provided signature value did not match the expected signature value., Thumbprint of key used by client: '977B10FB9D1C087E3105564B1D31B09D247BEBCD', Found key 'Start=02/07/2021 17:00:39, End=02/06/2026 17:00:39'].\r\nTrace ID: 384637e2-0853-4a10-b42a-a7cb53aa8500\r\nCorrelation ID: 1e9a639e-45e4-48e4-99b6-028ce43fe815\r\nTimestamp: 2021-10-11 08:54:13Z",
"error_codes": [
50013
],
"timestamp": "2021-10-11 08:54:13Z",
"trace_id": "384637e2-0853-4a10-b42a-a7cb53aa8500",
"correlation_id": "1e9a639e-45e4-48e4-99b6-028ce43fe815",
"error_uri": "https://login.microsoftonline.com/error?code=50013"
}
What I believe is happening here is this JWT is not meant to do anything other than obtain a /userinfo token that in turn contains the roles that an application would need to secure access to its code. Even though the access token is a JWT, it is meant to be treated as an opaque token, and can not be used in other flows like OBO.
You can create an authroized client that requires a scope defined on the Azure AD application. This scope is usually just some dummy value with no particular significance (I called my scope default, but any scope name would do):
azure:
activedirectory:
tenant-id: ${TENANT_ID}
client-id: ${CLIENT_ID}
client-secret: ${CLIENT_SECRET}
authorization-clients:
api:
scopes:
- api://${CLIENT_ID}/default
This client can be used like so:
#GetMapping("/test2")
public String test2(#RegisteredOAuth2AuthorizedClient("api") OAuth2AuthorizedClient api) {
final String accessToken = api.getAccessToken().getTokenValue();
System.out.println(accessToken);
return "test";
}
The access token created for this authorized client decodes to something like this:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "l3sQ-50cCH4xBVZLHTGwnSR7680",
"kid": "l3sQ-50cCH4xBVZLHTGwnSR7680"
}.{
"aud": "api://abfb7030-ff37-48cd-8bd7-1bf5ec0777db",
"iss": "https://sts.windows.net/2ed832a8-cd8c-4f7d-89b3-935447e260c8/",
"iat": 1633942486,
"nbf": 1633942486,
"exp": 1633946386,
"acr": "1",
"aio": "AVQAq/8TAAAAjlqywvUGKhdO5xKGTXyMxif6+n+PW+HQfqQUE2rz5vXQHmeqAWCJuWbxQEZJrn5FQwxPibQ/FZ8ZjsvlMWbscs8bG7AiCNwHepHg9HqCYTo=",
"amr": [
"pwd",
"mfa"
],
"appid": "abfb7030-ff37-48cd-8bd7-1bf5ec0777db",
"appidacr": "1",
"family_name": "Casperson",
"given_name": "Matthew",
"ipaddr": "45.67.96.131",
"name": "Matthew Casperson",
"oid": "4c037c2f-4b2f-46c1-a25f-d214e2396d47",
"rh": "0.AUEAqDLYLozNfU-Js5NUR-JgyDBw-6s3_81Ii9cb9ewHd9tBACU.",
"scp": "default",
"sub": "gnpeDIbUpAnTYqfXYe3lNOwC2_MV5UvEIm3zvZK6LVc",
"tid": "2ed832a8-cd8c-4f7d-89b3-935447e260c8",
"unique_name": "matthewcasperson#matthewcasperson.onmicrosoft.com",
"upn": "matthewcasperson#matthewcasperson.onmicrosoft.com",
"uti": "AqpGPbtGNkayP_368gJ1AQ",
"ver": "1.0"
}.[Signature]
Note the aud field is the Azure AD application rather than the Microsoft Graph API. I believe this token is mean to be transparent, meaning we can use it for other flows like OBO. Indeed this token works as you would expect when using it to create OBO tokens.
It doesn't make much sense for a web app to use an OBO flow, as web apps can simply ask for the appropiate access tokens directly with any required user permissions as part of an authorization code flow. So the above exmaple is a bit of a hack, but highlights the differences between the opaque tokens you get automatically, and the transparent ones you get through an authroized client.
Transparent JWTs are what you send to an OAuth resource server, and it does make sense for resource servers to generate their own OBO tokens.
For further reading, this post has a detailed walkthrough generating tokens with Azure AD.

Microsoft graph return "access token is empty"

I post this request:
POST https://login.microsoftonline.com:443/{my-tennant-here}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id={client id here}
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_secret={client secret here}
&grant_type=client_credentials
This returns:
{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 0,
"access_token": "eyJ0eX......
}
I have decoded the token using jwt.io and it definitely is not empty. It contains 14 claims. aud, iss, tid etc...
I then use the access token in this request
GET https://graph.microsoft.com/v1.0/users
Athorization: Bearer eyJ0eX...
I then get a 401 Unauthorized with this body:
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token is empty.",
"innerError": {
"request-id": "",
"date": "2018-08-14T15:41:44"
}
}
}
Expected result was a 200 Ok with a body containing a list of users
Does this simply mean that my app is Unauthorized, and the error message is just misleading (access token is empty)?
Or have I done something wrong?
Update:
I have noted that the although the token does contain claims it does not have a scope claim which seems a bit weird to me. I would assume that it had the User.Read.All scope. The application (client id/client secret) should have this permission.
The claims in the token I receive have the following claims:
aud: "https://graph.microsoft.com",
iss: "https://sts.windows.net/my tennant id",
iat: timestamp
nbf: timestamp
exp: timestamp
aio: looks like some kind of signature
app_displayname: "the expected app name"
appid: "the expected appid"
appidacr: "1"
idp: "https://sts.windows.net/...."
oid: "GUID"
sub: "GUID"
tid: "my tennant id"
uti: "value"
ver: 1.0
The Authorization header was misspelled.
So "Access token is empty" probably actually meant not present or even "No authorization header in request".
For me, my issue was that I had put a linebreak between the request url and the Authorization header, making it the body instead.
Wrong:
GET https://graph.microsoft.com/v1.0/users
Authorization: Bearer {{token}}
Correct:
GET https://graph.microsoft.com/v1.0/users
Authorization: Bearer {{token}}
A stupid mistake, but easy to overlook - if you get to this post you have probably done a silly mistake like OP (typo) or this. Look through your request syntax again!
One URL works while the other doesn't.
This works:
endpoint = "https://graph.microsoft.com/v1.0/reports/getOffice365ActiveUserDetail%28period%3D%27D7%27%29"
headers = {"Authorization": 'Bearer ' + access_token_gmc}
response = requests.get(endpoint, headers=headers)
But this one doesn't:
endpoint = "https://graph.microsoft.com/v1.0//users/myuserid/calendars"
headers = {"Authorization": 'Bearer ' + access_token_gmc}
response = requests.get(endpoint, headers=headers)
Please make sure the spellings are correct.
I was getting the same error in my angular application where I use MSAL.
And it was all because of the wrong API scope provided in the MSALAngularConfigFactory. I was having the environment as preceding.
export const environment = {
production: false,
clientId: 'clientid',
redirectUrl: 'http://localhost:4200',
graphApiUrl: 'https://graph.microsoft.com/v1.0',
graphGetUserImage: '/me/photo/$value',
protectedResourceMap: [
['http://localhost:65498/api', ['api.access']],
['https://graph.microsoft.com/beta', ['user.read']]
] as [string, string[]][],
authority: 'https://login.microsoftonline.com/organizations'
};
As you can see that I have given https://graph.microsoft.com/beta in the protectedResourceMap, which is wrong. Instead, we should give https://graph.microsoft.com/v1.0/. So here is the correct environment.
export const environment = {
production: false,
clientId: 'clientid',
redirectUrl: 'http://localhost:4200',
graphApiUrl: 'https://graph.microsoft.com/v1.0',
graphGetUserImage: '/me/photo/$value',
protectedResourceMap: [
['http://localhost:65498/api', ['api.access']],
['https://graph.microsoft.com/v1.0/', ['user.read']]
] as [string, string[]][],
authority: 'https://login.microsoftonline.com/organizations'
};
And I use it in the app.module.ts as below.
function MSALAngularConfigFactory(): MsalAngularConfiguration {
return {
popUp: false,
protectedResourceMap: environment.protectedResourceMap,
};
}
For User.Read.All scope you can't have a user consent. It must be admin consent. It looks like you may have missed consenting your app using an admin account.
To do this hit:
GET https://login.microsoftonline.com/{tenant}/adminconsent
?client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&state=12345
&redirect_uri=http://localhost/myapp/permissions
Then get an access token and you should get the scopes the admin has consented users for.

Resources