user3881465
user3881465

Reputation: 269

Get user's profile picture from Azure

I have a multi tenant app in azure. I am able to login and get basic info of singed in user like name and email.

Now I need to get user's profile picture from azure. I tried some solutions provided on internet but none of them is working for me.

Here is my Startup.Auth.cs code

public partial class Startup
    {
        private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
        private string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
        private string graphResourceID = "https://graph.windows.net";
        private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
        private string authority = aadInstance + "common";
        private ApplicationDbContext db = new ApplicationDbContext();

        public void ConfigureAuth(IAppBuilder app)
        {

            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions { });

            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = clientId,
                    Authority = authority,
                    TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                    {
                        // instead of using the default validation (validating against a single issuer value, as we do in line of business apps), 
                        // we inject our own multitenant validation logic
                        ValidateIssuer = false,
                    },
                    Notifications = new OpenIdConnectAuthenticationNotifications()
                    {
                        SecurityTokenValidated = (context) => 
                        {
                            return Task.FromResult(0);
                        },
                        AuthorizationCodeReceived = (context) =>
                        {
                            var code = context.Code;

                            ClientCredential credential = new ClientCredential(clientId, appKey);
                            string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
                            string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;

                            AuthenticationContext authContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
                            AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                                code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceID);

                            return Task.FromResult(0);
                        },
                        AuthenticationFailed = (context) =>
                        {
                            context.OwinContext.Response.Redirect("/Home/Error");
                            context.HandleResponse(); // Suppress the exception
                            return Task.FromResult(0);
                        }
                    }
                });

        }
    }

Here is code for getting user's basic info

        private ApplicationDbContext db = new ApplicationDbContext();
        private string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
        private string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
        private string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
        private string graphResourceID = "https://graph.windows.net";

        // GET: UserProfile
        public async Task<ActionResult> Index()
        {
            string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
            string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
            try
            {
                Uri servicePointUri = new Uri(graphResourceID);
                Uri serviceRoot = new Uri(servicePointUri, tenantID);
                ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
                      async () => await GetTokenForApplication());

                // use the token for querying the graph to get the user details

                var result = await activeDirectoryClient.Users
                    .Where(u => u.ObjectId.Equals(userObjectID))
                    .ExecuteAsync();
                IUser user = result.CurrentPage.ToList().First();

                return View(user);
            }
            catch (AdalException)
            {
                // Return to error page.
                return View("Error");
            }
            // if the above failed, the user needs to explicitly re-authenticate for the app to obtain the required token
            catch (Exception)
            {
                return View("Relogin");
            }
        }

        public void RefreshSession()
        {
            HttpContext.GetOwinContext().Authentication.Challenge(
                new AuthenticationProperties { RedirectUri = "/UserProfile" },
                OpenIdConnectAuthenticationDefaults.AuthenticationType);
        }

        public async Task<string> GetTokenForApplication()
        {
            string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
            string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
            string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;

            // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
            ClientCredential clientcred = new ClientCredential(clientId, appKey);
            // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
            AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
            AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
            return authenticationResult.AccessToken;
        }

I also tried this and getting error

Insufficient privileges to complete the operation.

My app has following permissions

Code for getting user photo

var servicePoint = new Uri("https://graph.windows.net");
var serviceRoot = new Uri(servicePoint, "<your tenant>"); //e.g. xxx.onmicrosoft.com
const string clientId = "<clientId>";
const string secretKey = "<secretKey>";// ClientID and SecretKey are defined when you register application with Azure AD
var authContext = new AuthenticationContext("https://login.windows.net/<tenant>/oauth2/token");
var credential = new ClientCredential(clientId, secretKey);
ActiveDirectoryClient directoryClient = new ActiveDirectoryClient(serviceRoot, async () =>
{
    var result = await authContext.AcquireTokenAsync("https://graph.windows.net/", credential);
    return result.AccessToken;
});

var user = await directoryClient.Users.Where(x => x.UserPrincipalName == "<username>").ExecuteSingleAsync();
DataServiceStreamResponse photo = await user.ThumbnailPhoto.DownloadAsync();
using (MemoryStream s = new MemoryStream())
{
    photo.Stream.CopyTo(s);
    var encodedImage = Convert.ToBase64String(s.ToArray());
}

Upvotes: 3

Views: 9065

Answers (2)

Bruce Chen
Bruce Chen

Reputation: 18465

AFAIK, the Read directory data permission would allow your app to read all of the data in the organization's directory, such as users, groups, and apps, etc. I assumed that could leverage jwt.io for decoding your access_token and check the scp and make sure the scope Directory.Read.All (Read directory data) and User.Read (Enable sign-in and read user profile) exist as follows:

enter image description here

Note: The permission Read directory data is a delegation permission and must be consented by the Administrator. If you are admin of your AAD, You could grant permission by clicking Grant permissions button shown in the following screenshot:

enter image description here

After you granted permission, you could wait for a while and check with your application and make sure the Directory.Read.All scope has been added to your decoded access_token.

enter image description here

UPDATE:

Based on your code, you are using client credential flow for token acquisition, at this point, you need to set APPLICATION PERMISSIONS for Windows Azure Active Directory API as follows:

enter image description here

Wait for a while and decode your access_token and check the granted permissions under roles as follows:

enter image description here

UPDATE2:

As you said that you are not the admin under Microsoft directory, I tested this on my own AAD, I could grant permissions and it could work on my side. Moreover, I noticed that your are using code flow and retrieve the delegated permissions for getting user's basic info successfully. You are using the client credential flow for getting user's basic info, but you have no permission for granting permissions. I assumed that you could leverage ADALTokenCache and use GetTokenForApplication for retrieving the access_token, then get user photo.

Upvotes: 0

RAS
RAS

Reputation: 3385

Insufficient privileges might mean that you need to add some additional permissions.

Try to grant: User.Read permisisons. See more details in the docs: https://msdn.microsoft.com/en-us/library/azure/ad/graph/howto/azure-ad-graph-api-permission-scopes

Also, try to follow the following SO topic on how to grant permissions: Graph API - Insufficient privileges to complete the operation

Upvotes: 1

Related Questions