Reputation: 6216
Short version: I am having trouble in merging together the correct Authentication config in my .NET Core MVC Website to allow my users to authenticate against Azure Active Directory, but to also allow a Daemon connection (from a Console App) in, too.
Long version: I've got a .NET Core MVC website, which authenticates against Azure Active Directory perfectly fine when using the following in the ConfigureServices method in the Startup.cs:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddSignIn("AzureAd", Configuration, options => Configuration.Bind("AzureAd", options));
I am also trying to get my .NET Core Console App to call into the APIs (as a Daemon connection) into the above MVC website (all is configured in the App Registration section in my Microsoft Azure account). I can connect the Console App to the MVC website and it will successfully hit an Action Result in a controller but only if I am using the following in the ConfigureServices method in the Startup.cs of the website:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddProtectedWebApi("AzureAd", Configuration, options => Configuration.Bind("AzureAD", options));
BASICALLY, if I only use the OpenIdConnect option, my web users can access the website but my console app is denied. If I only use the JwtBearer option, then my Console App can connect, but my web users are denied.
I have Google-Bing'd all day and I'm struggling to get a mash-up of these two configurations to work without knocking the other out.
I have tried to use the .AddJwtBearer() method, but am completely confused by it:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddSignIn("AzureAd", Configuration, options => Configuration.Bind("AzureAd", options))
.AddJwtBearer(options => Configuration.Bind("AzureAD", options));
How do these work together, such that both can be in place and my web app works through a browser, and the Console App (Daemon) works too? Can I bind both to my appsettings.json file??
Incidentally, the appsettings.json file looks like this:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "zzzzzzzzzzzzzz.onmicrosoft.com",
"TenantId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath ": "/signout-callback-oidc",
"ClientSecret": "myAzureClientSecret"
}
}
UPDATE 2020-06-15: Having working on/off of this for AGES, I've found a suitable resolution that works, hence my awarding the bounty points to @michael-shterenberg. ALSO, I now know that I have a great deal to learn from @gary-archer and his impressive blog site. I just happened to get success from Michael's input.
Here's the mods to the Startup.cs file, within the ASP.NET Core MVC Web App in the diagram above:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddSignIn("AzureAd", Configuration, options =>
Configuration.Bind("AzureAd", options))
.AddJwtBearer(o =>
{
o.Authority = "https://login.microsoftonline.com/common";
o.TokenValidationParameters.ValidateAudience = false;
o.TokenValidationParameters.ValidateIssuer = false;
});
services.AddAuthorization(options =>
{
options.AddPolicy("UserAndApp", builder =>
{
builder.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
builder.AuthenticationSchemes.Add(OpenIdConnectDefaults.AuthenticationScheme);
builder.RequireAuthenticatedUser();
});
});
...coupled with the use of the following attribute on the Controller that I'm trying to call from the Daemon app.
[Authorize("UserAndApp")]
My users can still log into the website using the Azure Active Directory processes and now my automated processes can log in, too.
In case anyone is struggling to understand how the Azure App Registration side of all of this works, try this really explanatory blog post:
Secure a .NET Core API using Bearer Authentication
(I wish that I had seen that earlier, when I was trying to get my head around how the Azure App Registration process works!)
Upvotes: 9
Views: 7481
Reputation: 29243
It feels like the architecture is not quite right, and you need to separate the 2 roles performed by your Web Back End:
CURRENT WEB ONLY ARCHITECTURE
MULTI CLIENT ARCHITECTURE
You'll need to update the web back end to include API entry points that are secured by OAuth 2.0 access tokens and not by cookies. The console app will then be able to call your web back end.
.NET CORE SUB PATHS
Introduce an additional /api subpath in your web back end. The UseWhen feature will allow you to do this without impacting other web back end behaviour:
/*
* Apply API behaviour to only subpaths, without impacting the rest of the app
*/
app.UseWhen(
ctx => ctx.Request.Path.StartsWithSegments(new PathString("/api")),
api => {
api.useAuthentication();
api.useJwtBearer();
});
EXAMPLE .NET CORE API
For an example that uses subpaths, see my Sample .Net Core API. The startup class is where ASP.Net middleware is wired with different authenticaiton handling for different subpaths.
FUTURE POSSIBILITIES
Once you have the above logical separation you could potentially evolve it further in future, eg to a completely cookieless model:
My blog at https://authguidance.com follows this approach, and my sample APIs all support any type of client.
Upvotes: -1
Reputation: 1016
Here is the solution that worked for me (Tested on ASP .NET Core 2.1 and 3.1)
AddAuthentication
should be without parameters:services.AddAuthentication()
.AddAzureAD(options => Configuration.Bind("AzureAd", options))
.AddJwtBearer(o=> {
o.Authority = "https://login.microsoftonline.com/common";
o.TokenValidationParameters.ValidateAudience = false;
o.TokenValidationParameters.ValidateIssuer = false;
});
I used AddAzureAd
and not AddSignIn
(is that a custom external library you are using?)
services.AddAuthorization(options =>
{
options.AddPolicy("UserAndApp", builer =>
{
builer.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
builer.AuthenticationSchemes.Add(AzureADDefaults.AuthenticationScheme);
builer.RequireAuthenticatedUser();
});
});
[Authorize("UserAndApp")]
public class HomeController : Controller
Some explanation on the mechanics:
You don't want to setup automatic authentication scheme since this will be the default schema run in the authorization middleware, while you have 2 different types
The policy will try run both authentication handlers, if one of them succeeds then authentication succeeded
Note: if you send a request with an invalid Bearer token, both authetnication handlers will fail, in this case the AzureADDefaults
will "win" since it actually implement a challenge method and will redirect you (status code 302), so make sure to handle this in your app
Upvotes: 5