Validating "scp" in Api Management's validate-jwt - azure

I am attempting to validate that a passed in JWT token has the scopes "labresults.read" and "user_impersonation". I did the following policy snippet
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Invalid JWT Token" require-signed-tokens="true">
<openid-config url="(snip)" />
<audiences>
<audience>(snip)</audience>
</audiences>
<required-claims>
<claim name="scp" match="all">
<value>labresults.read</value>
<value>user_impersonation</value>
</claim>
</required-claims>
</validate-jwt>
I pass in a token that looks like
{
"iss": "(snip)",
"exp": 1522334650,
"nbf": 1522331050,
"aud": "(snip)",
"sub": "(snip)",
"email": "(snip)",
"name": "Scott Chamberlain",
"scp": "labresults.read user_impersonation",
"azp": "(snip)",
"ver": "1.0",
"iat": 1522331050
}
When I do a "Try It" from the developer portal looking at the tracing the on-error reports
validate-jwt (648 ms){
"message": "JWT Validation Failed: Claim value mismatch: scp=labresults.read.."
}
Am I forced to use a single claim of
<claim name="scp">
<value>labresults.read user_impersonation</value>
</claim>
I really would not like to as I do not want to force on the consumers of this api that those two scopes will be the only things passed in and in that specific order.
What do I need to do to validate the scope the propper way?

Got an answer from someone at Microsoft on the MSDN Forums.
I would suggest you to specify “separator” attribute in policy
statement to validate the JWT token and see if it helps. For more
information, you might refer this document:
https://learn.microsoft.com/en-us/azure/api-management/api-management-access-restriction-policies#ValidateJWT.
Swikruti BoseMicrosoft-MSFT (MSFT CSG)
I totally overlooked the seperator attribute when i looked at the documentation the first time.

Related

oauth token does not contain information about roles or scope

I have created app registration in AD, created client secret and exposed the API. I have created App Role and added permissions (additional permissions to the default graph user read). I also have added a scope.
When I do the request
curl --location --request POST 'https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<application id>' \
--data-urlencode 'client_secret=<secret>' \
--data-urlencode 'scope=api://<application id>/.default' \
--data-urlencode 'grant_type=client_credentials'
I get the response containing access_token. However when I decode the token it does not contain any roles, scopes or anything
{
"aud": "api://<application id>",
"iss": "https://sts.windows.net/<tenant id>/",
"iat": 1672241639,
"nbf": 1672241639,
"exp": 1672245539,
"aio": "<>",
"appid": "<application id>",
"appidacr": "1",
"idp": "https://sts.windows.net/<tenant id>/",
"oid": "<>",
"rh": "<>.",
"sub": "<>",
"tid": "<tenant id>",
"uti": "<>",
"ver": "1.0"
}
What is missing?
/oauth2/token returns similar response
{
"aud": "<resource id>",
"iss": "https://sts.windows.net/<tenant id>/",
"iat": 1672241960,
"nbf": 1672241960,
"exp": 1672245860,
"aio": "<>",
"appid": "<application id>",
"appidacr": "1",
"idp": "https://sts.windows.net/<tenant id>/",
"oid": "<>",
"rh": "<>",
"sub": "<>",
"tenant_region_scope": "EU",
"tid": "<tenant id>",
"uti": "<>",
"ver": "1.0",
"xms_tdbr": "EU"
}
I tried to reproduce the same in my environment via Postman and got below results:
I registered one Azure AD application and created App role named READER.ALL like below:
Now I exposed one API named ReaderScope.ALL same as you like below:
Now I generated the token using Client credentials flow via Postman with below parameters:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
client_id: <appID>
client_secret: <secret>
scope: api://<appID>/.default
grant_type:client_credentials
Response:
When I decoded the token, I did not find roles claim same as you like below:
To get roles claim in the token, you need to add API permission like below:
Now, select Application permissions and add the App role like below:
Make sure to grant admin consent to the added permission like below:
When I generated the token again now and decoded it, I got roles claim successfully like below:
You will get only Application permissions in roles claim while using client credentials flow.
To get Delegated permissions in scp claim, you need to use interactive flows like authorization code flow etc.

JWT Validation Failing in Azure APIM

I'm trying to configure a React SPA to connect to an Azure API. JWT validation is failing no matter what I try. I've been at this for 3 days and at this point am certain that I'm overlooking something easy/obvious. Here are the details:
We have two applications:
front-end (React)
API (Azure API Management)
Azure Application Gateway sits in front of APIM, but all it does is route traffic (APIM
is not available from public internet)
I’ve confirmed that the bearer token is being submitted:
Correct permission scopes are being requested:
API Policy – All Operations:
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid">
<openid-config url="https://login.microsoftonline.com/tenantName.onmicrosoft.com/.well-known/openid-configuration" />
<required-claims>
<claim name="aud">
<value>guid-of-registered-API-app</value>
</claim>
</required-claims>
</validate-jwt>
I’ve tried various other configurations:
Claim name: iss Claim value: https://sts.windows.net/*tenant-guid*/
Claim name: appId Claim value: api://guid-of-registered-API-app
Claim name: aud Claim value: aud-value-of-token-from-jwt.io
2-3 more I don’t recall details on
[see below for decoded token info]
Permission Scopes
Exposed by registered app: api://*guid-of-registered-API-app*/user_impersonation
API Permissions for Registered App
Registered App Authentication
Decoded JWT Token
(from JWT.io)
{
"aud": "00000003-0000-0000-c000-000000000000",
"iss": "https://sts.windows.net/tenant-guid/",
"iat": 1626371934,
"nbf": 1626371934,
"exp": 1626375834,
"acct": 0,
"acr": "1",
"acrs": [
"urn:user:registersecurityinfo",
"urn:microsoft:req1",
"urn:microsoft:req2",
"urn:microsoft:req3",
"c1",
"c2",
"c3",
"c4",
"c5",
"c6",
"c7",
"c8",
"c9",
"c10",
"c11",
"c12",
"c13",
"c14",
"c15",
"c16",
"c17",
"c18",
"c19",
"c20",
"c21",
"c22",
"c23",
"c24",
"c25"
],
"aio": "ASQA2/8TAAAA8QnjnouL0YsWy8XPuXhel67vtgZgYV5CgGbbKZXPsy0=",
"amr": [
"pwd"
],
"app_displayname": "app-display-name",
"appid": " guid-of-registered-API-app",
"appidacr": "0",
"family_name": "SMith",
"given_name": "Joe",
"idtyp": "user",
"ipaddr": "xxx.x.x.x",
"name": "Joe Smith",
"oid": "xxxxxxxx-7bf2-4b4a-b029-2bb10eb41c5e",
"platf": "3",
"puid": "10032000792D2327",
"rh": "0.AXMA2BYRYPLYhkC01dWK00t0K2OSYmKM4etBkR1sRefhJxVzACg.",
"scp": "Directory.Read.All Group.Read.All GroupMember.Read.All User.Read User.Read.All profile openid email",
"sub": "YryKYUeWm04s9todeZ32A1-OWhXdLLyWxOgQI-wa6KQ",
"tenant_region_scope": "EU",
"tid": " tenant-guid",
"unique_name": "joe.smith#mytenant.onmicrosoft.com",
"upn": " joe.smith#mytenant.onmicrosoft.com",
"uti": "6mbN5WfQLUCL-LVno3cgAQ",
"ver": "1.0",
"wids": [
"62e90394-69f5-4237-9190-012177145e10",
"b79fbf4d-3ef9-4689-8143-76b194e85509"
],
"xms_st": {
"sub": "KdgX5DPaGQfoho9O2ohOa_L-ULeYEOnLlTyTX8rf0jE"
},
"xms_tcdt": 1471244337
}
You are requesting scopes for multiple resources in your token request, which is not allowed. According to the msal example docs:
Access Token requests in MSAL.js are meant to be per-resource-per-scope(s). This means that an Access Token requested for resource A with scope scp1:
- cannot be used for accessing resource A with scope scp2, and,
- cannot be used for accessing resource B of any scope.
Instead, in this example, request only the exposed API scope, api://*guid-of-registered-API-app*/user_impersonation (i.e., remove User.Read and other MS Graph scopes). If you also need to use MS Graph, request a token only for that resource.

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 API in German environment - InvalidAuthenticationToken (Message: Access token validation failure)

we use Microsoft.Graph dll (v. 1.20.0). When using in Global environment everything works fine. Now I have tried to make same calls in German environment but every call ends with exception: [Code: InvalidAuthenticationToken, Message: Access token validation failure]. I have tried these endpoints: Me, Domains, Subscriptions.
On my side only device auth url and graphApi url has changed . I authorize via device authentication on https://login.microsoftonline.de/. Then bearer token is get from https://graph.microsoft.de. The user I authorized with is administrator for subscriptionId (see code below)
Code is something like this:
var http = new Microsoft.Graph.HttpProvider();
var credentials = new Microsoft.Azure.TokenCloudCredentials(subscriptionId, accessToken);
Microsoft.Graph.IAuthenticationProvider provider = new AuthenticationProvider(credentials);
var graphClient = new GraphServiceClient(provider, http);
var me = await graphClient.Me.Request().GetAsync(); // ERROR
It looks to me that error is not related to invalid token. I have read that some API endpoints are not yet supported in German cloud. Maybe this is the cause...
Any ideas what to try?
Thank you.
Decoded (and blinded) token:
{
"typ": "JWT",
"nonce": "4GrWG3nKdEoekxxxxxxxxxxxpyXQ5yky7_HTAg",
"alg": "RS256",
"x5t": "J1XZUkznV-JEe-xxxxxxxxxxxxx",
"kid": "J1XZUkznV-JEe-xxxxxxxxxxxxx"
}.{
"aud": "https://graph.microsoft.de",
"iss": "https://sts.microsoftonline.de/146e589e-f5c2-4dd2-95dd-a94c3ffa00f8/",
"iat": 1575010843,
"nbf": 1575010843,
"exp": 1575014743,
"acr": "1",
"aio": "ASQA2/8FAAAApfed4j4xxxxxxxxxxxxxxxxxxMd4v7aEsASSm8LxIElw=",
"amr": [
"pwd"
],
"app_displayname": "Microsoft Azure CLI",
"appid": "04b07795-xxxx-xxxx-xxxx-02f9e1bf7b46",
"appidacr": "0",
"ipaddr": "XX.XX.XX.XX",
"name": "Chuck Norris",
"oid": "bc7a9fcf-xxxx-xxxx-xxxx-557ce18e4fd6",
"platf": "14",
"puid": "10032XXXXXXXXXD1DB9",
"scp": "AuditLog.Read.All Directory.AccessAsUser.All Group.ReadWrite.All User.ReadWrite.All",
"signin_state": [
"kmsi"
],
"sub": "hq-v5yL38qnRxxxxxxxxxxxxufB-I-17BNqy-Rp0",
"tid": "146e589e-xxxx-xxxx-xxxx-a94c3ffa00f8",
"unique_name": "chuck#norris.onmicrosoft.de",
"upn": "chuck#norris.onmicrosoft.de",
"uti": "CgOXXXXXX2k-XXXXXXXXXXX",
"ver": "1.0",
"xms_tcdt": 1495192142
}.[Signature]
answer to this was simple, my fault. Token is OK, from Germany cloud as it should be. Problem was in that GraphServiceClient was using default baseUrl (https://graph.microsoft.COM/v1.0) and it was not talking with German cloud https://graph.microsoft.DE/v1.0.

Authorize a client to browse Office365 Graph API

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.

Resources