Unable to access Graph API when using Azure AD B2C - azure

I have a test user ID as test#gollahalliauth.onmicrosoft.com (without global admin rights) and I am trying to access Graph API for Azure AD.
Try 1 (Success)
I used Azure AD Graph Explorer, logged in with test#gollahalliauth.onmicrosoft.com and using the API https://graph.windows.net/gollahalliauth.onmicrosoft.com/users/test#gollahalliauth.onmicrosoft.com to get the contents. I was able to do this without any issue.
Try 2 (Fail)
I wrote a Go program with profile edit policy
import (
"crypto/rand"
"encoding/base64"
"fmt"
"golang.org/x/oauth2"
"os"
)
const AuthDomainName string = "https://gollahalliauth.b2clogin.com/gollahalliauth.onmicrosoft.com/oauth2/v2.0"
func main() {
conf := &oauth2.Config{
ClientID: os.Getenv("clientID"),
ClientSecret: os.Getenv("clientSecret"),
RedirectURL: "http://localhost:8080/callback",
Scopes: append([]string{"openid", "profile"}),
Endpoint: oauth2.Endpoint{
AuthURL: AuthDomainName + "/authorize?p=b2c_1_gollahalli_edit",
TokenURL: AuthDomainName + "/token?p=b2c_1_gollahalli_edit",
},
}
// Generate random state
b := make([]byte, 32)
rand.Read(b)
state := base64.StdEncoding.EncodeToString(b)
parms := oauth2.SetAuthURLParam("response_type", "id_token")
url := conf.AuthCodeURL(state, parms)
fmt.Println("AUth URL:",url)
}
This creates an auth URL to get the token. I used the id_token to access the graph API using Authorization: Barer id_token and I get an error as
{
"odata.error": {
"code": "Authentication_ExpiredToken",
"message": {
"lang": "en",
"value": "Your access token has expired. Please renew it before submitting the request."
}
}
}
Try 3 (Fail)
I tried adding User.Read in Azure AD B2C > Applications >
<application name> > Published scopes and used the full scope URL and now I get an error as Error: AADB2C90205: This application does not have sufficient permissions against this web resource to perform the operation.
I am not sure what the problem is here. Any idea as to how to get over this?

The AAD B2C is a specialized instance of AAD. You may consider it as a AAD tenant with some B2C extensions. Note: this is a separate tenant from your organization's main AAD tenant in which you've already created the B2C directory/feature!
You can access the B2C records through the AAD Graph API, in 2 steps:
acquire an AAD Graph token by providing the ClientID and ClientSecret to the AAD endpoint (e.g. https://login.microsoftonline.com/yourtenant.onmicrosoft.com).
connect to the AAD Graph REST endpoint (e.g. https://graph.windows.net/yourtenant.onmicrosoft.com/users?api-version=1.6) with the desired method (GET/POST/PATCH/DELETE), passing it the token obtained in step 1 in the Authentication header of the request.
The best example is probably the user migration tool provided by MS. The AAD B2C configuration is covered here and the sample code can be downloaded from the documentation page or directly from the Github project.
You should take a look to the SendGraphPostRequest method and its friends in B2CGraphClient.cs. The code uses ADAL to get the AAD Graph token, but you can also obtain it directly with REST requests. A simplified version in C# (you'll have to translate it yourself to GO, and maybe replace ADAL if it's not available in GO):
// NOTE: This client uses ADAL v2, not ADAL v4
AuthenticationResult result = aadAuthContext.AcquireToken(Globals.aadGraphResourceId, aadCredential);
HttpClient http = new HttpClient();
string url = Globals.aadGraphEndpoint + tenant + api + "?" + Globals.aadGraphVersion;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
object formatted = JsonConvert.DeserializeObject(error);
throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));
}

Related

MSAL authentication token issues

I have integrated MSAL library in iOS to get the token and send to the our backend server for further use. we are using below code to get the token:
let kClientID = "xxxxxx-xxxx-xxxx-xxxx-xxxxxx"
let kGraphEndpoint = "https://graph.microsoft.com/"
let kAuthority = "https://login.microsoftonline.com/xxxxxx-xxxx-xxxx-xxxx-xxxxxx"
let kScopes: [String] = ["user.read"]
let bundleID = Bundle.main.bundleIdentifier ?? "com.default.test"
let kRedirectUri = "msauth.\(bundleID)://auth"
Aquire Token code:
if let applicationContext = self.applicationContext, let webViewParameters = self.webViewParamaters {
let parameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: webViewParameters)
parameters.promptType = .selectAccount
applicationContext.acquireToken(with: parameters) { (result, error) in
if let error = error {
self.log(text: "Could not acquire token: \(error)")
return
}
guard let result = result else {
self.log(text: "Could not acquire token: No result returned")
return
}
self.token = result.accessToken
// calling graph API to get the name and user id ( Success )
// sending this token to our API backend ( Failure 401 )
}
}
Problem:
When Graph API is called from frontend iOS application after getting token, it is working, and when we are sending same token to backend then it is not working getting 401 error. The same token is not valid for backend application, but this was working Earlier when we were using ADAL Library in iOS application.
Is it because of Redirect URI ?? in ADAL we were using API endpoint as redirect and now we are using "msauth.\(bundleID)://auth" this format.
Please help.
Token you may have received is only for MS Graph API, not your API ,As front-end acquires access token for Microsoft Graph API.
In your front-end you need to specify scopes for your backend API as mentioned by #juunas.
When your application needs to request an access token with specific permissions for a resource API, pass the scopes containing the app ID URI of the API in the format like this-> app ID URI/scope
From MSdocs reference, Some example scope values for different resources:
Microsoft Graph API: https://graph.microsoft.com/User.Read
Custom web API:api://11111111-1111-1111-1111-111111111111/api.read
To set scopes in portal ,
go to the app registration of your API in Azure AD > Expose an API> add a scope.
Azure AD should then give you a token that is meant for your API.
SO reference

AcquireTokenSilent for Multiple WebAPI Access tokens using Azure B2C and MSAL.NET

We have a web application which needs authenticated access to several Web APIs. We are using Azure AD B2C for authentication.
I understand you can not include scopes for both resources in one call.
I understand that MSAL is meant to use a refresh token that was cached from the first resource token, to request an access token for the second resource.
IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scopeForApi2, accounts.FirstOrDefault())
When this call is made, the call doesnt not fail, but the Access Token is null.
Within Azure B2C, the web app, and webapis are registered - and the web app has been granted permission and admin consent to all the scopes from the webapis.
Is this supported using Azure AD B2C and/or MSAL.NET?
We get the first access token for the first resource api successfully:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.Tenant, Globals.DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Globals.ClientId,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claim type that specifies the Name property.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {Globals.ReadTasksScopeApi1} {Globals.WriteTasksScopeApi1}"
}
);
we get the first access token using the authorizationcode - we receive an access token and refresh token
IConfidentialClientApplication confidentialClient
= MsalAppBuilder.BuildConfidentialClientApplication(
new ClaimsPrincipal(notification.AuthenticationTicket.Identity));
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient
.AcquireTokenByAuthorizationCode(Globals.Scopes_Api1, notification.Code)
.ExecuteAsync();
I have also tried adding the "resource" query parameter but it doesnt seem to make any difference:
var scope = new string[] { Globals.ReadTasksScopeApi2 };
IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault())
.WithExtraQueryParameters(new Dictionary<string, string>
{
// { "resource", "https://myb2c.onmicrosoft.com/api2" }, // tried appid and guid
{ "resource", "0a6ab6b5-1b88-49fe-a6cf-19f1878d3508" }
})
.ExecuteAsync();
Why is the Access Token null for the second call to AcquireTokenSilent on a second resource using Azure AD B2C?
2/19/2020
I have confirmed with Azure support engineer that Azure AD B2C doesn't support such scenario.

How to obtain the token returned from Azure AD B2C in ASP Core 2.0?

I have used Visual Studio's latest New Project wizard to create a ASP Core 2.0 Web page (Razor Pages) that uses Individual Accounts as my authentication option. I have created an Azure AD B2C tenant and validated that it works properly.
When I run the web application that was created by the wizard and click Log In in the upper right, it redirects to my Azure AD B2C site, and I can properly login.
After login, the callback url goes to the endpoint configured in my user secrets:
...
"CallbackPath": "/signin-oidc",
...
That all seems to work properly. I understand that the Azure AD B2C portal sends a token back to the above /signin-oidc callback path and stores it.
How can I retrieve the value of that token?
I've been following all of the Azure AD B2C guides, but not all of them have been updated to ASP Core 2.0, and none of them seem to use the code generated from the 15.4 VS wizard such:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAdB2C(options => Configuration.Bind("AzureAdB2C", options))
.AddCookie();
services.AddMvc();
}
Note: the .AddAzureAdB2C(...)
None of the B2C samples are using this so its difficult for me to follow.
My end goal is to get the token and use that in a strongly-typed set of API classes I generated from Swagger using Autorest which require the token.
The best way to do this is outlined in the Azure AD B2C .Net Core sample, specifically the branch for Core 2.0.
In the normal model/flow, your application will get an id_token and an authorization code but not a token. The authorization code needs to be exchanged for a token by your middle tier. This token is what you'd then be sending over to your web API.
The way to do this involves the following:
Ensure your middle tier is requesting id_token+code for your primary policy (you don't want to do this for your edit profile or password reset policies). From the sample's OpenIdConnectOptionsSetup.cs#L77:
public Task OnRedirectToIdentityProvider(RedirectContext context)
{
var defaultPolicy = AzureAdB2COptions.DefaultPolicy;
if (context.Properties.Items.TryGetValue(AzureAdB2COptions.PolicyAuthenticationProperty, out var policy) &&
!policy.Equals(defaultPolicy))
{
context.ProtocolMessage.Scope = OpenIdConnectScope.OpenIdProfile;
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken;
context.ProtocolMessage.IssuerAddress = context.ProtocolMessage.IssuerAddress.ToLower().Replace(defaultPolicy.ToLower(), policy.ToLower());
context.Properties.Items.Remove(AzureAdB2COptions.PolicyAuthenticationProperty);
}
else if (!string.IsNullOrEmpty(AzureAdB2COptions.ApiUrl))
{
context.ProtocolMessage.Scope += $" offline_access {AzureAdB2COptions.ApiScopes}";
// -----------------------------
// THIS IS THE IMPORTANT PART:
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.CodeIdToken;
// -----------------------------
}
return Task.FromResult(0);
}
Exchange the code for a token. From the sample's OpenIdConnectOptionsSetup.cs#L103-L124:
public async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
// Use MSAL to swap the code for an access token
// Extract the code from the response notification
var code = context.ProtocolMessage.Code;
string signedInUserID = context.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, context.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(AzureAdB2COptions.ClientId, AzureAdB2COptions.Authority, AzureAdB2COptions.RedirectUri, new ClientCredential(AzureAdB2COptions.ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, AzureAdB2COptions.ApiScopes.Split(' '));
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
You can then use this token elsewhere in your code to call an API. From the sample's HomeController.cs#L45-L57:
var scope = AzureAdB2COptions.ApiScopes.Split(' ');
string signedInUserID = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, this.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(AzureAdB2COptions.ClientId, AzureAdB2COptions.Authority, AzureAdB2COptions.RedirectUri, new ClientCredential(AzureAdB2COptions.ClientSecret), userTokenCache, null);
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scope, cca.Users.FirstOrDefault(), AzureAdB2COptions.Authority, false);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AzureAdB2COptions.ApiUrl);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
The ASP.NET Core team has created 2 excellent documents on this process.
Cloud authentication with Azure Active Directory B2C in ASP.NET Core
Use Visual Studio to create an ASP.NET Core web app configured to use the Azure AD B2C tenant for authentication
Cloud authentication in web APIs with Azure Active Directory B2C in ASP.NET Core
Use Visual Studio to create a Web API configured to use the Azure AD B2C tenant for authentication

Resource not found for the segment 'me'

i'm using Graph API to retrieve profile information of user who's currently logged in from the Azure AD, unfortunately i'm receiving the following error message :
{"odata.error":{"code":"Request_ResourceNotFound","message":{"lang":"en","value":"Resource not found for the segment 'me'."}}}
Below is my code :
Uri serviceRoot = new Uri(serviceRootURL);
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot,
async () => await GetAppTokenAsync());
var user = (User)await adClient.Me
.Expand(x => x.Manager)
.ExecuteAsync();
And below is my code for GetAppTokenAsync() :
private static async Task<string> GetAppTokenAsync()
{
// Instantiate an AuthenticationContext for my directory (see authString above).
AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
// Create a ClientCredential that will be used for authentication.
// This is where the Client ID and Key/Secret from the Azure Management Portal is used.
ClientCredential clientCred = new ClientCredential(clientID, clientSecret);
// Acquire an access token from Azure AD to access the Azure AD Graph (the resource)
// using the Client ID and Key/Secret as credentials.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(resAzureGraphAPI, clientCred);
// Return the access token.
return authenticationResult.AccessToken;
}
From your code "await GetAppTokenAsync()" , you are getting an app-only token , which using application identity, instead of as a user's identity .
The "(User)await adClient.Me" won't work if that token is not associated with a user .
To use app token to get user manager information ,you need to specify the user you want to query , code below is for your reference :
try
{
User manager = (User)await adClient.Users.GetByObjectId("5eba8883-c258-45d0-8add-a286a1ec1e91").Manager.ExecuteAsync();
}
catch (Exception ex)
{
throw;
}
Update
You could use authorization code flow for delegated permissions(user's identity) . If you want a client library code sample , you could refer to this code sample . After user sign in , you could use below code to get manager of current login user :
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
User manager = (User)await client.Me.Manager.ExecuteAsync();
I used an application identity with the legacy Azure Active Directory api and the 'Application.ReadWrite.OwnedBy' permission to work around the Resource not found for the segment 'me' error. The same permission exists in the Microsoft Graph api, but the behavior is not identical. More information here.

Is it possible to operate OneNote with azure daemon App?

In order to operate OneNote with azure's daemon app,
I created a new ClientID, acquired the Access Token by user authentication with that ClientID, and realized access to the OneNote API using it.
However, instead of user authentication, Access token is acquired by ClientID and certificate, and access to OneNote API using it is refused.(401 Unauthorized)
How can I operate OneNote from azure dameon App?
The way I tried
The AccessToken creation by the certificate was implemented with reference to the following.
https://azure.microsoft.com/ja-jp/resources/samples/active-directory-dotnet-daemon-certificate-credential/
Specific AccessToken acquisition codes are as follows,
public async Task AuthWithCertAsync(string tenant, string clientID, string certName)
{
var authority = $"{aadInstance}{tenant}";
var authContext = new AuthenticationContext(authority);
//refer: above URL
ClientAssertionCertificate certCred = GetCertificate(clientID, certName);
if (certCred == null) {return false;}
//"https://graph.microsoft.com/";
var graphResult = await authContext.AcquireTokenAsync(graphResourceID, certCred);
graphToken = graphResult.AccessToken;
//"https://www.onenote.com/";
var onenoteResult = await authContext.AcquireTokenAsync(onenoteResourceID, certCred);
onenoteToken = onenoteResult.AccessToken;
}
With this graphToken, access to the Graph API succeeds.
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {graphToken}");
//e.g. "https://graph.microsoft.com/v1.0/groups", "https://graph.microsoft.com/v1.0/users"
var response = await client.GetStringAsync(url);
...
}
However, if the target URL is an API on onenote, it fails.
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {graphToken}");
//e.g:"https://graph.microsoft.com/beta/users/{userID}/notes/notebooks"
// Occured HttpRequestException(401 Unauthorized)
var response = await client.GetStringAsync(url);
...
}
This request returns HTTP 401 Unauthorized status.
Also when accessing OneNote API on onenoteToken failed.
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {onenoteToken}");
//e.g.:"https://www.onenote.com/api/v1.0/users/{userID}/notes/notebooks"
var response = await client.GetStringAsync(url);
return response;
}
This request also returns HTTP 401 Unauthorized status.
The application setting in Azure Active Directory:
Type:
WEB APPLICATION AND/OR WEB API
Multi Tenant:
ON
permissions to other applications:
Graph, OneNote, Active Directory, SharePoint :Application Permissions all checked.
In the admin account of the target tenant, the following admin consent URL is accessed and accepted.
https://login.microsoftonline.com/common/adminconsent?client_id={clientID}&state={state}&redirect_uri={redirectUrl}
Update
According to the answer of https://stackoverflow.com/a/41890179/1411521,
I understood that there is no way to access OneNote by daemon App with the current Graph API. (at 2017-1-31)
However, Application Permission of OneNote API can set as follows.
View and modify notes for all users
View notes for all users
Despite the fact that they are valid, what causes the authentication error (401 Unauthorized) with the following code?
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {onenoteToken}");
//e.g.:"https://www.onenote.com/api/v1.0/users/{userID}/notes/notebooks"
var response = await client.GetStringAsync(url); // Occured HttpRequestException(401 Unauthorized)
...
}
You were mixing the Microsoft Graph and OneNote API.
The token you were acquire is for the Microsoft Graph REST, and you can manipulate the OnenNote through Microsoft Graph REST which in beta version by following the document here(beta reference->OneNote).
And if you want to use the OneNoe API, you can refer the document here for the authentication.
Update
To list the notebooks, we need permissions like Notes.Read, Notes.ReadWrite.CreatedByApp, Notes.ReadWrite, Notes.Read.All, or Notes.ReadWrite.All. However there is no such kinds of permission for the Client Credential flow for Microsoft Graph.
If you want the Microsoft Graph to support the Client Credential flow to manipulate the OneNote, you can submit the feedback from here.
This problem was solved today(2017-2-10).
The OneNote REST API now supports application-level permissions
OneNote authentication and Azure AD application permissions

Resources