Access Microsoft Graph API from Azure B2C - azure

I have an Azure AD B2C tenant with an application running. It is configured to use only Azure AD and Microsoft Accounts to login. This application is used by App Center Auth.
I want to access some Microsoft APIs (Microsoft Graph API, Azure DevOps API) from my mobile application with the same login. Therefore I added the API permissions (Azure DevOps -> user_impersonation and Microsoft Graph -> User.Read) to my application in my Azure AD tenant (not the B2C tenant) to grant these permissions on login.
If I now try to use the access token after the login in my application to access e.g. the user in Microsoft Graph, I get an Unauthorized error.
// Sign-in succeeded, UserInformation is not null.
var userInfo = await Auth.SignInAsync();
// Get tokens. They are not null.
var idToken = userInfo.IdToken;
var accessToken = userInfo.AccessToken;
Within the same method, I try to get the user photo from Microsoft Graph
var graphAPIEndpoint = "https://graph.microsoft.com/v1.0/me";
var scopes = new[] { "user.read" };
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, graphAPIEndpoint + "/photo/$value");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.SendAsync(request);
var image = await response.Content.ReadAsByteArrayAsync();
UserImage.Source = ImageSource.FromStream(() => new MemoryStream(image));
Has anyone an advice how to configure the B2C / Azure AD application to get access to these API with the Access Token? Or am I on the complete wrong way?

Just like #juunas said, as of today,you need to use AAD Graph API to access Azure AD B2C tenant, this is different from the Microsoft Graph API.
Here is the document for this: https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet

Take a look at this documentation published about using the Microsoft Graph API and authenticating against it for B2C instances:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/microsoft-graph-get-started?tabs=applications
With recent migration away from login.microsoftonline.com to *.b2clogin.com, if you're using MSAL to obtain the authentication token from the AAD B2C instance, you need to override the authority config, and turn off authority validation, as per this document:
https://learn.microsoft.com/bs-latn-ba/azure/active-directory/develop/msal-b2c-overview
In JavaScript, this is done like below:
const msalConfig = {
auth: {
clientId: "e760cab2-b9a1-4c0d-86fb-ff7084abd902" //This is your client/application ID
authority: "https://fabrikamb2c.b2clogin.com/fabrikamb2c.onmicrosoft.com/b2c_1_susi", //This is your tenant info
validateAuthority: false
},
};
// create UserAgentApplication instance
const myMSALObj = new Msal.UserAgentApplication(msalConfig);
In C# with the MSAL.NET library, it would be done with the authority URL passed to the PublicClientApplication class constructor.

I was able to consume Graph REST API using the below steps,
Get access token,
POST
URL: https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
form-data:
{"client_id": <Registered application clientId>, "client_secret": <Registered application generated secret>, "scope": "https://graph.microsoft.com/.default", "grant_type": "client_credentials"
Response:
{"token_type" : "", "expires_in" : "", "ext_expires_in" : "", "access_token" : ""}
NOTE: Registered application within b2c tenant should have required grants to access Graph API
Pass token to access API resources, ex: to get user details with objectId,
GET
URL: https://graph.microsoft.com/v1.0/users/{objectId}
set header key
"Authorization": "Bearer " ++ {access_token from above step 1.>

Related

OAUTH / Azure Functions: Method to auth AAD user for endpoints that don't support service principals

I've been leveraging Azure Function Apps to automate items in Azure. I currently have working functions that connect to Microsoft Graph, Resource Explorer, KV etc. using service principal / OAUTH client credentials flow (inside the function app). To call my function app, I've implemented implicit flow. While I'm not an expert at OAUTH, I am familiar enough now to get this configured and working.
However, there are Azure endpoints I need to use that don't support using a service principal token, they only support an actual AAD user requesting a token. Here's one that I want to run: Create synchronizationJob
If you look at the permissions section of the above link, you'll see that "application" is not supported. I did test this in a function: I can run these endpoints in Graph Explorer fine (as myself), but they fail in the function when using a token linked to a service principal.
Since this new automation is going to be an Azure Function (and not an interactive user), I can't use the authorization code flow. I need this service account's OAUTH to be non-interactive.
TL;DR
I can run the above endpoint in Azure's Graph Explorer just fine:
Azure Graph Explorer
since I'm authenticating as myself, and have a token generated based on my user ID. But for automating using Azure Functions where I need to use this endpoint (which doesn't support OAUTH using an SP), I need some way to have a back-end AAD user auth and pull a token that can be used to run the endpoint.
Any help is welcome! Feel free to tell me that I'm either missing something very basic, or not understanding a core principal here.
As juunas mentioned no guarantee that will work though, I test in my side and it seems doesn't work although I assigned "Global administrator" role to the service principal.
For your situation, you can request the access token in your function code and then use the access token to request the graph api.
Add the code like below in your function to get access token.
HttpClient client = new HttpClient();
var values = new Dictionary<string, string>
{
{ "client_id", "<your app client id>" },
{ "scope", "<scope>" },
{ "username", "<your user name>" },
{ "password", "<your password>" },
{ "grant_type", "password" },
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://login.microsoftonline.com/<your tenant id>/oauth2/v2.0/token", content);
var responseString = await response.Content.ReadAsStringAsync();
var obj = JObject.Parse(responseString);
var accessToken = (string)obj["access_token"];
And then use the access token got above to request graph api.

B2B users cannot sign in to Tenant using v2.0 endpoint & MSAL Auth flow

I am trying to create a B2B Management portal. I've started off with this sample since it uses MSAL and Graph API.
user#live.se is in the tenant. It's been invited as a "guest user", i.e a B2B user. However, signing in with user#live.se does not work even though it's been added to the tenant. Following error after sign-in:
AADSTS50020: User account 'user#live.se' from external identity provider 'live.com' is not supported for api version '2.0'. Microsoft account pass-thru users and guests are not supported by the tenant-independent endpoint. Trace ID: 2ad8bee0-d00a-4896-9907-b5271a113300 Correlation ID: 0ea84617-4aa1-4830-859f-6f418252765e Timestamp: 2017-10-03 15:35:22Z
I changed the authority (from common) to only allow users from my tenant (requirement):
https://login.microsoftonline.com/tenant.onmicrosoft.com/v2.0
Do guests not count as part of my tenant when using MSAL? that would mean I have to use "old" tech, i.e ADAL and AAD Graph, which is not recommended, and feels kinda lame.
If you pass the specific tenant value in the authority, then
Only users with a work or school account from a specific Azure AD tenant can sign in to the application. Either the friendly domain name of the Azure AD tenant or the tenant's GUID identifier can be used.
That's means the Microsoft Account is not supported in this scenario. Refer here for the Microsoft Account and Work or school accounts. And in this scenario, if you new a user user from other tenant, it should also works.
You can refer the document for tenant from link below:
Fetch the OpenID Connect metadata document
I know this is an old thread but just in case anyone stumbles upon it, here is a solution:
In cases of Personal guest accounts, use Credential Grant Flow (Get access without a user).
To do that, you would first need to grant appropriate permission (of Type Application) for the API you wanted to use on behalf of the signing user. This would let you acquire access token with the application's identity itself rather than the signed in user.
Next get token like this (in this sample, I'm getting access token for Graph API):
public async Task<string> GetAccessToken()
{
using (HttpClient httpClient = new HttpClient())
{
string token = "";
try
{
httpClient.BaseAddress = new Uri($"https://login.microsoftonline.com/{tenantId}");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
HttpRequestMessage request = new HttpRequestMessage();
List<KeyValuePair<string, string>> body = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("scope", "https://graph.microsoft.com/.default"),
new KeyValuePair<string, string>("client_secret", appSecret),
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
request.Method = HttpMethod.Post;
request.RequestUri = new Uri($"{httpClient.BaseAddress}/oauth2/v2.0/token");
request.Content = new FormUrlEncodedContent(body);
var response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsAsync<dynamic>();
token = content.access_token;
}
catch (Exception e)
{
}
return token;
}
}
Tip: If your goal is also Graph API, don't try to get logged in user info by using the /me endpoint in this case. Since the token was generated using the application identity rather than the signed in user, /me would be the application not the logged in user. What you want to do is: retrieve logged in user id from the Claim (Type: http://schemas.microsoft.com/identity/claims/objectidentifier) and use the /user/{userid} endpoint.
I found: for personal accounts (Get access without a user) in the body of the request you must to use grant_type = 'client_credentials' and for corporate accounts to use grant_type = 'authorization_code'

Using Oauth to protect WebAPI with Azure active directory

I have browsed all the tutorials regarding using Oauth to protect WebAPI in Azure active directory online. But unfortunately, none of them can work.
I am using VS 2017 and my project is .net core.
So far what I have tried is:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
ervices.AddAuthentication(); // -----------> newly added
}
In "Configure", I added:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
});
Here is my config:
"AzureAd": {
"AadInstance": "https://login.microsoftonline.com/{0}",
"Tenant": "tenantname.onmicrosoft.com",
"Audience": "https://tenantname.onmicrosoft.com/webapiservice"
}
I have registered this "webapiservice" (link is: http://webapiservice.azurewebsites.net) on my AAD.
Also, to access this web api service, I created a webapi client "webapiclient" which is also a web api and also registered it on my AAD and requested permission to access "webapiservice". The webapi client link is: http://webapiclient.azurewebsites.net
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://webapiservice.azurewebsites.net/");
//is this uri correct? should it be the link of webapi service or the one of webapi client?
HttpResponseMessage response = client.GetAsync("api/values").Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsAsync<IEnumerable<string>>().Result;
return result;
}
else
{
return new string[] { "Something wrong" };
}
So theoretically, I should receive the correct results from webapiservice. but I always received "Something wrong".
Am I missing anything here?
You need an access token from Azure AD.
There are plenty of good example apps on GitHub, here is one for a Daemon App: https://github.com/Azure-Samples/active-directory-dotnet-daemon/blob/master/TodoListDaemon/Program.cs#L96
AuthenticationResult authResult = await authContext.AcquireTokenAsync(todoListResourceId, clientCredential);
This app fetches an access token with its client id and client secret for an API. You can follow a similar approach in your case. You can just replace todoListResourceId with "https://graph.windows.net/" for Azure AD Graph API, or "https://graph.microsoft.com/" for Microsoft Graph API, for example. That is the identifier for the API that you want a token for.
This is the way it works in AAD. You want access to an API, you ask for that access from AAD. In a successful response you will get back an access token, that you must attach to the HTTP call as a header:
Authorization: Bearer accesstokengoeshere......
Now if you are building a web application, you may instead want to do it a bit differently, as you are now accessing the API as the client app, not the user. If you want to make a delegated call, then you will need to use e.g. the Authorization Code flow, where you show the user a browser, redirect them to the right address, and they get sent back to your app for login.
To call web api protected by azure ad , you should pass this obtained access token in the authorization header using a bearer scheme :
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);

Integrate Azure AD B2C with Xamarin App

My Xamarin App (PCL) calls a Web API as shown in the code below:
AuthenticationResult ar = await new AuthHelper().AcquireTokenSilentAsync();
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(Settings.ApiUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ar.Token);
using (HttpResponseMessage response = await client.GetAsync("api/job"))
{
response.EnsureSuccessStatusCode();
using (HttpContent content = response.Content)
{
string result = await content.ReadAsStringAsync();
return result != null ? JsonConvert.DeserializeObject<ObservableCollection<JobTask>>(result) : null;
}
}
}
My Web API is authorized as follows:
[Authorize(Roles = "Admin,BusinessAdmin")]
I am using Azure AD B2C to obtain the token. I am able to get the user roles from Azure AD Graph. What I am unsure about is how to add the roles that are returned from the Graph query into the AuthenticationResult.Token that gets passed to the Web API.
Azure AD B2C does not currently have first class support for application roles nor groups as claims in the token.
You can request this feature in the Azure AD B2C Feedback Forum
Alternatively, you can implement this yourself via Custom Policies. To do this you would add a step in your User Journey that calls out to the Graph to obtain either of these and adds them as claims to the token. See this article for more info.

Configuring ASP.Net application to support muliti tenant using Azure AD account

I created an ASP.NET MVC application and configured authentication with Azure AD using OpenIDConnect. I created a user in one Azure AD and added the same in another Azure AD with right privilege.
I store the claims returned after the Azure AD authentication, in ADAL cache. I use this claim(token cache)to call various Azure Service Management API.
ClientCredential credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientID"],
ConfigurationManager.AppSettings["ida:Password"]);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's EF DB
AuthenticationContext authContext = new AuthenticationContext(
string.Format(ConfigurationManager.AppSettings["ida:Authority"], organizationId), new ADALTokenCache(signedInUserUniqueName));
AuthenticationResult result = authContext.AcquireTokenSilent(ConfigurationManager.AppSettings["ida:AzureResourceManagerIdentifier"], credential,
new UserIdentifier(signedInUserUniqueName, UserIdentifierType.RequiredDisplayableId));
var token= result.AccessToken;
I have configured my application to support multitenant by adding the following in my Account/SignIn controller/action.
public void SignIn(string directoryName = "common")
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Environment.Add("Authority", string.Format(ConfigurationManager.AppSettings["ida:Authority"] + "OAuth2/Authorize", directoryName));
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
Now, upon successful signin, the claims that are returned, belong to the original Azure AD in which the user is initially registered in. Thus, the claims used to call management api for any other Azure AD, in which the user is also added, does not work and throws exception as "Acquire Token failed to obtain token".
I added the name of the other Azure AD to the variable "directoryName" on runtime. This time the claims obtained worked for both the Azure AD.
How to get the SSO for multitenant application, without explicitly mentioning the Azure AD name while signing-in, which will provide me with the claims that can work for all the Azure AD in which the user is registered.
Kindly suggest.
Thanks in advance,
Rahul
I am not sure what your parameter signedInUserUniqueName is, I often write like this to get accesstoken:
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.windows.net/" + Properties.Settings.Default.TenantID);
ClientCredential credential = new ClientCredential(clientId: Properties.Settings.Default.ClientID, clientSecret: Properties.Settings.Default.ClientSecretKey);
AuthenticationResult result = authenticationContext.AcquireToken(resource: "https://management.core.windows.net/", clientCredential: credential);
var token = result.AccessToken;

Resources