I am trying to use the create subscription API to open a subscription channel on a calendar's events such that we can receive updates when interviewers respond to the interview.
I have tried many variations to get this request to work:
Permission Scopes
I have gone through the oauth process to retrieve graph access tokens for a user with the following permission Scopes:
Mail.Send
Calendars.ReadWrite.Shared
User.ReadBasic.All
Successful Calendar Event Creation
Using the access tokens with the above scopes, before attempting to create the subscription, I add an event with {eventId} to a shared calendar, let's say with the user relative id of {sharedCalendarId}.
Subscription Creation Attempts
Using the same access tokens to create the same Authorization header with permission scopes listed above, I consistently receive the same ExtensionError response. Below I will list the various permutations of request I have made to the graph API.
Urls Attempted
I have sent each permutation of the subscription POST requests to the following URLs:
https://graph.microsoft.com/v1.0/subscriptions
https://graph.microsoft.com/beta/subscriptions
Request Headers
Content-Type: application/json
Authorization: Bearer {authToken}
Request Bodies Attempted
The changeType and notificationUrl, and expirationDateTime are the same for all requests.
I have tried 3 different variations on the resource string, and have tried the request with and without the clientState.
First I tried the fully identified resource and added clientState:
{
"changeType": "created,updated",
"notificationUrl": "https://{domain-with-valid-https-certs}/calendar/microsoft/notifications",
"resource": "users/{userPrincipalName}/calendars/{sharedCalendarId}/events",
"clientState": "{thisIsLimitedTo128Characters}",
"expirationDateTime": "2018-05-23T21:02:45.487Z"
}
Next, I abandoned clientState for the sake of narrowing down the problem and I used me instead of {userPrincipalName}:
{
...
"resource": "me/calendars/{sharedCalendarId}/events"
}
Then I abandoned the shared calendar to see if that was the problem and used a resource that's an example in the docs.
{
...
"resource": "me/events"
}
Error Response Received
While I seem to receive the validation request to the notificationUrl provided (and the server responds appropriately) the request to create the subscription then responds with this kind of error:
{
code: "ExtensionError",
message: "Operation: Create; Exception: [Status Code: Forbidden; Reason: Forbidden]",
innerError: {
"request-id": "0d86a983-ea50-4226-905c-38f0e8f12308",
date: "2018-05-23T03:55:38"
}
}
What's confusing is that I am attempting to create this subscription on a calendar I have just successfully created or changed and event, so the 403 seems like it should not be related to access to the resource.
Are there specific permissions or scopes that need to be granted for us to successfully create subscriptions to a calendar's events?
Is there a different API we should be using?
In order to subscribe to a resource, you minimally need read access to the resource. So for events, you would need Calendars.Read. Since you have more than that, you should be fine. (Reference)
I believe your problem is that you are not including expirationDateTime in the payload. This is a required property (documented here, arguably not the best place for this to be listed). Set it to a value equal to or less than the maximum for calendar, 4230 minutes (Reference).
Self-answer here for anyone having a similar issue who doesn't want to dig through comments.
After some debugging help from Marc LaFleur and Jason Johnston (thanks for the responses!), the answer to my specific question is "sort of".
No, there are no specific Subscriptions.* scopes, and they are not needed to create subscription channels.
But, quixotically, the POST /subscriptions creation endpoint requires more specific permission scopes than the resource itself.
In my example, the Calendars.ReadWrite.Shared permission does indeed allow my app to read and write events to the user's calendars and shared calendars, but I was unable to create a subscription channel for the same calendar.
Requesting the less permissive and more specific scope of Calendars.ReadWrite in addition to Calendars.ReadWrite.Shared solved my problem.
Related
I'm trying to use the subscription feature of the MS graph API to listen to changes in calendars. My app has Calendars.ReadWrite permission to all users set up in Azure, and reading and writing calendar events works well for all users with the access token I've obtained for my app.
However, when calling the POST /subscriptions endpoint to subscribe to changes in one user's calendar, I get "Access to OData is disabled".
POST https://graph.microsoft.com/v1.0/subscriptions
{
"changeType": "created,updated",
"notificationUrl": "https://...my.endpoint...",
"resource": "users/someone%40somedomain.com/events",
"expirationDateTime": "2021-10-27T20:09:25.025Z",
"clientState": "foo"
}
This gives the following error:
"code": "ExtensionError",
"message": "Operation: Create; Exception: [Status Code: Forbidden; Reason: Access to OData is disabled.]"
According to the documentation, this endpoint requires only the same permissions as the subscribed entity, which in my case would be Calendars.Read.
What could be causing this? How do I debug this further to understand why I'm getting this error?
I found some pages suggesting to use PowerShell to modify an ApplicationAccessPolicy. Is this relevant, and is it only possible through PowerShell? Why would there be an obscure policy accessible only through PowerShell and not through the Azure console?
Turns out this was due to the user for whom I was trying to subscribe wasn't in the proper security groups, which were mapped to application policy.
So it had nothing to do with the permissions of my client, but rather of the target user I was trying to subscribe to.
My company is using Microsoft 365 Business Standard licenses. We are using email through these accounts. We also have a few shared mailboxes. We are trying to create an app that uses the microsoft graph application permissions (rather than the delegated permissions) so the application can access one of the shared mailboxes without needing to be authenticated under the current user.
This is the steps we have taken so far:
Within Microsoft Azure, we have an application in which we have granted application api permissions for Mail.Read, and we have accepted Admin consent.
We authorized as an app, not as a user, in the application using this endpoint https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize, pointing out the required parameters for sending a request. Then, MS API builds this link:
https://login.microsoftonline.com/{some_string}/oauth2/v2.0/authorize?state={some_string}&scope=offline_access%20https%3A%2F%2Fgraph.microsoft.com%2F.default&response_type=code&approval_prompt=auto&redirect_uri=http%3A%2F%2Flocalhost&client_id={some_string}
When we follow the link, we get to the standard authorization form on the site. After we log in, a link is created, where we take the code and create the token: http://localhost/?code={some_string}&state={some_string}&session_state={some_string}
When we try to hit this endpoint: https://graph.microsoft.com/v1.0/users/sharedmailbox#domain.com/messages, we get this response:
{
"error": {
"code": "ErrorAccessDenied",
"message": "Access is denied. Check credentials and try again.",
"innerError": {
"date": "2020-09-14T11:22:30",
"request-id": "{some_string}",
"client-request-id": "{some_string}"
}
}
}
I am thinking that hitting this endpoint https://graph.microsoft.com/v1.0/users/sharedmailbox#domain.com/messages requires us to pass the token previously generated and/or specify which application is making the query?
Any help or direction on what needs to be done to make this query work would be greatly appreciated. Thank you!
I am thinking that hitting this endpoint https://graph.microsoft.com/v1.0/users/sharedmailbox#domain.com/messages requires us to pass the token previously generated and/or specify which application is making the query?
Yes you would need to send the AccessToken in the Authorization header, you should also include the x-anchormailbox header which helps route the request to correct mailbox eg
GET https://graph.microsoft.com/v1.0/users/sharedmailbox#domain.com/messages HTTP/1.1
Host: graph.microsoft.com
Authorization: Bearer EwAoA8l6BAAU ... 7PqHGsykYj7A0XqHCjbKKgWSkcAg==
X-AnchorMailbox: sharedmailbox#domain.com
The other thing you might want to check is to ensure you have the correct scopes in your token you can use https://jwt.io/ for that
In order to use application permissions you will need to use the client credentials auth flow (not the authorization code auth flow which uses delegated permissions). To get a token make a request against "/oauth2/v2.0/token" and specify "grant_type=client_credentials" in the request. See examples of client credentials auth flow here for more details: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
My app uses delegated user access tokens to interact with the MSFT graph api. They have the Calendars.ReadWrite.Shared permission. When making requests to https://graph.microsoft.com/v1.0/subscriptions in production with the following request body:
{
"changeType": "created,updated,deleted",
"notificationUrl": <https_app_notification_url>,
"resource": "/me/calendars/<calendar_id>/events",
"expirationDateTime": (datetime.now(timezone.utc) + timedelta(minutes=4200)).isoformat(),
"clientState": <a_crypto_random_string>
}
almost every time the response is an error with the following in the response body:
[Status Code: Forbidden; Reason: Access is denied. Check credentials and try again.]
The most recent occurrence of this error had the following innerError in the response:
{'date': '2020-05-13T20:58:09', 'request-id': '1448e490-9e45-4a08-9aab-dd9c996c18db'}
This happens even when trying to subscribe to the user's own default calendar.
The strange thing is that the exact same code, when run on my local machine and tunneled to the web via ngrok to expose my app's notificationUrl endpoint, is able to consistently get a 201 from the MSFT's subscriptions endpoint.
I've checked the system time on the server with the date command and there's less than 1 second difference between my local machine and the server. So I don't think the expirationDateTime field is the issue; it's got 30 minutes of buffer built in as I believe the max according to the docs is 4230.
Should I be requesting additional permissions in the oauth scopes for the user access tokens? The docs say that Calendars.Read is enough so I would've thought Calendars.ReadWrite.Shared would do it. Or am I missing something else?
You need Calendars.Read as outlined in the documentation to subscribe to the current user's calendar.
I'm trying to build an application that uses Microsoft Graph to automatically create and read pages in OneNotes stored in SharePoint 365.
I can successfully do this using Graph Explorer as long as I am logged in, but can't get it to work using a bearer token in Postman
The error I am getting is:
Either scp or roles claim need to be present in the token
I successfully get an access token using this:
https://login.microsoftonline.com/common/oauth2/v2.0/token
And passing in the grant_type, client_id, client_secret, code, redirect_uri and scope
Then I perform the following call, with the bearer token included in the header:
https://graph.microsoft.com/v1.0/sites
With the following getting returned:
{
"error": {
"code": "AccessDenied",
"message": "Either scp or roles claim need to be present in the token.",
"innerError": {
"request-id": "fa442c72-4ffe-493b-a33a-8e9e78c94f09",
"date": "2018-01-19T09:56:34"
}
}
}
I have set up graph permissions as per below. I have also tried enabling ALL Notes permissions configured with the same result though
Graph Permissions Image
One post I found said to check http://jwt.calebb.net/ what is returned in the token, and I found that it doesn't contain any roles, so I wonder if this is the problem.
You may need to state your tenant in the url when getting the token :
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Thanks to Tsuyoshi Matsuzaki as seen here : How to use Application Permission with Azure AD v2
Your application can get access token using the following HTTP request
(OAuth). Note that you cannot use
https://login.microsoftonline.com/common/oauth2/v2.0/token (which is
commonly used) for getting the token. Instead, you must use
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token, which
identifies the specific tenant.
The call that you mentioned "https://graph.microsoft.com/v1.0/sites" is not valid.
Also request to ~/sites/... routes may need other permissions in addition to Notes.ReadWrite.
Recommend to follow steps mentioned in the following references:
https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference
https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_user
https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_service
Using the Graph API and related authentication/authorization flows, how can I access data of all users in an organization? Also, multiple organizations/tenants need to be able to use the app.
As an example: I have an app which needs to read events from all calendars of all users under contoso.onmicrosoft.com. I would like that jack#contoso.onmicrosoft.com, the administrator, authorizes the app which will then be able to read the mentioned the data. Using the Managed API this can be easily done via impersonation.
However, I am trying to do the same with Graph API and OAuth, but I can't find a straight forward solution, or I must be missing something very obvious. I have created an app through manage.windowsazure.com (multi-tenant), and configured it so that it requires Microsoft Graph API (all application and delegated permissions).
I did the following:
1) Point jack#contoso.onmicrosoft.com towards https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&redirect_uri=<my redirect url>&client_id=<my client id>
2) Jack authorizes the app
3) I get back: <my redirect url>/?code=<my authorization code>&session_state=<blah>
4) I send a POST request like below:
POST https://login.microsoftonline.com/common/oauth2/token
Headers: content-type: application/x-www-form-urlencoded
Body:
grant_type=authorization_code&code=<my auth code from step above>
&redirect_uri=<my redirect url>
&client_id=<my client id>
&client_secret=<my client secret>
&resource=https%3A%2F%2Fgraph.microsoft.com%2F
5) When I do the following:
GET https://graph.microsoft.com/v1.0/users/jack#contoso.onmicrosoft.com/messages
Headers: Authorization: Bearer <auth token from step #4>
I get a 200 OK response with the messages.
When I do the following:
GET https://graph.microsoft.com/v1.0/users/anyotheruser#contoso.onmicrosoft.com/messages
Headers: Authorization: Bearer <auth token from step #4>
I get a 403 Forbidden response with:
{
"error": {
"code": "ErrorAccessDenied",
"innerError": {
"date": "2016-06-07T08:47:27",
"request-id": "5b629e30-e6bd-474d-b3dd-8ce25c5ad1c4"
},
"message": "Access is denied. Check credentials and try again."
}
}
The flow/URLs you've referenced are for the authorization code flow which leverages delegated scopes.
A) If you want the app to only work (i.e. access all calendars) for admins then you are using the right flow.
B) If you want the app to work for all users after the admin consents to it, you'll need to use the app (client credentials) flow and use application scopes. This means that you'll need to separate out consent from the regular auth flow.
For consent you'll need to to point the admin to the following url:
GET https://login.microsoftonline.com/common/oauth2/authorize?resource=https://graph.microsoft.com/&client_id=<YourClientId>&client_secret=<YourClientSecret>&response_type=code&redirectUri=<YourRedirectUri>&prompt=admin_consent
For auth flow you'll need a single call from your web server:
POST https://login.microsoftonline.com/common/oauth2/token
body resource=https://graph.microsoft.com/&client_id=<YourClientId>&client_secret=<YourClientSecret>&response_type=code
Or better yet, just use ADAL's AquireToken(resource, clientCredentials) overload.
Once that's done, your app should be good to go to make requests to Graph.
Regardless if you want to stick to A) or switch over to B), to double check that things are set up correctly you can:
Check the token that you get back from Graph (the one you attach to the request along with Bearer) and confirm that it has a roles entry with the roles you need i.e. Calendars.Read
NOTE: The following steps 2.a & 2.b require you to have admin to a test tenant where you'd be consenting to the application.
2.a Use GraphExplorer (https://graphexplorer2.azurewebsites.net/) and confirm that consent has been properly set up by querying
beta/servicePrincipals?$filter=displayName eq '[YourApplicationName]'
If nothing shows up, then the no one has consented to the application.
2.b (only applicable for auth code flow with delegated scopes) Use GraphExplorer and confirm that either delegation has been authorized correctly by querying
beta/oauth2permissiongrants?$filter=clientId eq '[IdFrom ServicePrincipal in 2.a]'
And ensuring you get either a result for the specific user in question or for "AllPrincipals".
More info on app vs delegated scopes here: http://graph.microsoft.io/en-us/docs/authorization/permission_scopes
More info on app flow here: https://graph.microsoft.io/en-us/docs/authorization/app_only
Please use the app-only auth flow (see https://graph.microsoft.io/en-us/docs/authorization/app_only) to use the application permissions - for the token request (step 4) you need to pass grant_type=client_credentials instead of grant_type=authorization_code.