2
votes

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): permissions

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"
}
1
Can you please provide the HTTP request and response including headers for a failure case? Thanks.Venkat Ayyadevara - MSFT
@VenkatAyyadevara-MSFT Thanks, but I already managed to get the request/response using postman - please see my updated question: at the end I included an exampleChristoph Fink
Thanks for the info! Will ask my team to take a look and get back to you.Venkat Ayyadevara - MSFT
@ChrFin can you tell us what permissions you've configured/requested as part of your app configuration please? Or better still can you decrypt your token (using something like jwt.calebb.net) and tell us which "roles" have been granted to your application please?Dan Kershaw - MSFT

1 Answers

0
votes

Just in case anyone else reads this when getting that error. I had this same error when creating my own graphClient and the reason I got it was due to using a non-admin account with...

var users = await graphClient.Users.Request().Select().GetAsync();

With a non-admin account, you only have access to some basic properties like lastname, firstname etc - this worked for me...

var users = await graphClient.Users.Request().Select("mail,givenName,surname").GetAsync();