Azure AD Client Credentials and Interactive in same ASP.NET Core API - asp.net-core

I have an ASP.NET Core (3.1) web API with a couple of client UI apps. Authentication is via Azure AD, everything is working, using:
services.AddAuthentication()
.AddAzureADBearer(options => Configuration.Bind("AzureAD", options))
I also want to allow machine to machine API access using the Client Credentials flow. I can also get this working using the same app registration.
However, I need a way to validate which flow the request is using, as I want to expose functionality using Client Credentials API to API requests that I don't want interactive users to have access to.
What is the best way to make this work?
I have created a separate app registration in AAD that the Client Secret for the Client Credentials grant is on, and I have it adding permissions (as roles) to the token. And in the app registration for the API, I have granted permission to the Client Credentials app registration. But if I obtain a token with this flow, I can't authenticate. I have found that changing the scope in the token request to match the scope on the API app registration gives me a token that allows me to access the API, but then it is missing the app roles.
One the interactive token there are some user specific claims. So one workaround would be to check for the presence of these claims and disallow the functionality I want to restrict if they are present, but this seems a little hacky.
What else can I do? Is there a way to make both login flows work? Or another option that I've missed?

In case anyone else needs to get this working, I got it working by switching from:
services.AddAuthentication()
.AddAzureADBearer(options => Configuration.Bind("AzureAD", options))
to:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.MetadataAddress = $"https://login.microsoftonline.com/mydomain.onmicrosoft.com/.well-known/openid-configuration";
options.TokenValidationParameters.ValidateAudience = false;
});
There are some additional steps too, as mentioned in the question. I created a separate app registration in AAD, and in the app registration for the API granted permission to the new app registration. In the new app registration I had to edit the manifest to get the scope I wanted included as a role (scopes are only assigned to user tokens, not tokens obtained with the client credentials grant).
With the token working that has the role data, for requests to my restricted endpoint I can just check that it's there:
public bool ValidateScope(string scopeName)
{
return _httpContextAccessor.HttpContext.User.IsInRole(scopeName);
}
bool authorised = _clientCredentialsService.ValidateScope("restricted");
if (!authorised)
{
throw new UnauthorizedAccessException("Attempt to access restricted functionality as a regular user");
}
(I have a filter that picks up this exception and bubbles it up to the consumer as a 403).
If anyone else is doing this you can see I've set ValidateAudience to false, so you probably want to add some policies if you do this.

Related

Trying to use token acquisition with ADFS authenticated app

I am stuck on a problem that i cannot think my way out of and have searched everywhere online for answers to no avail.
Here is the problem:
I usually embed PowerBI reports in asp.net application. I follow the Microsoft tutorial. Where we registered an azureAD app as service principal. And we use the Microsoft.Identity.Web library to authenticate our users as well as authenticate as the app's service principal for accessing PowerBI reports.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
builder.Services.AddScoped(typeof(PowerBiServiceApi));
The problem now is that I am building an app that doesn’t authenticate with the Microsoft.Identity.Web but will actually be authenticating users using individual accounts/ ADFS and federation service.
The problem is that I am unable to do token acquisition by authenticating as the app's service principal.
.AddOpenIdConnect(options =>
{
options.ClientId = "xxxxx-xxxxx-xxx-xxx-xxxxxx";
options.Authority = "https://xxxxxxxxxxx";
options.SignedOutRedirectUri = "https://localhost:xxxx/";
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = OnAuthenticationFailed,
};
})
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
However when I am unable to run this app and embed successfully.
My main question is:
is it possible to authenticate my users with one authentication provider (ADFS federation service via openID or wsFederation )
whilst also doing using Microsoft.Identity to do token acquisition
Thus far I've had success with using OpenIDConnect directly with ADFS and MSAL library to get tokens (also from ADFS) for downstream api's. For entries in ADFS using a client secret (i.e. server apps) I would use the IConfidentialClientApplicationBuilder, whereas native apps would use IPublicClientApplicaationBuilder.
This means I never need nor can use this code:
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi() .AddInMemoryTokenCaches();

ASP.NET Core Web API - authorize enduser applications and daemon app

We have a client application (with logged in user) and daemon services (just API no users) accessing a Web API.
The Web API methods need to check users role claims and scopes before executing the operation.
services.AddAuthorization(options =>
{
options.AddPolicy("AssetPolicy", policy =>
{
// checks the scope
policy.Requirements.Add(new ApiScopeRequirement("AssetServicFullScope"));
});
// checks the user's claim
policy.RequireClaim("AssetAdmin", true);
});
However, using this policy in the Web API would only allow access to the client app. The daemon app would fail because its access token not having user claims.
We use OpenIddict to implement the authentication server.
The question is what is the best way to allow authentication of both client apps and daemon apps using ASP.NET Core policies?
It's just the question of what unique scope or claim does the daemon's tokens have. You need some data from the token to make that authorization decision — that the API can be sure that it is dealing with a daemon and that it can authorize the request. Once you know what claim or scope that is you can use a function to fulfill a policy or implement a handler.
I think something like this should work:
services.AddAuthorization(options =>
{
options.AddPolicy("AssetPolicy", policy =>
policy.RequireAssertion(context => context.User.HasClaim(c =>
c.AssetAdmin == "true" || c.IsDaemon == "true")));
});
You must implement Client Credentials Oauth2 flow for authentication of daemons (machine to machine).
This is available out of the box in most Identity Servers, including OpenIddict

JWT authentication and login path

I have a MVC web application that uses JWT token to authenticate users. It calls an authentication/login API to generate and retrieve the token. Since we are in a single sign on setup there won't be a login page that I can redirect the users to. The authentication happens behind without users knowing based on some information. The only way I have found to make this API login call is by decorating the controller(s) in the web application with an Authorization filter and making the httpclient call to the login API in the filter. Is there any way to configure the login URL globally in the MVC application so that every controller will be protected. Something like the following in the cookie authentication that can be configured for the JWT authentication.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/User/Login";
options.LogoutPath = "/User/Logout";
});
Or what are the other places where I can make this httpClient call to the API to retrieve the token and subsequently authenticate the users. Let me know if what I explained here doesn't make sense.

Authenticate and get Azure AD token in .net core Web API with grant_type as Password

How to configure Azure AD authentication and get access token in .net core Web API with grant_type:password mechanism? without re-redirecting azure portal.
public void ConfigureServices(IServiceCollection services) {
services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme) .AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
That is possible if you directly send post request to token endpoint as shown in this thread .
But the Resource Owner Password Credentials Grant is Not Recommended as it's less secure than the other flows(your application handles the credentials), and it is not compatible with conditional access . In addition , personal accounts that are invited to an Azure AD tenant can't use ROPC. Also ,If users need to use multi-factor authentication (MFA) to log in to the application, they will be blocked instead.
-------Suggest approach
If the scenario is user has been authenticated on an client application ,and also acquires an access token to access your web api , in web api , you need to call another resource/API , you can use the On-Behalf-Of flow which enables an application that invokes a service or web API to pass user authentication to another service or web API. Your can click here for code sample in .Net Core .
You can also use the OAuth 2.0 Client Credentials Grant Flow permits a web service (confidential client) to use its own credentials instead of impersonating a user, to authenticate when calling another web service . You can click here for code sample .

Using Google OAuth to secure web services in aspnet core

I'm getting lost in OAuth and OpenIDConnect and aspnet core middleware. Any help on this would be appreciated.
I have multiple UIs (web, native apps) that use the same set of web services, and I'd like to ensure only authenticated users can access the web services. My organization uses Google accounts, so I'd like to use Google authentication restricted to the organization domain.
The web site is properly requiring authentication, following this sample. What I need now is to have the web site (AngularJS 4) invoke my back end web services with an auth token that I can verify with Google.
The back end services are written with aspnet core. I've tried using these approaches: Google middleware and Google OpenIDConnect but these still 1) assume there is a UI that can prompt an unauthorized user to log in, and 2) appear to be cookie-based, and I won't have cookies for the web service calls.
I don't want to prompt the user to log in, since the "user" in this case is a software client. Either they're authenticated or not already. I just need to get the authentication token, validate it, and carry on.
This appears to be the same question, which hasn't been answered yet, either.
Any suggestions are appreciated. Also, suggestions or tips on having native apps do the same!
Got it working. As mentioned, I was getting lost, and the OpenIDConnect, though referenced in several areas as a solution, was a red herring for the web services. Here's what is working for me now, with as complete steps as I can provide (some cleanup required):
Add authentication to the UI following these directions
Obtain the JWT token as shown in the first segment here
On each web service call, include the JWT token in the headers:
Name: Authentication
Value: Bearer {token value}
Install the JwtBearer NuGet package
In the ConfigureServices method of Startup in the web service, after you AddMvc():
services.AddAuthorization(options =>
{ // this policy needed only if you want to restrict to accounts within your domain. otherwise, don't use options. or use whatever options work for you.
options.AddPolicy("hd",
policy => policy.RequireAssertion(context =>
context.User.HasClaim(c =>
c.Type == "hd" &&
("https://accounts.google.com".Equals(c.Issuer) ||
"accounts.google.com".Equals(c.Issuer, StringComparison.CurrentCultureIgnoreCase)) &&
c.Value == "yourdomain.com"
)));
});
In the Configure method, before you UseMvc():
JwtBearerOptions jwtOptions = new JwtBearerOptions();
jwtOptions.Audience = "{the OAuth 2.0 client ID credential from google api developer console}";
jwtOptions.Authority = "https://accounts.google.com";
jwtOptions.TokenValidationParameters = new TokenValidationParameters();
jwtOptions.TokenValidationParameters.ValidIssuers = new List<string>()
{
"https://accounts.google.com",
"accounts.google.com"
};
app.UseJwtBearerAuthentication(jwtOptions);
Perhaps there is a more appropriate way to do this...if there is, I'm interested in trying it out. For now, this is working.
I will try to help.
First you need to look at OpenID Connect (which is built on top of OAuth 2.0) remembering that OAuth 2.0 NOT an Authentication protocol.
1) assume there is a UI
No UI is required for login assuming you are using Google services. You only need to check for the existence of and validate the Access Token, Identity Token (and perhaps the refresh token). If there is no Token, assume the user is NOT Authenticated and redirect them to the Authentication Server with a Authorization Request.
If there is a valid Access Token and Refresh Token, then you can assume the user is Authenticated.
You can also inspect the Access Token for proper "Scopes" to determine if they are Authorized for your specific application.
If you are using Google for Authorization Server, you can validate the the hd parameter within Identity Token has the desired Domain.
BTW: No cookies involved.
Hope that helps.