We use MSAL and Azure AD for authentication.
We have a client and several microservices (that are not hosted in Azure)
The calls from the client to the microservices work fine. But we need also to make calls between the microservices. We are trying to do this using a token that uses a client secret, with the following code:
var app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.Build();
var aquireTokenForClientParameterBuilder = app.AcquireTokenForClient(scopes);
var token = await aquireTokenForClientParameterBuilder.ExecuteAsync();
When we try to call the service it fails with:
WwwAuthenticate {Bearer error="invalid_token"}
When I look in the token with jwt.io, I cannot see that the client secret is in the token.
Any ideas how to fix this, or an alternative way to call between services?
In your scenario(you have a client + several micro services), you authenticated in the client and use the access token to call your service, and now you want to make your service to call another service, you should use on-behalf-flow here.
The whole flow can be understood as, in the server side, using request below to generate a new access token and use it to send request to another service.
POST /oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com/<tenant>
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
client_id={azure_ad_app_id}
&client_secret={client_secret}
&assertion={access token to this request}
&scope={scope}
&requested_token_use=on_behalf_of
And if the micro service is an asp.net core app, then it should integrate Microsoft.Identity.Web in the application, and we can use var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "api://xx/scope_name" }); to generate the on-behalf-of token.
Related
I'm trying to implement an on-behalf-of flow, with Microsoft Identity Platform where my Web app authenticates users, then makes a request to my Web API which in turn makes a request to the Microsoft Graph API (and returns the result to the Web app).
My problem is that I will need to pass on an on-behalf-of token to my Web API for it to be granted acces to Microsoft Graph, but I cannot manage to generate this token. (I'm trying to generate this token using Postman at the moment.)
What I want to be able to run is a code snippet provided by the official documentation here for the Microsoft Graph SDK (in the case of an OBO-flow), and what I need help with is how to generate the token for the oboToken variable.
using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Identity.Client;
var scopes = new[] { "User.Read", "Presence.Read.All" };
var tenantId = "common";
var clientId = "<id of my API as registered in Azure AD / App Registrations>";
var clientSecret = "<value from Client Secret in Registerd Application / Certificates & secrets";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var oboToken = "< WHAT NEEDS TO BE PROVIDED BY THE WEB APP >";
var cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var authProvider = new DelegateAuthenticationProvider(async (request) => {
var assertion = new UserAssertion(oboToken);
var result = await cca.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
var graphClient = new GraphServiceClient(authProvider);
Console.WriteLine(graphClient.Me.Request().GetAsync().Result);
I've tried the following: I generate an authorization code with the following request in my browser
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=< Id of my API >
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp
&response_mode=query
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
Then, with the returned code, make the following POST request (from Postman)
https://login.microsoftonline.com/common/oauth2/v2.0/token?
client_id=< Id of my API >
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp
&grant_type=authorization_code
&code=< code returned by request above >
When I use the returned token from the response as the value of the oboToken in the code snippet, I get a
MsalServiceException: AADSTS50027: JWT token is invalid or malformed.
If I instead also include a client_secret parameter in the POST request above for a token, I get the response AADSTS90023: Public clients can't send a client secret.
How could I generate an on-behalf-of token to be able to run the provided code snippet?
Thanks in advance!
It seems to me that your mistake is getting an MS Graph API token with the authorization code flow.
The way it should work is:
Web App gets an access token to the Web API using authorization code flow
Web API receives the access token and exchanges it for an MS Graph API token using the on-behalf-of flow
Web API calls MS Graph API
So when your Web App gets an access token, it should use a scope defined in the API app registration instead of scope=https%3A%2F%2Fgraph.microsoft.com%2F.default.
If you specify an MS Graph API scope, you get an access token for MS Graph API, meaning you are trying to call MS Graph API from the Web App, instead of your API.
Edit:
Here is my question reformulated:
I have a web server with secured api endpoints - one must have been authenticated with Google prior to using them. I implemented Challenge and Callback endpoints for that.
This works well from a browser with my SPA web front-end. The user gets redirected to the Google website to sign-in and then gets redirected back to my webapp; the browser then has the authenticated cookies and the webapp can use the endpoints to update its state.
I also have a WPF application that will communicate with the web server.
I want the WPF application to do the same as the web front-end: Use the web api endpoints after being authenticated with Google. The connection between the WPF application and my web server is done through an HttpClient.
My problem is I don't know how to authenticate that HttpClient connection between the WPF app and the web server.
I tried using the same Challenge endpoint but the response I get is of course the HTML from the Google Sign-In page, so I guess I can't use that with an HttpClient...
I also tried authenticating with GoogleApis from the WPF app and use the authenticated token to set cookies in the HttpClient but apparently this is not compatible.
How to authenticate an HttpClient connection to a web api with an external provider such as Google?
Original question:
From a WPF application, the user authenticates with Google with this code:
using Google.Apis.Auth.OAuth2;
...
public void Authenticate()
{
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "myClientId",
ClientSecret = "myClientSecret"
},
new[] { "email", "openid" },
"user",
CancellationToken.None).Result;
}
This works and the UserCredential object contains the authenticated token:
How to embed this token information in a web request made with an HttpClient in order to call my webapi endpoint?
I think the request must include some cookies to inform the server that it has been authenticated, but I don't know which ones exactly.
The endpoint on the server-side validates that the user is authenticated with the help of IdentityServer:
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
If I got your question right, you just have to set the Authorization header
var credentials = await GoogleWebAuthorizationBroker.AuthorizeAsync(
clientSecrets,
new[] { "email", "openid" },
"user",
CancellationToken.None);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
credentials.Token.TokenType,
credentials.Token.IdToken);
Maybe you'll find below a helpful hint to better understand OpenID :)
The confusion stems from mixing GoogleApis and IdentityServer frameworks.
Authentication/authorization can be achieved using either of them.
Objects from Google.Apis.Auth.OAuth2 and IdentityServer4 namespaces are not designed to interact.
No manual cookie handling is necessary, for sure.
Ask yourself to whom does Google provide trust for the user. If it calls back to WPF, then webapi trusting WPF is a separate issue.
You answer your own question in the question:
the browser then has the authenticated cookies and the webapp can use
the endpoints to update its state
HttpClient needs to send those same cookies.
How do I set a cookie on HttpClient's HttpRequestMessage
If I understood your question right, then I faced the same problem not too long ago.
The way I implemented it is that in the backend, no matter who tries to access the endpoint, they had to send a Bearer X authorization token.
The token contained the identity of the client that wanted to access the resource, and I checked if he was permitted.
No matter what kind of client wants to access the endpoint, it just has to have that authroziation header in the request that he sends and the backend will treat it the same.
In my scenario, I used an authentication service that returns a cookie to the client with a certain JWT that contains the identity information.
Then from the client I send in every request the JWT received from the authentication service as an authorization header to the backend.
The reason I had to put the JWT that I receive from the service in a header, is that the authentication service and the backend service are not in the same domain, so cookies cant be shared.
This results in such design that no matter how you authenticate the client, the end result must be some sort of token that the backend can receive and read.
Hope this helps.
UPDATE (solution): I ended up simply extracting the token from the request that my frontend is sending with:
private async Task<string> GetApplicationAccessToken()
{
var token = this.Request
.Headers["Authorization"]
.First()
.Substring("Bearer ".Length);
var assertion = new UserAssertion(token, _ASSERTION_TYPE);
var authResult= await this._app.AcquireTokenOnBehalfOf(new []{""}, assertion)
.ExecuteAsync();
return authResult.AccessToken;
}
ORIGINAL:
I want to funnel data from the MS Graph API (Azure AD endpoint) through my backend (.NET Core Web API) back to my Angular App, that requests the data.
I am running into the problem that I am unable to get an Access token in my backend Web API.
I have Implemented a graph service according to this sample where user consent is prompted through a static html page that is being hosted on the web API. But I want to access MS Graph without explicit user consent.
I have looked for ways to get an access token for my web API without user consent, but not found anything helpful. Only stuff that confuses me. I have also supplied the App registration in Azure AD with application permissions and supplied my web API with sufficient information to the Azure app.
I am still not sure how to exactly adapt the sample code to work with my scenario where user consent is not required / an token already present in the request that my Angular app makes to my web API.
I am getting a userId (objectId.tenantId) in my GraphAuthProvider class when I am trying to call GetAccountAsync(). Yet I still don't receive a token from that call and don't get any error hints, just null.
public async Task<string> GetUserAccessTokenAsync(string userId)
{
var account = await _app.GetAccountAsync(userId);
if (account == null)
{
throw new ServiceException(new Error
{
Code = "TokenNotFound",
Message = "User not found in token cache. Maybe the server was restarted."
});
}
My appsettings.json
"AzureAd": {
"CallbackPath": "/signin-oidc",
"BaseUrl": "https://localhost:63208",
"ClientId": "[redacted]",
"TenantId": "[redacted]",
"ClientSecret": "[redacted]", // This sample uses a password (secret) to authenticate. Production apps should use a certificate.
"Scopes": "user.read profile",
"GraphResourceId": "https://graph.microsoft.com/",
"GraphScopes": "User.Read.All Groups.Read.All"
}
Can you point me in the right direction as to how to call the MS Graph API from my backend by using the application permissions?
Client credential flow using directly http post
In you web api , you can directly create http request to authenticate using client credential flow and retire Microsoft Graph's access token :
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_secret=qWgdYAmab0YSkuL1qKv5bPX
&grant_type=client_credentials
Before that , you'd better admin consent the app permissions , see the detail steps in this article .
Client credential flow using MSAL.NET
If using the MSAL.NET , you can use below code sample for client credential flow :
// Even if this is a console application here, a daemon application is a confidential client application
IConfidentialClientApplication app;
#if !VariationWithCertificateCredentials
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId("{tenantID}")
.WithClientSecret(config.ClientSecret)
.Build();
#else
// Building the client credentials from a certificate
X509Certificate2 certificate = ReadCertificate(config.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId("{tenantID}")
.WithCertificate(certificate)
.Build();
#endif
// With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
// application permissions need to be set statically (in the portal or by PowerShell), and then granted by
// a tenant administrator
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
}
catch(MsalServiceException ex)
{
// Case when ex.Message contains:
// AADSTS70011 Invalid scope. The scope has to be of the form "https://resourceUrl/.default"
// Mitigation: change the scope to be as expected
}
You can refer to this article and code sample on Github.
Client credential flow using Microsoft Graph .NET authentication library
From document : https://github.com/microsoftgraph/msgraph-sdk-dotnet-auth
You can use Client credential provider :
// Create a client application.
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
// Create an authentication provider.
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
// Configure GraphServiceClient with provider.
GraphServiceClient graphServiceClient = new GraphServiceClient(authenticationProvider);
Or directly use MSAL.NET to authenticate using client credential flow and build the Microsoft Graph client like reply from #Philippe Signoret shows .
I have an on-premise Dynamics CRM (2016) that is configured with ADFS (3.0). When a user want's to Login, they get redirected to the ADFS login page and the user enter their Windows AD credentials.
From a .net core application I need to make request to the CRM api using HttpClient. When I try to send the credentials like I normally would for a Windows Auth CRM it doesnt work. I get a 401 Unauthorized. Like below.
HttpClient client = new HttpClient(new HttpClientHandler() { Credentials = new NetworkCredential("myuser", "mypassword", "mydomain") });
var result = client.GetAsync("https://mycrmaddress/api/data/v8.0/accounts");
I also tried using Adal to retrieve a token and attach it as a bearer token to the request but I'm unable to get a token with adal. When I try I receive the following:
The authorization server does not support the requested 'grant_type'. The authorization server only supports 'authorization_code'
ADFS 3.0 doesn't support this flow.
I cannot upgrade to ADFS 4.0 so I would like to know what are my options to make an authenticated call to CRM api (without prompting a login window as this application is a service).
Is there any configuration I can do on ADFS so my first example work? Or is it possible to do it with Adal even if it's ADFS 3.0? Or any other solution...
I found the answer to my question. It's kinda hackish, but I tested it myself and it works. As a temporary solution this will do the trick.
Details are available here: https://community.dynamics.com/crm/f/117/t/255985
ADFS 3.0 supports the Authorization Code flow and this what we will use in this case.
We need to retrieve an authorization code. Normally at this steps a windows is prompted to the user to enter its credentials. By doing a POST and sending the user/password it's possible to retrieve an authorization code.
{authProvider} - ADFS Uri - something like
https://adfs.mycompany.com/adfs/oauth2/
{ClientId} - The Guid used to
by your infrastructure team to add your application to ADFS
{RedirectUri} - The IFD Uri for dynamics - should match the redirect
Url used to by your infrastructure team to add your application to
ADFS
username - The User set up on ADFS and in Dynamics
password - The password for the above user
Then we make the following call with these information using HttpClient.
var uri = $"{authProvider}authorize?response_type=code&client_id={clientId}&resource={redirectUri}&redirect_uri={redirectUri}";
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("username",username),
new KeyValuePair<string,string>("password",password),
});
var responseResult = _httpManager.PostAsync(uri, content).Result;
The response content will be an html page (Remember normally this flow prompts a login page to the user). In this page there will be a form that contains the authorization code. using a library like HtmlAgilityPack retrieve the token. This is the hackish part of the solution.
Now that we have an authorization code we need to retrieve an access token.
For that we need to make the following call
var uri = $"{authProvider}token";
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("grant_type","authorization_code"),
new KeyValuePair<string,string>("client_id",clientId),
new KeyValuePair<string,string>("redirect_uri",redirectUri),
new KeyValuePair<string,string>("code",code)
});
var response = await _httpManager.PostAsync(uri, content);
The response content will be a json string that will contain the access token.
With the access token, make the call to CRM rest API.
You will need to attach the token to the HttpClient in the header as a bearer token.
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",token);
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
From now on you can make calls to CRM api and you will be authorized. However be carefull normally access token are short lived. You will either need to increase their lifetime or request a new token everytime it's expired.
I've been at this whole day and it's driving me NUTS. I've got a web API published on Azure which is secured with Azure AD authentication. I've been using this API in combination with a mobile app for quite a while now. On the mobile app, I use the library for client authentication (Microsoft account which is in AD) and this works perfectly. Within the app, API requests are authenticated with the X-ZUMO-AUTH header.
Now for a new part of the application, I've got a C# console app which also needs to be able to call the app. This needs to run stand-alone, so without asking for user credentials. So I created an application in Azure AD, gave it permissions to the Web API instance and got me some code to get an authentication token. That token is then passed into the HttpClient object:
AuthenticationContext ac = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(clientID, clientSecret);
AuthenticationResult authenticationResult = await ac.AcquireTokenAsync(resource, clientCred);
string authToken = authenticationResult.AccessToken;
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, endpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
HttpResponseMessage response = await client.SendAsync(request);
Server auth is set-up like this:
string aadTenant = configProvider.GetConfigurationSettingValue("ida.Tenant");
string aadAudience = configProvider.GetConfigurationSettingValue("ida.Audience");
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = aadTenant,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = aadAudience
},
});
I have triple checked all the variables. I also verified that the Bearer token is correctly passed to the service (Fiddler shows it). Everything seems absolutely fine, but still, the server keeps responding with Unauthorized.
My hunch is that this has something to do with the Web API. It's been set up as mobile services back-end. I'm suspecting that this is somehow interfering with 'normal' authentication schemes and the 401 is coming from mobile services not understanding this authentication request, even though the bearer authentication has been setup. But this is just a hunch and I'm not sure whether it actually makes sense or not.
I've seen the related questions on this topic, and all of the samples (used this one as the primary source of inspiration). I've tried all of the variations out there, to no avail. So what I'm looking for is ways to find out where the actual problem is. "Unauthorized" is a bit vague to say the least, so I'm trying to find out why the calls are returned with this response. Any help is greatly appreciated.
Additional endpoint info
As requested, here's some info on the endpoints I'm using:
authority = https://login.microsoftonline.com/{aad-tenant-name}
Also tried the OAuth2 token endpoints listed on the management page:
authority = https://login.microsoftonline.com/{aad-guid}/oauth2/token
authority = https://login.microsoftonline.com/{aad-guid}/oauth2/authorize
For the client, information I'm passing in the ClientID and generated secret I got from the AAD Application page for the application I created. I have granted the application permissions to access the App Service instance for my web API endpoint. Those calls are going to:
https://{app service url}.azurewebsites.net/api/controller
Whilst trying to get this going I noticed more weird behavior. I thought for now I'd just set this particular controller to [AllowAnonymous] so that I could call it without any authentication. But when I do so and omit passing in the Bearer token, I still get Unauthorized as response. That kind of enforces my idea that there's something wrong on the server side and that this might have something to do with how the mobile app stuff is strapped to the webapi controllers.
Never mind, the controller being forced to authorize was caused by Web App level authentication settings, documented here