I'm developing an application where I need to use Microsoft Graph to access the files on a OneDrive for Business. I created a web app on Azure and I manage to get the authentication token and I'm able to retrieve the user information with https://graph.microsoft.com/v1.0/me. However, if I try to get the content of the OneDrive with https://graph.microsoft.com/v1.0/me/drive/root/children I get an access denied error.
I already checked on Graph Explorer and I'm able to get the result for the query without any issues. For my web app, I'm using the following Graph permissions:
People.Read - Delegated
Sites.ReadWrite.All - Delegated
Sites.ReadWrite.All - Application
Tasks.ReadWrite - Delegated
User.Export.All -Delegated
User.Export.All - Application
User.Invite.All - Delegated
User.Invite.All - Application
User.Read - Delegated
User.Read.All - Delegated
User.Read.All - Application
User.ReadBasic.All - Delegated
User.ReadWrite - Delegated
User.ReadWrite.All - Delegated
User.ReadWrite.All - Application
offline_access - Delegated
openid - Delegated
public async Task<string> GetTokenAsync()
{
HttpResponseMessage resp;
using(var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
var req = new HttpRequestMessage(HttpMethod.Post,
$"https://login.microsoftonline.com/{tenant}/oauth2/token/");
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{ { "grant_type", "password" },
{ "client_id", clientId },
{ "client_secret", clientSecret },
{ "resource", "https://graph.microsoft.com" },
{ "username", username },
{ "password", password },
{ "scope", "https%3A%2F%2Fgraph.microsoft.com%2F.default" }
});
resp = await httpClient.SendAsync(req);
string content = await resp.Content.ReadAsStringAsync();
var jsonObj = new JavaScriptSerializer().Deserialize<dynamic>(content);
string token = jsonObj["access_token"];
Console.WriteLine(token);
return token;
}
}
public async Task<string> SendGraphRequest(string requestUrl)
{
using(HttpClient httpClient = new HttpClient())
{
// Set up the HTTP GET request
HttpRequestMessage apiRequest =
new HttpRequestMessage(HttpMethod.Get, requestUrl);
apiRequest.Headers.UserAgent
.Add(new ProductInfoHeaderValue("OAuthStarter", "2.0"));
apiRequest.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", await GetTokenAsync());
// Send the request and return the response
HttpResponseMessage response = await httpClient.SendAsync(apiRequest);
var s = response.Content.ReadAsStringAsync();
Console.WriteLine(s.Result);
return s.Result;
}
}
My call for the graph api is:
SendGraphRequest("https://graph.microsoft.com/v1.0/me/drive").Wait();
The result I get from this is:
{
"error": {
"code": "accessDenied",
"message": "There has been an error authenticating the request.",
"innerError": {
"request-id": "request-id",
"date": "2019-09-24T11:03:29"
}
}
}
You're way over-provisioning your scopes here. You don't need both Read and ReadWrite scopes for example. You're also requesting some Application scopes but using the OAuth Password grant which will only work with Delegated scopes (you can't mix the two).
I would recommend pairing down your scopes to the following Delegated permissions:
openid
User.ReadWrite
User.ReadBasic.All
Files.ReadWrite.All
People.Read
Sites.ReadWrite.All
Tasks.ReadWrite
offline_access
Note that Sites.ReadWrite.All is a bit different than Files.ReadWrite.All. One concerns SharePoint, the other OneDrive.
After almost two week trying to fix this issue I realised that the problem was not the code, but the azure configuration. I was under the assumption that if a web app is installed on the account of the administrator of the active directory, that all users on that active directory would be capable of using the same web app to access their respective One Drive accounts. Unfortunately this is not the case, if a user (not the administrator) tries to use the web app to access One Drive, the only thing he or she will be capable of reaching is their account information, with no access to files. In order to use the graph api to access the files, the web app needs to be installed on their respective active directories.
Related
I am trying to use the google-api-nodejs library to manage some resources in the google Campaign Manager API.
I have confirmed that we currently have a GCP project configured, and that this project has the google Campaign Manager API enabled (screenshot at the bottom).
I have tried several ways of authenticating myself (particularly API keys, OAuth2, and Service account credentials). This question will focus on using an API key though, as theoretically it is the simplest (and I will raise a separate question for the issues I am facing using Service account credentials).
Now, I have generated an API key and I have restricted it to have access to the Campaign Manager API (this is visible in the screenshots below). I configured my code
following the using-api-key section of the library's repo (https://github.com/googleapis/google-api-nodejs-client/#using-api-keys):
import { assert } from "chai";
import { google } from "googleapis";
it("can query userProfiles using API Key", async () => {
try {
const api_key = "AIza ****";
const df = google.dfareporting({
version: "v3.5",
auth: api_key,
});
const res = await df.userProfiles.list({}); // error thrown on this line
console.log("res: ", res);
assert(true);
} catch (e) {
console.error("error: ", e);
assert(false);
}
});
Unfortunately, this code results in an error with the message Login required:
{
"code": 401,
"errors": [
{
"message": "Login Required.",
"domain": "global",
"reason": "required",
"location": "Authorization",
"locationType": "header"
}
]
}
It is worth noting that:
While the API key seems to be correctly configured (that is, it matches what is visible in the Google API), changing the value of the API key in our code does not change the result - the error is still exactly the same
Moreover, changing the auth attribute from google.dfareporting to userProfiles.list results in the same error.
const df = google.dfareporting({
version: "v3.5",
});
const res = await df.userProfiles.list({
auth: api_key, // error thrown on this line
});
If you check the documentation for userProfiles.list
You will find it states
This means in order to access this method you need to be authorized by a user using oauth2 with one of those scopes.
Api keys will only give you access to public data not private user data.
you sould check the Google api node.js client for information on authorization with this library.
const {google} = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// generate a url that asks permissions access
const scopes = ['https://www.googleapis.com/auth/dfareporting'
];
const url = oauth2Client.generateAuthUrl({
// 'online' (default) or 'offline' (gets refresh_token)
access_type: 'offline',
// If you only need one scope you can pass it as a string
scope: scopes
});
Has anyone successfully used the CreateOrGet request to Microsoft Graph v1.0 to create an Online Meeting using Application permissions? I had the Application created on the Azure Portal, and with the client id and secret declare and initialize the GraphServiceClient.
I have a service(Hangfire Server) which without user interaction does background and out of sync processes and notifications. One of the features is for this server to send organizational communications with embedded links to online Teams meetings. For this, I'm trying to use Client Credential flow
I have the following permissions in my token:
"roles": [
"OnlineMeetings.Read.All",
"OnlineMeetings.ReadWrite.All",
"User.Read.All"
],
The first column has the names of the permissions cut, but the necessary delegate and application permissions are all there, although the screenshot taken a couple of days ago doesn't include the application permission for User.Read.All. I have the Admin's consent on the Application permissions. The name you see painted out is the Directory name.
I'm using the Client Credentials Provider with the following code:
//Client Credentials - Application
app = ConfidentialClientApplicationBuilder
.Create(config.Value.ClientId)
.WithTenantId(config.Value.Tenant)
.WithClientSecret(config.Value.ClientSecret)
.Build();
var scopes = new string[] { "https://graph.microsoft.com/.default" };
graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
authResult = await app
.AcquireTokenForClient(scopes)
.ExecuteAsync();
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
_logger.LogInformation("Token included in GraphClient: " + authResult.AccessToken);
})
);
OnlineMeeting onlineMeeting = new OnlineMeeting() {.....}
var user = await graphClient.Users[{userPrincipalName}].Request().GetAsync(); //<-- This works with the User.Read.All permission
_logger.LogInformation("User: " + JsonConvert.SerializeObject(user, serializerSettings));
string meetingId = Guid.NewGuid().ToString();
OnlineMeeting createdMeeting = await graphClient.Users[user.Id].OnlineMeetings.CreateOrGet(meetingId, null, onlineMeeting.EndDateTime,
onlineMeeting.Participants, onlineMeeting.StartDateTime,onlineMeeting.Subject).Request().PostAsync(); //<-- This doesn't work, and I'm getting a ServiceException
This is the error I get:
StatusCode: Forbidden ResponseBody: {
"error": {
"code": "Forbidden",
"message": "Application does not have permission to CreateOrGet online meeting on behalf of this user.",
"innerError": {
"date": "2021-03-26T15:55:22",
"request-id": "6e8466cb-807a-44df-93bf-27d42c413e44",
"client-request-id": "6e8466cb-807a-44df-93bf-27d42c413e44"
}
}
}
Any clues as to what might be the problem?
You might have missed below note which is mentioned in documentation:
Documentation: https://learn.microsoft.com/en-us/graph/api/application-post-onlinemeetings?view=graph-rest-1.0&tabs=http
Create application access policy steps: https://learn.microsoft.com/en-us/graph/api/application-post-onlinemeetings?view=graph-rest-1.0&tabs=http
I am integrating ServiceNow with Teams specifically to push notifications to users and channels using microsoft graph api.
Issue #1 -
I have configured an OAuth profile for azure app to utilize microsoft graph API, setup required API permissions and calling various chat related API functions. For 'Create Chat' request I am getting below error:
responseBody: {
"error": {
**"code": "UnknownError",**
"message": "",
"innerError": {
"date": "2021-01-29T09:20:53",
"request-id": "90a99bf5-048f-445b-b58c-ee46fc30edf3",
"client-request-id": "90a99bf5-048f-445b-b58c-ee46fc30edf3"
}
}
}
API call -
var restMessage = new sn_ws.RESTMessageV2();
restMessage.setHttpMethod("POST");
restMessage.setEndpoint("https://graph.microsoft.com/beta/chats");
restMessage.setRequestHeader('Content-Type', 'application/json');
restMessage.setRequestHeader('Authorization', 'Bearer ' + retrieveToken()); //retrieveToken() function creates access token.
restMessage.setAuthenticationProfile('oauth', 'a5325df007022010f60df7fc7c1ed0a2');
restMessage.setRequestBody(global.JSON.stringify(
{"chatType": "oneOnOne","members":
[
{
"#odata.type": "#microsoft.graph.aadUserConversationMember",
"roles": ["owner"],
"user#odata.bind": "https://graph.microsoft.com/beta/users('<userID>')"
},
{
"#odata.type": "#microsoft.graph.aadUserConversationMember",
"roles": ["owner"],
"user#odata.bind": "https://graph.microsoft.com/beta/users('<userID>')"
}
]
}
));
var response = restMessage.executeAsync();
var responseBody = response.getBody();
gs.info('responseBody: '+responseBody);
Most of the suggestions are to set appropriate API permissions. This is already set correctly on Azure (both delegated and apppication permissions) and in OAuth entity scope in servicenow.
Issue #2 -
For the same setup instead of usign scripted API, I defined REST Message record and trying to get the token by clicking 'Get OAuth Token' related link and getting following error:
https://dev72030.service-now.com/oauth_redirect.do?error=invalid_client&error_description=AADSTS650053%3a+The+application+%27msgraph-client-app%27+asked+for+scope+%27**Chat.Read.All%27+that+doesn%27t+exist+on+the+resource**+%2700000003-0000-0000-c000-000000000000%27.+Contact+the+app+vendor.%0d%0aTrace+ID%3a+b0b9ff39-fd1a-49c9-85fa-3c8c71943600%0d%0aCorrelation+ID%3a+9c276922-3a77-4cc1-890b-23177ffd1845%0d%0aTimestamp%3a+2021-01-29+10%3a31%3a27Z&state=-1877865371#
This say I need to set 'Chat.Read.All' API permission. This is already provided on both sides, Azure app and OAUth profile in SNOW.
What am I missing
Could you please check this docs? For second issue: Could you please check the permissions on src by going through the jwt.io
i have been trying to tackle this issues for a while now, i am workin on an Office-js addin for Outlook and is trying to access Microsoft Graph data through my addin.
But i have been unable to authenticate the token i recieve from getAccessTokenAsync.
If i attempt to use the authenticator from Office-JS-Helpers i can get access, but i would prefer to use the built in function of the addin for it.
the code i am trying to use is this:
Office.initialize = () => {
var options = { forceAddAccount: true, forceConsent: true } as Office.AuthOptions;
Office.context.auth.getAccessTokenAsync(options, getAccessTokenAsyncCallback);
}
function getAccessTokenAsyncCallback(accessTokenResponse) {
console.log(accessTokenResponse.value)
client = MicrosoftGraph.Client.init({
authProvider: (done) => {
done(null, accessTokenResponse.value);
},
debugLogging: false
})
client.api("/me")
.get((err, res, rawResponse) => {
console.log(err)
console.log("rawResponse")
console.log(rawResponse)
})
}
and my WebApplicationInfo in my manifest is:
<WebApplicationInfo>
<Id>{AppUID}</Id>
<Resource>api://localhost:3000/{AppUID}</Resource>
<Scopes>
<Scope>profile</Scope>
<Scope>user.read</Scope>
</Scopes>
</WebApplicationInfo>
</VersionOverrides>
and the rights has been set up in my app on the Application Registration Portal.
as far as i can see when validating/decoding the JSON Web Token i recieve from getAccessTokenAsync, it should be valid.
but whenever i try to connect using this token i get this response:
{
"statusCode": 401,
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"requestId": "4a0ce952-0e90-XXXXXXXXX-db20c6cca94e",
"date": "2018-08-30T05:37:43.000Z",
"body": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError": {
"request-id": "4a0ce952-0e90-XXXXXXXXX-db20c6cca94e",
"date": "2018-08-30T07:37:43"
}
}
}
i simply can't figure out what the issue is here, am i wrong in that the token returned from getAccessTokenAsync is the one i need for this?
oh yes and i am running up against an Office365 environment with Outlook 2016 version 1807 build 10325.20118 click to run
The token that is returned by getAccessTokenAsync, called the bootstrap token, does NOT give your add-in access to MS Graph directly. Instead it gives the Office host application, Outlook in this case, access to your add-in's web application. Your add-in's web application then uses that token to get an access token to Microsoft Graph. It does this by using the "on behalf of" OAuth flow. Please see the extensive documentation at this node: SSO in Office Add-ins and, for Outlook-specific guidance, also see Authenicate a user in an Outlook Add-in with an SSO token.
I am trying to get my office365 planner task using the Microsoft Graph API but I am getting below error:
GET https://graph.microsoft.com/beta/me/tasks
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Bearer access token is empty.",
"innerError": {
"request-id": "4f209643-f3f6-4256-87b7-cf4f2fd489eb",
"date": "2016-05-16T09:03:33"
}
}
}
The error message is pretty self-explanatory,
"message": "Bearer access token is empty."
You need to be authenticated before you can make this RESTful API call.
If you're developing your own app, follow this tutorial to learn about OAuth2 workflow,
http://graph.microsoft.io/en-us/docs/platform/rest
If you're using Graph Explore, make sure you're logged in before call that API.
I use this Controller's action code for granting admin consent:
//<Summary>
//Used to grant admin consent to Azure AD apps
//</Summary>
public virtual ActionResult AdminConsentApp()
{
string strResource = "https://graph.microsoft.com";
string strRedirectController = "https://localhost:[my local debug port number]";
string authorizationRequest = String.Format(
"https://login.windows.net/common/oauth2/authorize?response_type=code&client_id={0}&resource={1}&redirect_uri={2}&prompt={3}",
Uri.EscapeDataString(SettingsHelper.ClientId),
Uri.EscapeDataString(strResource),
Uri.EscapeDataString(String.Format("{0}", strRedirectController)),
Uri.EscapeDataString("admin_consent")
);
return new RedirectResult(authorizationRequest);
}
Hope this help,
Cheers, Danfer.