Is it possible to access microsoft graph using custom audience? - azure

I have an API that uses Azure AD to provide access to resources. It has one scope api://{client_id}/Api.Read and following API permissions:
User.Read.All,
User.Read.
When I receive an access token from the AD it has only one aud - {client_id} and when I try to access Microsoft graph (e.g. https://graph.microsoft.com/v1.0/me) I get "Access token validation failure. Invalid audience." response as expected.
My question is - Is it possible to access both graph API and my API using one token and how do it?

I am exchanging current token to a "proper" token using on-behalf-of call: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow
Basically, you just make the following call (copied from the link above):
POST /oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com/<YOUR_TENANT>
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&requested_token_use=on_behalf_of
&client_id=<YOUR_CLIENT_ID>
&client_secret=<YOUR_CLIENT_SECRET>
&assertion=<YOUR_CURRENT_TOKEN>
&scope=<YOUR_NEW_SCOPES>
The response contains the new token to access the resources that are requested in the scopes. My scenario is not exactly yours, but I think this should work for your case as well. You need to make this call server-side.
This assumes that the user has already consented that your app is okay to use <YOUR_NEW_SCOPES>, or otherwise this call will result in "interaction_required" or "invalid_grant" error (something like this, don't remember the exact error name) that basically means you need to raise UI prompt for the user to agree to the new scopes (how to do that is another question)

Related

SharePoint Online multi-tenant REST calls return 404 on resources that definitely exist

I am attempting to access the SharePoint Online REST API (this is hand coded REST calls, no library being used).
Access tokens are acquired using authorization grant flow as follows:
I send the browser https://login.microsoftonline.com/common/oauth2/authorize?...
This redirects to a handler endpoint that we extract the access code from
I obtain the tenant ID by:
GET https://{tenantname}.sharepoint.com/_vti_bin/client.svc
Then extracting the tenant ID from the WWW-Authenticate header
I then POST https://login.microsoftonline.com/{tenantid}/oauth2/authorize to obtain the access token
When I use that access token, I am able to do queries using:
GET https://{tenantname}.sharepoint.com/_api/search/query?querytext=....
This works and returns documents.
But when I attempt to retrieve information about one of those documents:
GET https://{tenantname}.sharepoint.com/_api/web/getfilebyserverrelativeurl('/TestFiles/test.pdf')
I get a 404 response with the following body:
{"odata.error":{"code":"-2130575338, Microsoft.SharePoint.SPException","message":{"lang":"en-US","value":"The file /TestFiles/test.pdf does not exist."}}}
If I navigate to the URL in a browser (https://{tenantname}.sharepoint.com/TestFiles/test.pdf), it accesses the file without issue.
This makes me think that I'm running into some sort of permission issue.
I have tried setting the following scopes in the authorize redirect:
Attempt 1: scope = Web.Write AllSites.Write Site.Write
Attempt 2: scope = https://{tenantname}.sharepoint.com/.default
Attempt 3: scope = https://{tenantname}.sharepoint.com/Web.Write https://{tenantname}.sharepoint.com/AllSites.Write https://{tenantname}.sharepoint.com/Site.Write
No matter what I set as the scope parameter of the authorize URL, the JWT details of the access token show (I can post the entire decoded JWT if anyone needs it):
"scp": "User.Read"
Nothing I do has any impact on the scp in the token - I have no idea if that's the issue or not. If it is, I would appreciate hearing how to properly request scope.
The application registration in Azure Active Directory has desired permissions (plus more):
What am I doing wrong?
UPDATE: Switching to OAuth endpoint v2.0:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
With query parameters:
response_type = code
client_id = my app id
redirect_uri = my redirect uri
scope = <varying - I'll explain what happens under different scenarios below>
Here's what I've tried for scopes:
AllSites.Write Site.Write - the redirect has invalid_client with error_description = AADSTS650053: The application '' asked for scope 'AllSites.Write' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'. Contact the app vendor.
https://{tenantname}.sharepoint.com/AllSites.Write https://.sharepoint.com/Site.Write - the redirect has invalid_client with error description = AADSTS650053: The application '' asked for scope 'Site.Write' that doesn't exist on the resource '00000003-0000-0ff1-ce00-000000000000'. Contact the app vendor.
https://{tenantname}.sharepoint.com/.default - this goes through
But the resulting JWT has only scp=User.Read
The following works: GET https://{tenantname}.sharepoint.com/_api/search/query?querytext=
But the following returns a 404: GET https://{tenantname}.sharepoint.com/_api/web/getfilebyserverrelativeurl('/TestFiles/test.pdf')
I don't understand how Scope=.Default isn't including the allowed permissions from the application registration. And I definitely don't understand why the AllSites.Write scope is failing when it's explicitly specified.
If it helps, I have also tried all of the above using a tenant specific authorize endpoint instead of 'common':
https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/authorize
UPDATE2: More scope changes:
I finally found a magical combination that works:
Use a tenant based URI for the /authorize and /token endpoint and use {tenanturl}\AllSites.Write for the scope (do NOT specify the Site.Write scope):
https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/authorize?response_type=code&client_id={clientid}&redirect_uri={redirecturi}&scope=https%3A%2F%2F{tenantname}.sharepoint.com%2FAllSites.Write
The resulting JWT has the following:
"scp": "AllSites.Write User.Read"
I am completely perplexed about why Site.Write wasn't allowed. I suppose that AllSites.Write is a superset of Site.Write, so maybe not needed?
All of my testing so far has been on my own tenant, next step is to test on a different tenant and make sure it actually works there as well.
It seems you use v1.0 endpoint https://login.microsoftonline.com/common/oauth2/authorize but not v2.0 endpoint https://login.microsoftonline.com/common/oauth2/v2.0/authorize. If we use v1.0 endpoint, we should use resource instead of scope. So that is why the scp claim in your access token always the same no matter you modify the scope.
You should use resource with https://{tenant-name}.sharepoint.com and the parameter scope is useless when you use v1.0 endpoint.
If you still want to use scope parameter, you can also change the endpoint to v2.0. Just add v2.0 into your endpoint, like: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
I finally found a magical combination that works:
use the https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/authorize and https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/token endpoints
specify {tenanturl}\AllSites.Write for the scope (do NOT specify the Site.Write scope - that was the primary problem):
https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/authorize?response_type=code&client_id={clientid}&redirect_uri={redirecturi}&scope=https%3A%2F%2F{tenantname}.sharepoint.com%2FAllSites.Write
The resulting JWT has the following: "scp": "AllSites.Write User.Read"
This works across tenants and gets us the access we need.
For thoroughness, we also specify offline_access scope so we get a refresh_token in addition to the access_token.

Authenticate for Azure REST API without login

I have a backend process that doesn't directly interact with the user. I want to access reservations associated with my Azure account but I'm having trouble with the authentication step. I was following the guide here and I managed to get the authentication request to work by calling
https://login.microsoftonline.com/{tenant-ID}/oauth2/token
as a POST with the following x-www-form-urlencoded body:
grant_type=client_credentials&
client_id={client-ID}&
client_secret={client-Secret}&
resource=http://myapp42
However, when I attempt to call:
https://management.azure.com/providers/Microsoft.Capacity/reservationOrders/{order-ID}/reservations/{reservation-ID}?api-version=2019-04-01
with the bearer token I received during the authentication step, I get the following error message:
The access token has been obtained for wrong audience or resource 'http://myapp42'. It should exactly match with one of the allowed audiences 'https://management.core.windows.net/', 'https://management.core.windows.net', 'https://management.azure.com/', 'https://management.azure.com'
However, if I modify the resource on the request to be one of these, http://management.core.windows.net/ for instance, the authentication then fails with:
AADSTS500011: The resource principal named https%3A%2F%2Fmanagement.core.windows.net%2F was not found in the tenant named {tenant-ID}. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.\r\nTrace ID: b54cedea-3804-41cf-bd27-fcf0ed1c4700\r\nCorrelation ID: 2371d375-6c89-4f05-83c9-c4629b3340a8\r\nTimestamp: 2020-02-05 01:59:57Z
How do I authenticate so that I can then get my reservations without having to login?
Update:
The service principal has both the Owner and Contributor roles assigned.
Update 2:
Thanks to #Jim Xu, I was realized that I needed to refrain from url-encoding the URL. That allowed me to get the access token with a value of https://management.azure.com/ for the resource field. However, at this point, when I attempt to call the REST API with the resulting bearer token, I get the following error:
The client '{Object-ID}' with object id '{Object-ID}' does not have authorization to perform action 'Microsoft.Capacity/reservationOrders/reservations/read' over scope '/providers/Microsoft.Capacity/reservationOrders/{order-ID}/reservations/{reservation-ID}' or the scope is invalid. If access was recently granted, please refresh your credentials
Note: The object ID returned by this error is the one associated with the service principal.
Update 3:
I checked the reservation and it seems that the principal does not have a role in that reservation's access control. However, I also cannot assign the principal a role because it does not show when I search for principals during the role-assigning process.
If you want to call Azure Rest API to get the information of reservation Orders, you need to assign Owner\Contributor for the service principal.(the action needs Microsoft.Capacity/reservationOrders/read permission).
The steps are as below
Get access token
POST : https://login.microsoftonline.com/{tenant-ID}/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id={client-ID}&
client_secret={client-Secret}&
resource=https://management.azure.com/ or https://management.core.windows.net
Call the api
GET : https://management.azure.com/providers/Microsoft.Capacity/reservationOrders/{order-ID}/reservations/{reservation-ID}?api-version=2019-04-01
Authorization: Bearer <token>
For more details, please refer to the issue and the issue
update
If you have assigned role but you still cannot get access token, please try to encode your url.

Azure OAuth2 Client Credential flow - getting token for multiple scopes throws error

When using the MSAL library to generate access token for a background console application, using client_credentials, to call two REST endpoints, the get token call is created as:
client_id=XXX&client_info=1&client_secret=XXXX&scope=api%3A%2F%2FMyService-DevBlr-ClientServices%2F.default+api%3A%2F%2FMyService-DevBlr-CoreServices%2F.default&grant_type=client_credentials
and posted to
https://login.microsoftonline.com/a1326aec-378e-4433-8e2a-67a086554fc8/oauth2/v2.0/token
The response is a 400 error:
{"error":"invalid_scope","error_description":"AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope api://MyService-DevBlr-ClientServices/.default api://MyService-DevBlr-CoreServices/.default is not valid.\r\nTrace ID: 203ad632-5e7c-4e61-9dcd-185eb9b49200\r\nCorrelation ID: 99fd979a-2c11-485b-ba14-c0cad3312e02\r\nTimestamp: 2020-01-11 08:36:29Z","error_codes":[70011],"timestamp":"2020-01-11 08:36:29Z","trace_id":"203ad632-5e7c-4e61-9dcd-185eb9b49200","correlation_id":"99fd979a-2c11-485b-ba14-c0cad3312e02"}
When individual scopes are passed in, the call works ok:
Request:
client_id=XXXX&client_info=1&client_secret=XXXX&scope=api%3A%2F%2FMyService-DevBlr-CoreServices%2F.default&grant_type=client_credentials
Response:
{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0e..."}
and
Request:
client_id=XXXX&client_info=1&client_secret=XXXX&scope=api%3A%2F%2FMyService-DevBlr-ClientServices%2F.default&grant_type=client_credentials
Response:
{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0eX..."}
The documentation and the API supports scopes array:
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-credential-flows#code-snippet
There is benefit in getting a single token to be used with multiple service calls, but I can't seem to get this working. Any suggestion is appreciated.
When you acquire an access token, you can only specify scopes for one API.
It seems to me you are trying to acquire a token with permissions to 2 APIs.
This is not possible.
An access token always only contains permissions to one API.
So you must request them separately.
This is due to a token having an audience (aud) claim that identifies the API it is meant for.
It cannot have two values.

Wrong access_token from AAD with OAuth2 flow

I am making OAuth 2.0 auth code authentication flow with multi-tenant application.
Here is my authorize url:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=my_id&prompt=consent&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauthorize&response_type=code&scope=openid+offline_access&state=17
It goes fine and I receive auth_code. Then I make request with this auth_code to token_url and receive a lot of information, like:
token_type
scope
id_token
access_token
refresh_token
expires_at
ext_expires_in
Seems fine to me, but when I make request on API with access_token like:
https://management.azure.com/subscriptions/my_sub_id/locations?api-version=2016-06-01
with headers:
Content-Type:
- application/json
Authorization:
- Bearer EwBQA8l6BAAURSN/FHlDW5xN74t6GzbtsBBeBUYAAV1IHgHb4dOWblzfd/YsSuFicAMDYbua17QivnAT9/pIaeKAg3uKsK5VGqWLzjMOUQrCpd7R1RAM6RkzI0u8e4rpO7DISG7qLso5H5+U1jb+38/j1urcwlXMMxhy83ZXmdpkLXpZV+vcOV...
It responds with 401 error
body:
encoding: UTF-8
string: '{"error":{"code":"InvalidAuthenticationToken","message":"The access token is invalid."}}'
To be honest I think something wrong with my access_token. It seems not like JWT for me. Documentation says it looks like:
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCEV1Q..."
But my access_token looks like:
"access_token": "EwBYA8l6BAAURSN/FHlDW5xN74t6GzbtsBBeBUYAAZDe7JE/MPLoAi+Fr+1Xxq5eBe5N9l8Q+c4QjkY5PGEzRnBpPe7+v6h+PLdh1cceBQx+/JsB2QCrYSCt7x/zGsQAhwoY/"
Is it fine?
Here is my permissions for application:
Permissions
The main issue you have here is that you have only asked for an access token for the scopes openid offline_access. The resulting access token will be for Microsoft Graph (https://graph.microsoft.com), not for the Azure REST API (https://management.azure.com).
To indicate you would like a token for a given API, the scope parameter in your authorization request should include the delegated permission you would like the app to have for the API. In the case of Azure REST API, there's only one delegated permission: user_impersonation. The identifier URI for the Azure REST API is https://management.azure.com, so the scope value you want to use is:
openid offline_access https://management.azure.com/user_impersonation
Two more important notes:
As you've discovered, you will not always be issued an access token as a JWT which you can decode peek at. The format of the access token is an agreement between the service which issued the token (Azure AD or Microsoft Accounts, in this case), and the service for which the token was issued (Microsoft Graph, in this example).
You should not always include prompt=consent. prompt=consent should only be used if you have already tried signing in the user without the user needs to be re-prompted for consent for a new permission.
If you simply include the required scopes in the scopes parameter, the Microsoft Identity platform will take care of figuring out if it needs to prompt for consent or not. If you always include prompt=consent, you will find that many organizations will be blocked from accessing your app, because they've disabled the ability for users to grant consent themselves (and this parameter specifically states that you require the user to be prompted again).

Authorization_IdentityNotFound Error while accessing graph API

I have searched with the error which I found, Did not find any matching questions. So posting question. Appreciate if some one provides some pointers to proceed.
My goal is to access graph API in my desktop client. I have started using fiddler to experiment.
I have followed instructions provided at https://graph.microsoft.io/en-us/docs/authorization/app_only
registered Web APP using Application Registration portal using my Microsoft work account.
Provided 'Read all users' full profiles in Delegated permissions
Requested token and Used the token in Authorization header to call the graph API, Getting following error.
https://graph.microsoft.com/v1.0/users
119
{
"error": {
"code": "Authorization_IdentityNotFound",
"message": "The identity of the calling application could not be established.",
"innerError": {
"request-id": "4c3a7bc6-e3d8-453c-adc9-5a12fec3b0ee",
"date": "2016-05-11T00:46:23"
}
}
}
In my case, I got the same error after I used Quickstart (step 1), then configured automatically .net sample (step 2), then download the code sample (step 3) as shown in the picture below.
All steps was done successfully except step 3. Microsoft code generate, generate app id, and app secret in project successfully but the tenant was set to common in appsetting.json as seen in image below.
I thought it was a valid thing, but later found out that this caused the issue.
Solution:
I copied the Directory (tenant) ID, than replace common with tenant Id, and it worked. I am not sure if this is a bug in Azure Quickstart code generation.
This sample helped me understand the flows around app-only permissions.
https://blogs.msdn.microsoft.com/tsmatsuz/2016/10/07/application-permission-with-v2-endpoint-and-microsoft-graph/
Key takeaways for me:
Ensure you set up the app and specify the Application Permissions needed
Do have an admin grant the app permission to run against the relevant directory.
Get the relevant token:
Notice the scope in the request below is https://graph.microsoft.com/.default
POST https://login.microsoftonline.com/{tenantname}.onmicrosoft.com/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=6abf3364-0a60-4603-8276-e9abb0d843d6&client_secret=JfgrNM9CcW...&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
Use the token to request the relevant graph resource, eg:
GET https://graph.microsoft.com/v1.0/users/demouser01#[tenant-name].onmicrosoft.com/drive/root/children
Accept: application/json
Authorization: Bearer eyJ0eXAiOi
For me, I had not given admin consent. This is a critical step. My mistake was in thinking that by granting the app permissions, this was giving admin consent, but its not the same thing.
From step 3 on this site: https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_service
I just pasted their call into a browser after filling in the tenant and client id, then signed in, and everything worked.
GET https://login.microsoftonline.com/{tenant}/adminconsent
?client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&state=12345
&redirect_uri=http://localhost/myapp/permissions
while generating new access token, make sure to replace tenant_id with the actual tenant id
https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
You'll find that this document is a better set of instructions for app-only apps.
There are two issues from your description that stand out.
You'll need to make the call with an X509 certificate for app-only flows.
You need to set up app scopes, rather than delegated scopes on your app - delegated scopes are for delegate flows rather than app-only flows.

Resources