Web API on-behalf-of adal id_token error - c#

I have created a Web API in Azure.
This Web API makes some calls in SharePoint Online. Some of the api calls are on-behalf-of.
This Web API works fine until 01.05.2018 - and it works fine on old app services, which were created before 01.05.2018.
A microsoft staff member said:
As part of our security hardening efforts we do not allow id_token
redemption for any application created after 2018-05-01 00:00:00.
During the log in process of adal, I got the id_token. The id_token has got the same value as the access_token:
When I call the web api, I will send this token as bearer token.
The Web API takes this token (string accessToken) and starts the method 'AcquireTokenAsync':
var clientID = ConfigurationManager.AppSettings["ClientID"];
var clientSecret = ConfigurationManager.AppSettings["ClientSecret"];
var tenant = ConfigurationManager.AppSettings["Tenant"];
var appCred = new ClientCredential(clientID, clientSecret);
var authContext = new AuthenticationContext(
"https://login.microsoftonline.com/" + tenant);
var resource = new Uri(sharePointUrl).GetLeftPart(UriPartial.Authority);
var authResult = await authContext.AcquireTokenAsync(resource, appCred,
new UserAssertion(accessToken));
return authResult.AccessToken;
But in the line which calls 'AcquireTokenAsync' I have got the error message:
AADSTS240002: Input id_token cannot be used as 'urn:ietf:params:oauth:grant-type:jwt-bearer' grant
But where is the problem?

The problem is that you use the same application identity in the front-end and back-end, and MS does not allow you to use the Id token (which you use as an access token here because of the former) to get another access token.
A possible solution:
Register another application (the front-end JS app should be a Native app)
It should acquire an access token for your back-end API using either the API's client id or app Id URI as the resource
Then the API can exchange the access token for another access token
If this is a multi-tenant app, the migration is probably not going to be easy.
If it's single-tenant, then all should be possible.
Your front-end app should of course require permission to call your back-end API in Azure AD, and that permission should be granted.
Another solution would be to acquire the other access token in the front-end using ADAL.JS instead of using on-behalf-of in the back-end and attaching that to all API requests in addition to the Id token.

Related

How to get access token to call MS Graph on behalf of a user in the console app using MSAL?

I have a SPA application that communicates with my backend Web API using AAD v2 authentication. Now I'm developing a console app to call Microsoft Graph on behalf of the user signed into the SPA app.
I have a valid access token of the user (used to call backend Web API). I want to use this access token to request a new token for accessing MS Graph.
Here is the code of the console app for requesting a new access token with MS Graph scopes using MSAL.NET:
string clientId = "<clientId>";
string clientSecret = "<clientSecret>";
string accessToken = "<validAccessTokenForWebApi>";
string assertionType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string[] scopes = new string[] { "User.Read", "Mail.Send" };
string graphAccessToken = null;
try
{
var app = ConfidentialClientApplicationBuilder
.Create(clientId).WithClientSecret(clientSecret).Build();
var userAssertion = new UserAssertion(accessToken, assertionType);
var result = app.AcquireTokenOnBehalfOf(scopes, userAssertion)
.ExecuteAsync().GetAwaiter().GetResult();
graphAccessToken = result.AccessToken;
}
catch (MsalServiceException ex)
{
throw;
}
But when I call app.AcquireTokenOnBehalfOf() I get an exception:
AADSTS50013: Assertion failed signature validation. [Reason - The provided signature value did not match the expected signature value., Thumbprint of key used by client: 'BB839F3453C7C04068B078EDADAB8E6D5F382E76', Found key 'Start=06/04/2019 00:00:00, End=06/04/2021 00:00:00']
What is the reason? What is the right way of getting access token on behalf of a user?
UPDATE - why do I need console app?
I could call Graph API directly from my backend API, but some actions may be delayed by the user (e.g. send mail using Graph API after 30 minutes). That is why I need to do this using the console app that runs on schedule.
If you want to use OAuth 2.0 On-Behalf-Of flow, I think you do not need to develop a console application to call graph api. You can directly use your backend Web API application to acquire access token then call Microsoft Graph. According to my understanding, you just do these steps
Sign-in the user in the client application
Acquire a token to the Web API (TodoListService) and call
it.
The Web API then calls another downstream Web API (The Microsoft
Graph).
For more details, please refer to the sample.
Regarding how to get access token with on behalf flow in the console application, The detailed steps are as below.
Register the web api app
Register APP
Create Client secrets
Configure permissions to access Graph API
Configure an application to expose web APIs(Add scope for the api)
Register the SAP app
Register APP
Create Client secrets
Configure permissions to access web API
Configure known client applications for web API application
In the Azure portal, navigate to your Web api
app registration and click on the Manifest section.
Find the property knownClientApplications and add the Client IDs of the SAP applications
Get access token to call web api
GET https://login.microsoftonline.com/common/oauth2/v2.0/authorize
?scope=<you web api scope> openid
&redirect_uri=<your sap app redirect url>
&nonce=test123
&client_id=<you sap app client id>
&response_type=id_token token
get access token with on behalf flow
REST API
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&client_id=<you web api client id>
&assertion=<you acess token you get in above steps>
&client_secret=<you app secret>
&scope=https://graph.microsoft.com/user.read
&requested_token_use=on_behalf_of
MSAL.net Code
string[] scopes = { "user.read" };
string accesstoken = "";
string appKey = "yor web api client secret";
string clientId = "your web api application id";
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(appKey)
.Build();
UserAssertion userAssertion = new UserAssertion(accesstoken,
"urn:ietf:params:oauth:grant-type:jwt-bearer");
var result = app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync().Result;
Console.WriteLine(result.AccessToken);

Securing Back end services in Azure API Management using OAuth2 Client Credentials flow

I have deployed a .NET Core Web API and provided access through Azure API Management. I now wish to secure the back end using OAuth2 Client Credentials flow.
I have added Azure AD Authentication as follows:
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options =>
{
Configuration.Bind("AzureAd", options);
});
I have created an App Registration and Client Secret in Azure AD and confirmed it is working by creating a token using the code below and calling the API.
var clientCred = new ClientCredential(clientId, clientSecret);
var result = await authContext.AcquireTokenAsync(resource, clientCred);
return result.AccessToken;
Is it possible to use this flow in Azure API Management? I would like Azure API Management to handle acquiring the token and passing in the header.
The closest I have found is the following article but this seems to involve the consumer of the API passing headers which seems to defeat the point of the API Management subscription functionality
https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-protect-backend-with-aad
At the moment the only way to do that at APIM side is to use send-request policy to do OAuth flow. That will require you to give APIM client id and secret, but you could use named values to store those securely.

Adal.NET acquiretoken for current application

Is it possible to acquire a jwt token for the current application and not based on another Azure app or keep it through the entire lifecycle of signed in?
In my app I'm authenticating my user to Azure with Owin and OpenIdConnect. Authentication succeeded and I received a token from Azure.
In later methods I need to pass the token as parameter, so now I store it in session variable, but this expires earlier and results in an empty variable.
Now I'm trying to acquire a token from Azure with ADAL.
string userObjectId = ((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(c => c.ToString() == "http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
AuthenticationContext authContext = new AuthenticationContext(ConfigurationManager.AppSettings["ida:AADInstance"] + ConfigurationManager.AppSettings["ida:TenantId"], new TokenCache());
ClientCredential credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"], "client_secret");
var result = authContext.AcquireTokenAsync(ConfigurationManager.AppSettings["ida:ClientId"], credential).Result.AccessToken;
This results in an error:
{"error":"invalid_grant","error_description":"AADSTS50105: Application >
'4337e286-7b84-4377-8843-82ea9504606b' is not assigned to a role for the application '4337e286-7b84-4377-8843-82ea9504606b'}
For other apps, it is set up with another app which has permissions for the resource, but I don't want to depend on another app to get the token.
Anyone an idea?
EDIT
I've followed the suggestion of #Jean-MarcPrieur, but on the line
var accounts = await application.GetAccountsAsync();
it returns no accounts which results in an empty result and no accessToken.
Here you are trying to use the client credentials flow (which requires your app to register permissions to call the Web API you want to call - tenant admin permissions).
If you want to access resources in the name of the user, you need to acquire a token for your app to call the API in the name of the signed-in user.
See for instance this sample: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/aspnetcore2-2-signInAndCallGraph which signs-in an user and calls the graph
It's an ASP.NET Core sample using the Azure AD v2.0 endpoint (the latest technos). If you have other constraints, don't hesitate to ask.

How to validate user credentials in Azure AD with Web application / WebAPI

I have a web application. In the home page, user will enter the credentials, and system should validate against Azure AD and proceed further.
When I use a native app, and use UserCredentials, it validates the user, but if I use same approach for WebAPI, it throw the exception
The request body must contain the following parameter: 'client_secret
or client_assertion'
When I use the WebAPI using clientCredentials, it generates the accessToken, which do not validate the user credentials. I also tried passing the credentials as part of httpclient headers in the consequent calls, it is working despite the wrong credentials.
string AzureADSTSURL = "https://login.windows.net/{0}/oauth2/token?api-version=1.0";
string GraphPrincipalId = "https://graph.windows.net";
string userid = "userid";
string password = "pass";
string tenantId = "axxx"; // webapi
string clientId = "bxxx";
string clientSecret = "cxxx";
string authString = String.Format(AzureADSTSURL, tenantId);
var context = new AuthenticationContext(authString);
UserCredential userCredentials = new UserCredential(userid, password);
AuthenticationResult authenticationResult = context.AcquireToken(GraphPrincipalId.ToString(), clientId, userCredentials); // this works only if the clientId corresponds to a native app
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = context.AcquireToken(GraphPrincipalId, clientCredential);
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(result.AccessToken, Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(userid + ':' + password)));
httpClient.GetAsync("http://localhost:11455/Login.aspx");
Is there a way to validate the credentials without using native app? Graph API is not a right choice for this I believe.
I was trying to do the same thing, and ran into the same error:
The request body must contain the following parameter: 'client_secret or client_assertion'
I banged my head on it for a while, and then hit up AzureSupport on twitter.
Turns out this type of auth is only supported if you set up the Azure AD App as Native Client Application. If you set it up as a Web Application then you get that error because the only way to access a web application in Azure AD is via client ID + secret.
You can have multiple apps on top of a single AD, so you can just set up a second app as native client to authenticate the same users in that directory.
You can certainly use WebAPI. Here's how to set it up:
If you use Azure Web Apps, which supports ASP.NET MVC then you can use the Azure Active Directory authentication mechanism. Here is a blog post describing how to set it up: https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-how-to-configure-active-directory-authentication/
Once you have that, auth will be enabled for your app and you can configure the AAD app in the portal. See this blog post for more details: http://blogs.technet.com/b/ad/archive/2014/12/18/azure-active-directory-now-with-group-claims-and-application-roles.aspx
Here is an example which shows how to read AAD group claims from a web app: https://github.com/Azure-Samples/active-directory-dotnet-webapp-groupclaims
Once you have the tokens, you can then call a Web API, which is shown by this example: https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect
There's a good list of AAD examples here: https://azure.microsoft.com/en-us/documentation/articles/active-directory-authentication-scenarios/
Short answer: No
I would consider this article to be the authoritive answer as to why.
No web sites/confidential clients
This is not an ADAL limitation, but an AAD setting. You can only use those flows from a native client. A confidential client, such as a web site, cannot use direct user credentials.
Direct use of username an password is [...] a bit of a Faustian pact – the price you pay for its directness is in the many limitations it entails and the reduced flexibility that it imposes on any solution relying on it.

Authenticate to Azure API App using ADAL

I have an Azure API App marked as "Public (authenticated)" and set up an Azure Active Directory identity in the associated gateway as detailed in Protect an API App.
I then created a native application in the same Azure Active Directory Tenant and added permission to access the Gateway in the delegated permissions.
Using ADAL and the following code, I'm able to successfully authenticate and get an access token, but I can't figure out how to use it to access my API app.
string Tenant = "[xxx].onmicrosoft.com";
string Authority = "https://login.microsoftonline.com/" + Tenant;
string GatewayLoginUrl = "https://[gateway].azurewebsites.net/login/aad";
string ClientId = "[native client id]";
Uri RedirectUri = new Uri("[native client redirect url]");
async Task<string> GetTokenAsync()
{
AuthenticationContext context = new AuthenticationContext(Authority);
PlatformParameters platformParams = new PlatformParameters(PromptBehavior.Auto, null);
AuthenticationResult result = await context.AcquireTokenAsync(GatewayLoginUrl, ClientId, RedirectUri, platformParams);
return result.AccessToken;
}
I've tested the API app manually entering an x-zumo-auth header I get in Chrome and it works then, but not with a token I get using ADAL. I've also tried the browser forms described in their sample code which works but doesn't give me a refresh token.
How do I need to set up my authentication code so I can use a TokenCache and ADAL with my API app?
Generally you pass the access token in the Authorization header when when calling a web api:
Authorization: Bearer ThisIsTheAccessTokenYouRecievedFromADAL
You may want to use AppServiceClient to authenticate the user and invoke a protected API App endpoint. Install Microsoft.Azure.AppService SDK (-pre) Nuget package to your client project.
You can find more details in the AzureCards samples on GitHub - https://github.com/Azure-Samples/API-Apps-DotNet-AzureCards-Sample

Categories