Our application is managing Office 365 calendars without requiring explicit consent from users using the Office 365 Exchange Online API. This works as expected for existing installations at customers, but for new customer all requests to the Exchange Online API return a 401 Unauthorized response. We've narrowed this down to a roles claim missing in the JWT token.
Our question is if this roles claim missing in the JWT is a bug, or if this is by design. Perhaps someone from the Azure AD team can share their thoughts.
How to reproduce
In Azure AD, we've created an app registration. A public key has been uploaded in order to authenticate via ADAL4j. Next to this, some application permissions have been granted to Exchange Online:
We can successfully request a access token via ADAL4j, using https://outlook.office365.com/ as the Resource Id. The JWT looks something like this (removed some irrelevant information):
{
typ: "JWT",
alg: "RS256",
},
{
aud: "https://outlook.office365.com/",
iss: "https://sts.windows.net/yyy/",
app_displayname: "Test",
appid: "app-id",
ver: "1.0"
}
As can be seen, the property roles is missing in the JWT token.
When calling the Exchange Online API (e.g. https://outlook.office365.com/api/v2.0/users/user#tenant.onmicrosoft.com/calendars), sending the JWT as a Bearer token, a 401 Unauthorized is returned. The x-ms-diagnostics header mentions:
2000008;reason="The token contains no permissions, or permissions can not be understood.";error_category="invalid_grant"
Expected behaviour
When using an old application registration (created using the Azure Classic Portal, if I recall correctly), the JWT does contain a Roles property with the role we've requested:
{
typ: "JWT",
alg: "RS256",
},
{
aud: "https://outlook.office365.com/",
iss: "https://sts.windows.net/yyy/",
app_displayname: "Test",
appid: "app-id",
roles: [
"Calendars.ReadWrite.All"
],
ver: "1.0"
}
Using this JWT as a Bearer token when calling the Exchange Online API works as expected.
Workaround
We've worked around the issue by using the Grant Permissions button for the new app registration:
Now, the Calendars.ReadWrite.All role is present in the JWT, so everything is working as expected.
Question
In the past we've never had to execute the Grant Permissions action. Also, this page mentions (emphasis added):
As an administrator, you can also consent to an application's
delegated permissions on behalf of all the users in your tenant.
Administrative consent prevents the consent dialog from appearing for
every user in the tenant, and can be done in the Azure portal by users
with the administrator role. From the Settings page for your
application, click Required Permissions and click on the Grant
Permissions button
However, the "Read and write calendars in all mailboxes" permission is an application permission, and not a delegated permission, as mentioned at this page.
Is the workaround the correct solution to our missing Roles claim issue, or is something else wrong on the Azure AD side?
The workaround is the correct solution. When your application needs application permissions, an admin must consent by either clicking in the "grant permissions" button (as you did) or by passing admin_consent to the login URL. This applies to the AAD v1 application model. For the AAD v2 application model, there is a different way to get admin consent. More information here.
In the past (Azure Classic Portal), when you added application permissions to an application, the consent was granted automatically. This is not the case in the new Azure Portal.
Related
I want create APIM subscriptions through rest api, And was able to do it successfully by following this Microsoft doc, https://learn.microsoft.com/en-us/rest/api/apimanagement/current-ga/subscription.
And for Authentication I am generating a bearer token using ROPC grant type(My UserName & Password). Everything works fine with this flow.
But i dont want to configure my username & password in a application to get a bearer token, instead i followed Client-Credentials grant type(get token by client id & secret), i am able to generate token, but when i use that token to create subscription in APIM, i am getting a exception
The client '0--e' with object id '0--e' does not have authorization to perform action 'Microsoft.ApiManagement/service/subscriptions/write'
Is it possible to add a AAD application inside APIM AccessControl(IAM) to grant permission.
Or is this any other way to do this? or ROPC is the only way?
Can someone please help.
Yes, you can grant permission to AAD application (service principal) in APIM Access Control (IAM) by assigning it API Management Service Contributor role.
I tried to reproduce the same in my environment and got the below results:
I have generated one access token using Client-Credentials grant type like below:
When I used the above token to create APIM subscription with below query, I got the same error:
PUT https://management.azure.com/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/subscriptions/testsub?api-version=2021-12-01
{
"properties": {
"ownerId": "/subscriptions/subid/resourceGroups/rgname/providers/Microsoft.ApiManagement/service/servicename/users/xxxxxxxxxxx",
"scope": "/subscriptions/subid/resourceGroups/rgname/providers/Microsoft.ApiManagement/service/servicename/products/xxxxxxxxxxx",
"displayName": "testsub"
}
}
Response:
To resolve the error, you need to grant API Management Service Contributor role for that application like below:
Go to Azure Portal -> APIM Services -> Your APIM -> Access control (IAM) -> Add role assignment
After granting the above role, I generated the access token again and ran the same query as below:
PUT https://management.azure.com/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/subscriptions/testsub?api-version=2021-12-01
Response:
When I checked the Portal, APIM subscription got created successfully like below:
Reference:
How to use Role-Based Access Control in Azure API Management | Microsoft Docs
We are implementing SSO in outlook adding. Let's presume, there is two tenants for simplicity.
Home Tenant
Customer Tenant
We have added Customer Tenant's user as guest in Home Tenant. Hence, we can manage guest user with certain accessibility.
We have created multi-tenant app registration in Home Tenant to get user consent and authentication from Customer Tenant. And we have put application (client) id and application id uri in Outlook-addin xml as show in below.
</OfficeApp>
...
...
<WebApplicationInfo>
<Id>928cd908-multi-tenant-application-id</Id>
<Resource>api://company-domain.com/928cd908-multi-tenant-application-id</Resource>
<Scopes>
<Scope>Files.Read.All</Scope>
<Scope>offline_access</Scope>
<Scope>openid</Scope>
<Scope>profile</Scope>
</Scopes>
</WebApplicationInfo>
</VersionOverrides>
</VersionOverrides>
</OfficeApp>
Let's assume consent has been done on before,
In order to do SSO, I'm following steps,
Executing OfficeJs.auth.getAccessToken and get access token which is issued by Customer Tenant.
Passing Customer Tenant token to application server through api call.
Exchanging Customer into Home Tenant AD token by using on-behalf-of (OBO) request.
POST /2e627697-home-tenant-ad-id/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&client_id=928cd908-multi-tenant-application-id
&client_secret=.m_7Qxxx
&assertion=eyJ0eXAiOiJKV1Q-customer-tenant-ad-token
&requested_token_use=on_behalf_of
&scope=api://company-domain.com/928cd908-multi-tenant-application-id/access_as_user openid
Received accesstoken from Home Tenant AD, since Customer Tenant's user exist in Home Tenant AD as guest.
Allowing user to access server resource.
Send response to Outlook-addin from server.
So far all good, The problem is,
If I enable Per-User MFA for the guest user, then I started to get below error when I do on-behalf-of request from server.
{
"error": "invalid_grant",
"error_description": "AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '928cd908-multi-tenant-application-id'.\r\nTrace ID: 706875e8-bfe7-44b8-a9f9-402b1f4a2201\r\nCorrelation ID: 8a4bde4c-9222-4aa0-a4d6-cd0748ff3816\r\nTimestamp: 2022-06-29 18:00:31Z",
"error_codes": [
50076
],
"timestamp": "2022-06-29 18:00:31Z",
"trace_id": "706875e8-bfe7-44b8-a9f9-402b1f4a2201",
"correlation_id": "8a4bde4c-9222-4aa0-a4d6-cd0748ff3816",
"error_uri": "https://login.microsoftonline.com/error?code=50076",
"suberror": "basic_action"
}
There is two options to make user interations,
OPTION 1
Get Customer Tenant token by calling Officejs.auth.getAccessToken.
Send Customer Tenant token to server.
Create on-behalf-of request from server.
Get 50076 error and then send error response to addin.
Open authorize popup for MFA from addin by using following url. This will do MFA against Home Tenat AD.
const authorizeUrl =
`https://login.microsoftonline.com/2e627697-home-tenant-ad-id/oauth2/v2.0/authorize?client_id=928cd908-multi-tenant-application-id
&response_type=id_token+token
&redirect_uri=https://company-domain.com/928cd908-multi-tenant-application-id
&scope=openid api://company-domain.com/928cd908-multi-tenant-application-id/access_as_user
&response_mode=fragment
&state=12345
&nonce=678910`
window.open(authorizeUrl)
Close authorize popup on after MFA completed.
Call Officejs.auth.getAccessToken to get Customer Tenant token (2e627697-home-tenant-ad-id MFA Passed).
Pass Customer Tenant token with MFA to server.
Create on-behalf-of request from server with Customer Tenant token (2e627697-home-tenant-ad-id MFA Passed).
Get Home Tenant AD token successfully.
OPTION 2
Get Customer Tenant token by calling Officejs.auth.getAccessToken.
Send Customer Tenant token to server.
Create on-behalf-of request from server.
Get 50076 error and then send error response to addin.
Call Officejs.auth.getAccessToken to get Customer Tenant token (MFA Passed). Here I don't have clear understand to force OfficeJs to get Customer Tenant token on after passing MFA for 2e627697-home-tenant-ad-id.
Questions
Option 1 is getting MFA through window.open from Outlook-addin code, instead of getting MFA through OfficeJs. is this correct way?
If Option 2 is correct way, how do I force OfficeJs to perform MFA for 2e627697-home-tenant-ad-id?
To ensure MFA is satisfied use the message returned by MS Graph as the Office.AuthOptions.authChallenge value. E.g.
let exchangeResponse = await getGraphToken(bootstrapToken);
await Officejs.auth.getAccessToken({
authChallenge: exchangeResponse.claims
});
We want to achieve an authorization at our APIs.
Ex. We have API-A and API-B and both are exposed to our different consumers.
We have setup of scope based authorization in place with IdentityServer4 where we decorate endpoints with different policies. With IdentityServer4 we are able to achieve this as IdentityServer4 token has scopes claims present in all the grant types but with Azure AD, we found we can't have scope claim in token generated with Client Credential flow.
In our case, Web API B is also exposed to consumers and again they have scope based authorization. To call, Web API B from Web API A we use client credential flow and it will not have scopes claim in token so we are not able to authorize our call to Web API B.
How to achieve scope based authorization with Azure AD in microservices architecture where we call other context APIs from one context.
When you are using client credential flow and using
application permission , you get roles and not scope i.e; scp claim in the token.
Application permissions are sort of roles given to the application
itself and the scope in client credentials should be used as
api://<APP_ID>/.default . They only apply when doing client
credentials authentication, where no user is involved.
See quickstart to configure app access web-apis
Scopes are usually delegated permissions that only apply when a
user is involved in the login process. They allow you to act on
behalf of a user i.e; In the user context only, we will get
scp claims in case of client credential flow.
See azure-ad-scope-based-authorization
So , If you want delegated permissions then you will have to use implicit grant flow instead of client credentials.
As scopes in expose an api page are for Authorization Code Grant flows and where the user is involved, in this case (client credential) its not possible, we have to add our own scopes that is availible for applications to use which are indirectly called roles that we need to add in the manifest itself under approles in the app registration or through the app roles blade.
ex:
{
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "Read all todo items",
"id": "f8dxxxxxxxxxxxxf98",
"isEnabled": true,
"description": "Allow the application to read all todo items as itself.",
"value": "Todo.Read.All"
}
]
}
After that , those has to be granted admin consent.
So now when requesting a token with a default scope of api://<app id>/.default the "scopes" are returned in the roles claim.
So we can use role claim for authorization purpose.
Also as a work around
Try to make sure to add additional scope like profile, offline_access open_id.
And give response_type=token instead of id_token
Example request:
......&redirect_uri=https://jwt.io&scope=openid profile offline_access&response_type=token&prompt=login
References:
Scope-based authorization in your API with Azure AD – the IT generalist (wordpress.com)
Scope is not being added to Access Token returned from Azure Ad - Stack Overflow
EDIT:
To call a web api from other , there need to be scopes defined in
one api i.e (api2 that you want to call) and those scopes need to be
selected in calling api(api1) . Please go through the process
here
When login in first Api include scope in the request and also try
response type as Token and see if scp available or then with idtoken
https://tenant.b2clogin.com/tenant.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_TenantSignUpIn&client_id=<appid>&nonce=defaultNonce&redirect_uri=https://jwt.ms&scope=openid offline profile https://tenant.onmicrosoft.com/b2capi/write https://tenant.onmicrosoft.com/b2capi/read https://tenant.onmicrosoft.com/b2capi/user_impersonation&response_type=id_token&prompt=login.
Please note that scopes are present as roles depending on the flow
type.
I'm trying to get a subscription created with the callRecord resource (https://learn.microsoft.com/en-us/graph/api/subscription-post-subscriptions?view=graph-rest-beta&tabs=http)
In the app registration section of the Azure portal, I've created a multi-tenant app with a client secret. That app has permissions for application-level "CallRecords.Read.All" as well as the default delegated "User.Read". The statuses also have a green checkbox for being granted against my organization by an admin.
I am able to get an access token with the following HTTP POST request to https://login.microsoftonline.com/common/oauth2/v2.0/token:
grant_type:authorization_code
scope:https://graph.microsoft.com/.default
client_secret:<client_secret>
client_id:<client_id>
code:<code>
redirect_uri:http://localhost:3000
However, that token is not able to generate a subscription to my callRecord resource. I get a response with this message: "Operation: Create; Exception: [Status Code: Forbidden; Reason: The request is not authorized for this user or application.]"
The message suggests that the app has not been granted admin-level authorization, but in fact it has. This used to work for me. I'm wondering if there has been a regression on the MS Graph side.
Further, when I examine the JWT, I see that the scope is "User.Read profile openid email". There is no mention of the application-level permission (specifically, CallRecords.Read.All)
Thanks.
Because when you use the auth code flow, just the Delegated permission will take effect. So even if you grant the Application permission, the token you got will not include the permission.
From the doc, to call this API Get callRecord, just the Application permission is supported.
To get the token which include the permission, your option is to use the client credential flow.
Note: You need to use <tenant-id> instead of common in this flow.
POST https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
client_id=xxxxxxx
&scope=https://graph.microsoft.com/.default
&client_secret=xxxxxxx
&grant_type=client_credentials
Decode the token in https://jwt.io, the roles includes the CallRecords.Read.All permission:
I want my server application to interact with it's own Excel files using Microsoft Graph. That is, the files belong to the application, not a particular user of the application.
I have registered an application with Azure ID and granted "Have full access to all files user can access" permission for Microsoft Graph.
I am trying to use OAuth Resource Owner Password Credentials Grant.
I can get an authorization token like this:
POST https://login.microsoftonline.com/common/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=password
&resource=https://graph.microsoft.com
&client_id=<ID of application registered with Azure AD>
&username=<Microsoft username>
&password=<password>&scope=Files.ReadWrite.All
But the response only indicates scope User.Read:
{
"token_type": "Bearer",
"scope": "User.Read",
"expires_in": "3600",
"ext_expires_in": "0",
"expires_on": "1494467388",
"not_before": "1494463488",
"resource": "https://graph.microsoft.com",
"access_token": "eyJ0e...",
"refresh_token": "AQAB..."
}
And when I try to list files in the account's One Drive, I do not get an error, but the response contains no items:
Request:
GET https://graph.microsoft.com/v1.0/me/drive/root/children
Authorization: bearer eyJ0e...
Response:
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('<account ID>')/drive/root/children",
"value": []
}
When I make the same request in Graph Explorer when logged in with same account the response includes all the items in that account's one drive root.
I understand that Microsoft Graph does not currently support application-only file access, when authorized via OAuth Client Credentials Grant (as per instructions for calling Microsoft Graph in a service), but since I am getting authorization for a particular user account (not just application) I would expect to get access to that users files.
Am I doing something wrong, or is file access not supported using Resource Owner Password Credentials Grant either?
If the latter, how can I achieve allowing my application to use user credentials to manipulate Excel files via Microsoft Graph without user interaction?
UPDATE:
I have had administrator permissions assigned to the account I am using, and re-set the application permissions for Microsoft Graph in the Azure Portal, but it still is not working for me.
Here are details of the account I am using:
Please try to click Grant Permissions(better using admin account) in "Required permissions" blade after granted "Have full access to all files user can access" permission for Microsoft Graph:
After that acquire token using Resource Owner Password flow , you will find Files.ReadWrite.All in scp claims . Then you could call microsoft graph api to list files .
Update
Here is the steps how i make the resource owner flow work :
register a native app , Add the "Have full access to all files user can access" delegate permission for Microsoft Graph(don't click grant permissions button as above picture shown) . using Resource Owner Password Credentials Grant and get the access token ,only find User.Read in scp claim :
POST https://login.microsoftonline.com/common/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=password&client_id=XXXXXXXXXX&resource=https://graph.microsoft.com/&username=XXXXXX&password=XXXXXXX
click grant permissions button as above picture shown , using Resource Owner Password Credentials Grant and get the access token ,you could find Files.ReadWrite.All User.Read in scp claim :
The issue with this is due to permissions on the Graph API. The reason is since you are logged in under a specific user for the Microsoft Graph Explorer - you are able to see everything ... due to the fact you have authenticated as a single person ... the reason you see nothing is because the app-only permissions does not work.