How to Update Profile Picture with Microsoft Graph API - asp.net

I'm developing a .Net Core 3.1 API that will connect to Microsoft Graph to update photos of company employees.
However, I searched in several places and didn't find any code or method that could help me change a user's photo without me being logged in with that user's account.
Is there any way I can change the photo of a user with Microsoft Graph without me being logged in with the same?
Remembering that my user is the administrator of Azure AD and my application has "User.ReadWrite.All" released.

Firstly, what you need is this api, and as you can see it provides the application permission so that you can use client credential flow to generate access token and use it to call the api. Client credential flow makes users don't need to sign in and generate access token, then it can meet your requirement.
Here's the detail, you can generate token by this request, pls not you need to set User.ReadWrite.All application permission for you azure ad app:
and you can call the api with the token like this:
If you found your token can't work correctly, maybe you can try to add Group.ReadWrite.All application permission to your azure ad app too, as this is a known issue mentioned in the api document.
And maybe what you need is code snippet, then you can use graph sdk to call ms graph api:
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "your_tenant_name.onmicrosoft.com";
var clientId = "azure_ad_app_client_id";
var clientSecret = "client_secret_for_the_azuread_app";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
using var stream = new System.IO.MemoryStream(Encoding.UTF8.GetBytes(#"Binary data for the image"));
await graphClient.Users["user_id_which_you_wanna_change_photo_for"].Photo.Content
.Request()
.PutAsync(stream);

Related

Authenticating to Azure AD non-interactively

I have an asp.net application where I am trying to integrate AD authentication with out redirecting the user to https://login.microsoftonline.com. I used the following code and did some modification as per the latest documentation but I am getting an error as follows
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalException: 'unknown_user_type: Unknown User Type'
Here is the code I have written
var tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
var serviceUri = "https://login.microsoftonline.com/4061611f-98d4-4484-944c-3796d4c9746f/oauth2/authorize";
var clientID = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
var userName = "";
var password = "";
var authority = string.Format(System.Globalization.CultureInfo.InvariantCulture, System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
var authContext = new AuthenticationContext(authority);
var credentials = new UserPasswordCredential(userName, password);
var authResult = await authContext.AcquireTokenAsync(serviceUri, clientID, credentials);
Settings are as follows for tenant and application id
Am I missing some thing can some one help me.
Not sure about why the error says "unknown user type", but I'm pretty sure this doesn't work because you are trying to use ROPC flow with a personal Microsoft account.
Resource Owner Password Credentials (ROPC) flow does not work if:
The user is a personal Microsoft account
The user has MFA enabled
The user's password has expired
It also doesn't work with Guest accounts if I recall correctly.
In general I recommend avoiding this method of authentication and instead redirect the user to the Azure AD login page.
Being unable to use multi-factor authentication is usually a pretty big reason to avoid this flow.
Also I'm pretty sure your serviceUri is wrong.
The parameter should define the API you want the tokens for, not a URL to the Azure AD authorization page.

How to get Facebook Picture and Friends using Azure Mobile Services?

Is it possible to get Facebook Profile Picture and user friends using Azure Mobile services Claims or any other way?
On the azure backend as shown below,
User picture is missing there if it is not public profile. It looks
like public profile is only link to facebook profile
I set user_friends but when I request identites with the code below,
i see 9 claims (name, surname, fullname, gender, profile etc.) but I
dont get any friends information.
Code I am using is simply
identities = await App.Client.InvokeApiAsync<List<AppServiceIdentity>>("/.auth/me");
According to the documentation, it should return what i set in the backend as dictionary of claims type and value.
I am able to achieve this using simple http request as below
var requestUrl = $"https://graph.facebook.com/v2.11/me/friends?fields=id,picture.width(72).height(72),name&access_token=" + accessToken;
But i want a general solution using azure backend if possible?
AFAIK, your could only retrieve the basic profile (name, picture, email,etc.) from the current authenticated user. Per my understanding, if you hope your mobile app backend would handle the requests against the Graph API of facebook stead of retrieving the access token and send request in your mobile client, you could create a custom Web API and use the following code to get the access token and send request in your mobile backend:
// Get the credentials for the logged-in user.
var credentials =
await this.User
.GetAppServiceIdentityAsync<FacebookCredentials>(this.Request);
if (credentials.Provider == "Facebook")
{
// Create a query string with the Facebook access token.
var requestUrl = $"https://graph.facebook.com/v2.11/me/friends?fields=id,picture.width(72).height(72),name&access_token=" + credentials.AccessToken;
// Create an HttpClient request.
var client = new System.Net.Http.HttpClient();
// Request the current user info from Facebook.
var resp = await client.GetAsync(requestUrl);
resp.EnsureSuccessStatusCode();
// Do something here with the Facebook user information.
var fbInfo = await resp.Content.ReadAsStringAsync();
}
For your mobile client, you could leverage InvokeApiAsync to call the related custom Web API provided by your mobile backend. More details, you could refer to How to: Work with authentication and Custom HTTP Endpoints.

Using a refresh token in AAD B2C with Azure App Service and Xamarin.Forms

I'm trying to get a refresh token set up in my Xamarin.Forms app using AAD B2C. I've got everything set up but run into issues when calling LoginAsync on my MobileServiceClient. All of the docs and examples I can find show to update my LoginAsync method to this:
var user = await App.MobileServiceClient.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
new Dictionary<string, string>() { { "response_type", "code id_token" } });
Except that the MobileServiceClient does not take a Dictionary<string, string> for the second parameter. It takes a JObject. Here's what my current code looks like:
var authResult = await App.AuthenticationClient.AcquireTokenAsync(Constants.Scopes, "", UiOptions.SelectAccount, string.Empty, null, Constants.Authority, Constants.Policy);
var payload = new JObject();
payload["access_token"] = authResult.Token;
var user = await App.MobileServiceClient.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
I can not find an example use the JObject anywhere.
It is as simple as adding payload["response_type"] = "code id_token"; to my payload?
AFAIK, Mobile Apps support two authentication flows (client-managed flow and server-managed flow).
Client-managed authentication
Your app can independently contact the identity provider and then provide the returned token during login with your backend. This client flow enables you to provide a single sign-on experience for users or to retrieve additional user data from the identity provider.
After you retrieved the token, then you would login with your azure mobile backend by passing the token into a JObject instance as follows:
JObject payload = new JObject();
payload["access_token"] = ar.AccessToken;
var user = await client.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
For more details about other identity providers via client-flow authentication, you could refer to Client-managed authentication.
Server-managed authentication
Your app directly contacts your mobile backend, then your azure mobile backend contacts the identity provider and provide you with the logged user.
For Xamarin.Forms UWP app, you could login as follows:
For Xamarin.Forms IOS app, you could login as follows:
For more details about server-managed authentication in Xamarin.Forms, you could refer to Add authentication to your Xamarin Forms app.
UPDATE:
I have checked that if you call MobileServiceClient.LoginAsync in PCL, you could not see any extensions for LoginAsync. As you could see, there are many extension LoginAsync methods in the Microsoft.WindowsAzure.Mobile.Ext.dll for each platform. You need to define the IAuthenticate interface and implement it in each of your app (uwp, android, ios, etc.), for more details you could refer to here.

Navigate to a website and set claims

I need to navigate to a web site and set a number of claims for that web site to use, but I cannot find any explanation on how to do it. I must be googling the wrong words.
I'm using c# and .Net framework 4.6.1
Edit 1
I was asked to explain my challenge better. I am developing 2 separate websites. Users will always go to website A where they will authenticated using Azure B2C. Once authenticated they will perform a few actions after which they will be navigated to website B. Azure B2C passes user data to website A using Claims, which I would like to forward to website B
What you need to do is to expose a new login action in your website B. This login action can receive an encrypted token which inside contains the claims you are interested in forwarding to the site B. Once that encrypted token is received, the website B can decrypt it and recreate the claims that you are interested in. You can use the built in JwtSecurityToken Handler:
https://msdn.microsoft.com/en-us/library/system.identitymodel.tokens.jwtsecuritytokenhandler(v=vs.114).aspx
For example:
public string CreateSecurityToken(string audience, IEnumerable<Claim> claims)
{
if (claims == null) return null;
var credentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(Encoding.UTF8.GetBytes("SOMEKEY")),
"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
"http://www.w3.org/2001/04/xmlenc#sha256");
var claimsIdentity = new ClaimsIdentity(claims, "Custom Authentication", System.Security.Claims.ClaimTypes.Name, System.Security.Claims.ClaimTypes.Role);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = claimsIdentity,
TokenIssuerName = TokenIssuer,
AppliesToAddress = "http://" + audience,
Lifetime = new Lifetime(DateTime.UtcNow, DateTime.UtcNow.AddMinutes(GetTokenExpirationMinutes(audience))),
SigningCredentials = credentials
};
var jwtHandler = new JwtSecurityTokenHandler();
var token = jwtHandler.CreateToken(tokenDescriptor);
return jwtHandler.WriteToken(token);
}
this will create a token which you can send to a login method on website B
A claim from website A will not be sent to website B.
This could work this way:
A users logs on to site A (using credentials from B2C)
User saves info into B2C (using Graph API)
Redirect to Site B
User logs on to site B (this can occur without prompting the user, same B2C)
Claim are created for site B, but they can contain values set during step 2
graph API:
https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-devquickstarts-graph-dotnet/
If the info is already in the B2C directory, the example is even more simple, create 2 sites:
https://azure.microsoft.com/nl-nl/documentation/articles/active-directory-b2c-devquickstarts-web-dotnet/
Create a custom attribute to store the information:
https://azure.microsoft.com/nl-nl/documentation/articles/active-directory-b2c-reference-custom-attr/
Create a B2C custom attribute:
Create 2(both) B2C Applications
Create a single B2C sign-in policy using the custom attribute and add it to the claims
Use the same policy in both applications, both should see the same claim

Google Admin SDK Directory API - Getting Groups from varying Domains

I'm attempting to migrate our code for using the (soon to be deprecated) Google Provisioning API to the Admin SDK Directory API, via the .NET client libraries provided by Google.
In the old Provisioning API (via the .NET client library) a call to get the groups for a domain was very simple:
Google.GData.Apps.AppsService apps = new Google.GData.Apps.AppsService(AppDomain, DomainAdminEmail, AdminPassword);
Google.GData.Apps.Groups.GroupsService service = apps.Groups;
AppsExtendedFeed appsFeed = service.RetrieveAllGroups();
Not exactly rocket science, and the only credentials required were the domain, the domain's admin email, and the admin password. As long as you can supply those three parameters, you can get the groups for any domain.
I've been trying for two days to create an equivalent call using the new Admin SDK Directory API (via the new .NET client library), and it's giving me a very hard time. The only way that I've been able to get it to work at all is to create a Service Account for the project associated with the domain's admin email, including generation of a private key file, based on a useful post by mwpreston):
//Create security certificate using private key file and password.
var certificate = new X509Certificate2(pathToPrivateKeyFile, privateKeyPassword, X509KeyStorageFlags.Exportable);
//Create a service credential using the certificate, admin email and API scopes.
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = adminUserEmail,
Scopes = scopes
}.FromCertificate(certificate));
//Create Directory Service using the service credential and the application name.
var dirservice = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = applicationName
});
var groupsListRequest = dirservice.Groups.List();
groupsListRequest.Domain = domain;
domainGroups = groupsListRequest.Execute();
However, because the Service Account is associated with a specific domain (as specified in the admin email account for the Service Account) it can only be used to request groups from that specific domain.
We have clients with thousands of different domains. It's impractical to create a new Service Account for every client's domain (as well as a private key file).
I've searched and searched for a way to call the DirectoryService in a way that uses the same parameters (domain, admin email and password) but I can't find anything. The documentation for the Admin API .NET client library is extremely sparse and is of no help.
The Provisioning API will be deprecated on April 20th 2015, so someone else there must have been faced with this issue. Can anyone help?
You should use "impersonate" (via .setServiceAccountUser("user#example.com")) while constructing credential. Look for examples here.
You can create a project on Google Developer Console and generate a refresh/access token instead. Here's the link - https://developers.google.com/accounts/docs/OAuth2WebServer
I haven't used the client library hence can't help you with the code. You can use the 'list' API which will give you all the groups for all domains of a customer (if you provide the customerId) - https://developers.google.com/admin-sdk/directory/v1/reference/groups/list

Resources