Azure AD Bearer Token has wrong "aud" claims - azure-active-directory

I am trying to use AAD delegated permission Bearer tokens for a Visio VSTO addin to create SharePoint Online pages using CSOM. Initially I was able to get this working entering username / password following Modern Authentication with CSOM for Net Standard
However, I would like for the user to select an existing AAD account. When I attempt to use the following code the Bearer token "aud" claim is consistently set to "00000003-0000-0000-c000-000000000000" which is the Graph API. Whilst a ClientContext object is returned I am getting a HTTP 401 Unauthorized error when performing a page lookup.
The code is as follows
//
// Get Client App
//
var ClientApp = (PublicClientApplication)PublicClientApplicationBuilder.Create(<AAD App ID>)
.WithDefaultRedirectUri()
.WithTenantId(<AAD Tenant ID>)
.WithAuthority(AzureCloudInstance.AzurePublic, <AAD Tenant ID>)
.Build();
//
// Prompt for user to select preferred AAD account
// The returned JWT Bearer Token "aud" claim is 00000003-0000-0000-c000-000000000000
//
var Token = ClientApp.AcquireTokenInteractive(scopes)
.WithPrompt(Prompt.SelectAccount)
.WithParentActivityOrWindow(GetActiveWindow())
.ExecuteAsync()
.GetAwaiter()
.GetResult();
//
// Get client Context
//
var ClientContext = AuthenticationManager.GetAzureADAccessTokenAuthenticatedContext(<SharePoint Site URL>, Token.AccessToken);
//
// Using the Client Context to query the Site results in HTTP 401
//
ClientContext.Load(ClientContext.Web, p => p.Title, t => t.Description);
ClientContext.ExecuteQuery();
Looking at the code for the AuthenticationManager class in the above link I can see that the AAD Bearer request is passing the following resource request parameter to the SharePoint online URL:
var body = $"resource={resource}&client_id={clientId}&grant_type=password&username={HttpUtility.UrlEncode(username)}&password={HttpUtility.UrlEncode(password)}";
So it seems that AAD is setting the Bearer token "aud" claim based upon this parameter. However, when I try and add this parameter using 'WithExtraQueryParameters' I am getting the following error: "AADSTS901002: The 'resource' request parameter is not supported"

Ok, I figured out the problem. The scope needs to be prefixed with the resource:
string[] scopes = { "https://<domain>.sharepoint.com/AllSites.Write", "user.read" }
Then retrieve the token
this.Token = await ClientApp.AcquireTokenInteractive(scopes)
.WithPrompt(Prompt.SelectAccount)
.WithParentActivityOrWindow(GetActiveWindow())
.ExecuteAsync();

Related

Client - API authentication flow with Microsoft Azure AD

I'm building a react frontend application with a spring backend that is secured with azure ad.
I can't get the authentication flow to work.
In azure ad, I have registered 2 applictions:
API: Default configurations and under "Expose an API" I have added a scope with api://xxxx-api/Access.Api and also added the client application. Under "App Roles" I have added the roles "User" and "Admin". I have assignes both roles to myself.
Client: Registered as SPA with redirect to http://localhost:3000 where the react app is running. Did not check the two boxes for the token to enable PKCE. Under "API permissions" I added the "Access.Api" scope from the api app and granted admin consent.
In the react app I'm using #azure/msal-browser and #azure/msal-react.
My authConfig looks like this:
Then I'm just using useMsalAuthentication(InteractionType.Popup); to sign the user in.
All this works as expected and I'm getting a token back. If I parse this token in jwt.io,
I get "iss": "https://sts.windows.net/42xxxxx-xxxxx-xxxxxx-xxxxx/",
"scp": "openid profile User.Read email", "ver": "1.0",.
However, I do not see the scopes or roles for my API app.
I'm then using an Axios request interceptor to provide the bearer token on every API request:
const { instance, accounts } = useMsal();
const account = useAccount(accounts[0]);
axios.interceptors.request.use(async (config) => {
if (!account) {
throw Error('No active account! Verify a user has been signed in.');
}
const response = await instance.acquireTokenSilent({
...loginRequest,
account,
});
config.headers.Authorization = `Bearer ${response.accessToken}`;
return config;
});
The token is successfully added to the header of each request.
My spring application however fails to validate this token.
My spring config:
I could implement the token validation myself if that is an issue here, but how do I fix, that the bearer token does not contain the roles that I need to check if the user has access to specific resources on the api?
I figured it out.
Basically the scopes openid, email and profile should only be used when requesting an ID token. The ID token contains all roles exposed in the client app.
If these scopes are used when requesting an access token, the token will contain no roles or scopes at all. Only use the scope from the api app that is exposed in the client app when requesting an access token, and the roles will show up in the access token.

How to obtain an Azure B2C bearer token for a non-interactive/daemon application and get it validated in an Azure HTTP-triggered function

There is a C# application under development that is supposed to be a part of a bigger backend application to process some data. This application is supposed to obtain a token from Azure AD B2C and send it to an HTTP-triggered function where it is supposed to be validated by the following code:
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
$"{_authenticationSettings.Authority}/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var config = await configManager.GetConfigurationAsync();
_validationParameters = new TokenValidationParameters
{
IssuerSigningKeys = config.SigningKeys,
ValidateAudience = true,
// Audience MUST be the app ID aka clientId
ValidAudience = _authenticationSettings.ClientId,
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateLifetime = true
};
var tokenHandler = new JwtSecurityTokenHandler();
var result = tokenHandler.ValidateToken(authHeader.Parameter, _validationParameters, out var jwtToken);
First, we thought that obtaining an access token from Microsoft Graph API using MSAL would help us but the C# code above threw an invalid signature exception which we discovered makes sense due to this GitHub post. Apparently, we need to obtain an id_token instead in the application and send it to the HTTP-triggered function for validation by the code snippet above.
The application cannot obtain the id_token because it's not supposed to launch Azure AD B2C's login UI to have a user sign-in and redirect it through a URL. What is the solution to this problem so that the application would obtain a token without a UI and send that to the http-triggered function for validation?
Obtaining a token for the AAD B2C tenant without UI is possible in two ways and you should probably pick one depending on what exactly you want to achieve:
user token - by using Resource Owner Password Credentials flow - https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-ropc-policy. This flow is deprecated though and mentioned usually in legacy application context
server-side application token - by using Client Cretendial flow - this on the other hand requires using requests specific for AAD but with AAD B2C tenant - https://learn.microsoft.com/en-us/azure/active-directory-b2c/application-types#daemonsserver-side-applications
I'm also not quite sure why should you use id_token for that. If the application needs to authorize the request to the function with the token then it should be an access token regardless of how the token is retrieved (interactive UI or not).

On behalf of token issue (AADSTS50013: Assertion contains an invalid signature)

I'm getting an error (mentioned below) when I'm trying to use Cortana Bot user token (which is a Graph token) to generate an "on-behalf-of" token to another consuming Web API application using ClientAssertionCertificate / ClientCredential targeted to another consuming Web API by passing its AppId as ResourceId and userAssertion generated by using Cortana Bot user token.
When checked our Bot AAD settings it is configured with other consuming Web API (API B) as valid application along with Graph application. Do we need to do any additional setting in AAD to get this on-behalf-of token?
AADSTS50013: Assertion contains an invalid signature.
[Reason - The provided signature value did not match the expected signature value.,
Thumbprint of key used by client: '9DB0B05B5D70DD7901FB151A5F029148B8CC1C64',
Found key 'Start=11/11/2018 00:00:00,
End=11/11/2020 00:00:00'
]
Trace ID: a440869f-b8f5-4d87-ba1a-6bd8dd7ba200
Correlation ID: 651e1fa8-2069-4489-a687-e68e5206e193
Timestamp: 2019-01-02 07:14:45Z
Following is the flow and Sample code how we are trying to get an on-behalf-of token for other consuming Web API (API B).
Flow Steps:
Cortana asks for user Sign-in
User Sign-in to Cortana
Cortana sends this user token (generated targeting to use https://graph.microsoft.com as Audience) to Microsoft Bot Framework API
Microsoft Bot Framework API validates and wants to consume this token for calling other Web API (which is called API B).
As this Cortana user token cannot be used directly, it needs to be generated as an on-behalf-of token to API B from Microsoft Bot Framework API.
Following is the code sample used to generate an on-behalf-of token from Microsoft Bot Framework API:
public async Task<string> GetOnBehalfOfTokenAsync(string authority, string resource, string scope = "", string token = "")
{
AuthenticationResult output;
var clientId = ConfigurationManager.AppSettings["API-B-ClientId"];
// Read certificate which can be used for getting token to API B using ClientAssertionCertificate
// GetCert() is used to get the Certificate based on Thumbprint configured in Web.config file.
var certificate = this.GetCert();
// 'authority' is https://login.microsoftonline.com/{tenant id}
var authContext = new AuthenticationContext(authority);
var cllientCertificateCredential = new ClientAssertionCertificate(clientId, certificate);
// 'token' is the user token which was received from Cortana.
var userAssertion = (!string.IsNullOrWhiteSpace(token)) ?
new UserAssertion(token, "urn:ietf:params:oauth:grant-type:jwt-bearer",
TokenHelper.ExtractUserInfoFromAuthToken(token, "upn")) : null;
try
{
// 'resource' is the Resource Id of API B
// if UserAssertion is null then get token with ClientAssertionCertificate else get
// on-behalf-of token using UserAssertion and ClientAssertionCertificate
if (userAssertion == null)
{
output = await authContext
.AcquireTokenAsync(resource, cllientCertificateCredential)
.ConfigureAwait(false);
}
else
{
output = await authContext
.AcquireTokenAsync(resource, cllientCertificateCredential, userAssertion)
.ConfigureAwait(false);
}
}
catch (Exception ex)
{
logger.log("Error acquiring the AAD authentication token", ex);
}
return output.AccessToken;
}
Getting an exception which was mentioned above at this step:
output = await authContext
.AcquireTokenAsync(resource, cllientCertificateCredential, userAssertion)
.ConfigureAwait(false);
My understanding is: first you get user token from your Cortana access ms graph API; and then you want to use the user token to generate the OBO token in Microsoft Bot Framework API; final, you want to use the OBO token to access API B from Microsoft Bot Framework API.
You want to get OBO token in Microsoft Bot Framework API, you should use the API id and the secret, for this, I have never tried this.
On my side, I use v1 endpoint, I create two API (API A and B) and my flow is:
First, my app requests token1 for API A;
Next, use the token1 to request OBO token2 for API B from API A;
Final, use the OBO token2 to request OBO token3 for aad graph API from API B.
For the OBO in v1 endpoint, please read link1.
For the OBO in v2 endpoint, please read link2.
We could able to resolve this issue by configuring our dependent custom API (API B) "user_impersonation" scope to Cortana channel configuration to our Bot. With this configuration change, we do not need to generate On-Behalf-Of token to API B from our Microsoft Bot application.
Thanks to all who has supported to provide solutions for this thread...

Calling Microsoft Graph via REST API using Application token

I'm trying to call Microsoft Graph using REST API, and I'm having some problems. My app will eventually be a web app deployed to Azure, and I need to call Graph via REST without a logged-in user.
In trying to debug this, I tried to make the simplest app that I could. This app is just trying to read a user's profile from Azure Active Directory using Graph.
I registered my app in AAD, so I have a tenant, client ID and a client secret. At this point, I've given it every permission under the AAD and Graph APIs (for testing purposes). I'm able to get an token from AAD, but when I call the Graph API with this token, I get 401 - Unauthorized, with Access Token Validation Error.
I've searched for this error, and haven't found anything that seems to apply.
EDIT: I've also Granted the permissions to my app after added the permissions.
I've grabbed pieces from various samples in an attempt to get this working. Here's the code:
var tenant = "tenant ID";
var clientID = "app ID";
// I've tried graph.microsoft.com and graph.microsoft.com/.default
var resource = "https://graph.microsoft.com/user.read.all";
var secret = "client secret";
string token;
using(var webClient = new WebClient())
{
var requestParameters = new NameValueCollection();
requestParameters.Add("scope", resource);
requestParameters.Add("client_id", clientID);
requestParameters.Add("grant_type", "client_credentials");
requestParameters.Add("client_secret", secret);
var url = $"https://login.microsoftonline.com/{tenant}/oauth2/token";
var responseBytes = await webClient.UploadValuesTaskAsync(url, "POST", requestParameters);
var responseBody = Encoding.UTF8.GetString(responseBytes);
var jsonObject = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(responseBody);
token = jsonObject.Value<string>("access_token");
}
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(new Uri("https://graph.microsoft.com/v1.0/user/<user ID>"));
That second call comes back with the 401 - Unauthorized.
Does anyone see anything that I'm doing wrong, or that I should check?
Thanks!
EDIT: Here's the JSON from the token, decoded with Fiddler with tenant and client IDs removed:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "HHByKU-0DqAqMZh6ZFPd2VWaOtg",
"kid": "HHByKU-0DqAqMZh6ZFPd2VWaOtg"
}
{
"aud": "spn:00000002-0000-0000-c000-000000000000",
"iss": "https://sts.windows.net/<tenant id>/",
"iat": 1507313785,
"nbf": 1507313785,
"exp": 1507317685,
"aio": "Y2VgYCguP8H/lUPs5seMpgeOze3vAA==",
"appid": "~client id~",
"appidacr": "1",
"idp": "https://sts.windows.net/~tenant id~/",
"oid": "37df6326-7ed1-4b88-a57c-b82319a5cb07",
"sub": "37df6326-7ed1-4b88-a57c-b82319a5cb07",
"tenant_region_scope": "NA",
"tid": "~tenant id~",
"uti": "IspHJJNqjUKWjvsWmXhDAA",
"ver": "1.0"
}
After looking at the code sample and access token you edited in to your question, I can point out a few problems:
The access token you are getting is not valid for calling https://graph.microsoft.com. Note that the aud claim in the token has the app id 00000002-0000-0000-c000-000000000000 which is the app id for the AAD Graph API, which is a similar but different endpoint.
The token you have does not have any role claims, which is what is used for granting access to a client app trying to call in the App Only flow. This is probably a result of the first problem, fixing your resource should also then populate the role claims.
When looking at your code to see where the resource problem is occurring, we can see that you are slightly mixing up the V1 and V2 authentication procedures.
Your code seems to want to follow the V1 procedure, so you need to make sure your POST request to get a token has the following parameters:
grant_type, client_id, client_secret, resource.
In your code sample, you seem to have added a scope parameter rather than a resource parameter. This is consistent with the V2 method, where we support dynamic consent, where you can define the scopes you want right when you are getting a token, but this does not work with the V1 endpoint.
Instead, you should update these two lines in your code:
var resource = "https://graph.microsoft.com";
and
requestParameters.Add("resource", resource);
Let me know if this helps.
I believe all you need to do here is click "Grant Permissions" in the required permissions menu.
Basically, adding permissions to your App is not good enough. Your application must also have a user consent to give it access to call the Graph API using App Only permissions. The Grant Permissions button is one way to grant consent to your application. The other method would be to simply sign into the application, which will prompt the user to consent.
Let me know if this helps!

AADSTS50013: Assertion audience claim does not match the required value

I've got a single page app that authenticates users in Azure using adal-angular.js/adal.js [client].
The returned token is inserted into the auth header and passed to a web API [server]. This web api generates a new access token for the app using the on-behalf-of workflow (https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof)
This token is then used to call a downstream API [API1].
So the downstream API then repeats this process to get a new token to call another API [API2]. It's at this point that I'm getting the above error.
The aud value in the token passed from [client] to [server] is the application id of the [server] app.
The aud value in the token passed from the [server] to [API1] is the Application URI of the [API1] app.
So far so good.
When I call AcquireTokenAsync in [API1] app, I get the following error:
AADSTS70002: Error validating credentials. AADSTS50013: Assertion audience claim does not match the required value. The audience in the assertion was 'http://application_uri.com/' and the expected audience is 'snip-a1d5-e82e84f4e19e' or one of the Application Uris of this application with App ID 'snip-a1d5-e82e84f4e19e'
The relevant code from [API1]:
public static async Task<string> GetApplicationTokenOnBehalfOfUser(string appId, string appKey)
{
var clientCredential = new ClientCredential(appId, appKey);
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as
System.IdentityModel.Tokens.BootstrapContext;
var userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
var userAccessToken = bootstrapContext.Token;
var userAssertion = new UserAssertion(userAccessToken, _assertionType, userName);
var authority = string.Format(System.Globalization.CultureInfo.InvariantCulture, _aadInstance, _tenant);
var userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var authContext = new AuthenticationContext(authority, new TokenCache());
var result = await authContext.AcquireTokenAsync(_resourceId, clientCredential, userAssertion);
var accessToken = result.AccessToken;
return accessToken;
}
Where:
appId = "snip-a1d5-e82e84f4e19e"
And the "aud" value from the BootstrapContext.Token is:
"aud": "http://application_uri.com/"
If I change the above to use the "aud" value from the token as the appId in the ClientCredential, I get this error instead:
AADSTS65001: The user or administrator has not consented to use the application with ID 'http://application_uri.com/'. Send an interactive authorization request for this user and resource.
Am I doing this right?
Thanks.
AADSTS70002: Error validating credentials. AADSTS50013: Assertion audience claim does not match the required value. The audience in the assertion was 'http://application_uri.com/' and the expected audience is 'snip-a1d5-e82e84f4e19e' or one of the Application Uris of this application with App ID 'snip-a1d5-e82e84f4e19e'
To use the on-behalf-of flow, we need to provide the access token for the API1 and provide the clientId and secrect of API1 to request the access token for the API2.
AADSTS65001: The user or administrator has not consented to use the application with ID 'http://application_uri.com/'. Send an interactive authorization request for this user and resource.
Before that tenant users can use the app, the corresponding service principal must be register to that tenant first by permission grant. Is API2 is not in the tenant of the users sign-in?
If I understand correctly, we need to specify the knownClientApplications in the manifest of API1(http://application_uri.com/') with the client_id of your SPA, and it also require to set the permission of API1 to the SPA. After that, when the users sign-in your SPA, the API1 app will also register to the users' tenant.
More detail about multi-tier applications please refer the document below:
How to sign in any Azure Active Directory (AD) user using the multi-tenant application pattern
Update( append the test result to explain)
To get this working I had to add the following delegated permissions to API1 for AP2.
Azure Permissions

Resources