I am trying to use the Unified API (Microsoft.Graph 1.0.1) to access my users profil photos, but I only get the following error back when accessing the photo:
Code: ErrorAccessDenied
Message: Access is denied. Check credentials and try again.
Accessing/Listing the other user profile data works fine and my application was added as a "Company Administrator" via PowerShell and has all rights set in the management portal. When I use the GraphExlorer logged in with my admin user it also works fine. Also via the "old" Azure Active Directory Graph API I can read/write to the users thumbnail photo, but thats not the one showing up in Office 365.
How can I get the appropriate access rights to perform actions on users profile photo?
This is the code I use (shortened to the parts in question):
class Program
{
private const string authStringMicrosoft = "https://login.microsoftonline.com/MY_APP_ID/";
private const string clientID = "MY_CLIENT_ID";
private const string clientSecret = "MY_CLIENT_SECRET";
private const string graphResourceId = "https://graph.microsoft.com";
static void Main(string[] args)
{
AsyncContext.Run(RunAsync);
Console.WriteLine("DONE");
Console.ReadLine();
}
private static async Task RunAsync()
{
var token = await GetAppTokenAsync(authStringMicrosoft, graphResourceId);
var authHelper = new AuthenticationHelper() { AccessToken = token }
var graphClient = new GraphServiceClient(authHelper);
await ListUser(graphClient);
}
private static async Task ListUser(GraphServiceClient graphClient)
{
Console.WriteLine("User-List:");
var users = await graphClient.Users.Request().GetAsync();
foreach (var user in users)
{
Console.WriteLine($"{user.UserPrincipalName}:\t\t{user.GivenName} {user.Surname}");
if (user.UserPrincipalName == "USER_WITH_PICTURE")
{
var graphUser = graphClient.Users[user.UserPrincipalName];
var graphPhoto = graphUser.Photo;
var photoInfo = await graphPhoto.Request().GetAsync(); // <= here the exceptions is thrown
Console.WriteLine($"{photoInfo.Id}:\t{photoInfo.Width}x{photoInfo.Height}");
var photoStream = await graphPhoto.Content.Request().GetAsync();
byte[] photoByte = new byte[photoStream.Length];
photoStream.Read(photoByte, 0, (int)photoStream.Length);
File.WriteAllBytes(@"D:\User.jpg", photoByte);
}
}
}
private static async Task<string> GetAppTokenAsync(string authority, string azureGraphAPI)
{
var authenticationContext = new AuthenticationContext(authority);
var clientCred = new ClientCredential(clientID, clientSecret);
var authenticationResult = await authenticationContext.AcquireTokenAsync(azureGraphAPI, clientCred);
return authenticationResult.AccessToken;
}
}
public class AuthenticationHelper : IAuthenticationProvider
{
public string AccessToken { get; set; }
public Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization", "Bearer " + AccessToken);
return Task.FromResult(0);
}
}
I use the following NuGet-packages:
<packages>
<package id="Microsoft.Data.Edm" version="5.7.0" targetFramework="net46" />
<package id="Microsoft.Data.OData" version="5.7.0" targetFramework="net46" />
<package id="Microsoft.Data.Services.Client" version="5.7.0" targetFramework="net46" />
<package id="Microsoft.Graph" version="1.0.1" targetFramework="net46" />
<package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="2.24.304111323" targetFramework="net46" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net46" />
<package id="Nito.AsyncEx" version="3.0.1" targetFramework="net46" />
<package id="System.Spatial" version="5.7.0" targetFramework="net46" />
</packages>
This is an example request delivering the error (using postman with the token read out from the app above):
GET /v1.0/users/MY_USER_WITH_PHOTO/photo/ HTTP/1.1
Host: graph.microsoft.com
Connection: keep-alive
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1...
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Postman-Token: e756a8a3-22e2-d40c-8e52-15c4d1aa7468
Accept: /
Accept-Encoding: gzip, deflate, sdch
Accept-Language: de,en-US;q=0.8,en;q=0.6
And the response:
HTTP/1.1 403 Forbidden
Cache-Control: private
Transfer-Encoding: chunked
Content-Type: application/json
Server: Microsoft-IIS/8.5
request-id: 96e8dda8-2353-4891-8c42-99cfe7e22887
client-request-id: 96e8dda8-2353-4891-8c42-99cfe7e22887
x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"North Europe","Slice":"SliceA","ScaleUnit":"001","Host":"AGSFE_IN_4","ADSiteName":"DUB"}} Duration: 1367.7691
X-Powered-By: ASP.NET
Date: Sun, 01 May 2016 17:57:02 GMT
Body:
{
"error": {
"code": "ErrorAccessDenied",
"message": "Access is denied. Check credentials and try again.", "innerError": {
"request-id": "96e8dda8-2353-4891-8c42-99cfe7e22887",
"date": "2016-05-01T17:57:02"
}
}
}
Again, if I remove the /photo
from the request I get all common user details without a problem.
Here the permissions of my app (web-app):
Here a decrypted access token:
{
typ: "JWT",
alg: "RS256",
x5t: "MnC_VZcATfM5pOYiJHMba9goEKY",
kid: "MnC_VZcATfM5pOYiJHMba9goEKY"
}.
{
aud: "https://graph.microsoft.com",
iss: "https://sts.windows.net/11205e59-fa81-480f-b497-571579c5389a/",
iat: 1462795409,
nbf: 1462795409,
exp: 1462799309,
appid: "c34a87ef-352a-4af4-a166-eb7e521a0ec9",
appidacr: "1",
idp: "https://sts.windows.net/11205e59-fa81-480f-b497-571579c5389a/",
oid: "1db8c6b5-10ba-40ac-bbff-86ab440c4fd3",
roles: [
"Mail.ReadWrite",
"Device.ReadWrite.All",
"User.ReadWrite.All",
"Calendars.Read",
"Group.Read.All",
"Directory.ReadWrite.All",
"Contacts.ReadWrite",
"Group.ReadWrite.All",
"Directory.Read.All",
"User.Read.All",
"Mail.Read",
"Calendars.ReadWrite",
"Mail.Send",
"MailboxSettings.ReadWrite",
"Contacts.Read"
],
sub: "1db8c6b5-10ba-40ac-bbff-86ab440c4fd3",
tid: "11205e59-fa81-480f-b497-571579c5389a",
ver: "1.0"
}