The OpenIddict ASP.NET Core server cannot be used as the default scheme handler - c#

I'm trying OpenIddict 3.0. I followed the steps in the documentation, created an Authorize controller, and added a test application. When I try to run I get this exception:
The OpenIddict ASP.NET Core server cannot be used as the default
scheme handler. Make sure that neither DefaultAuthenticateScheme,
DefaultChallengeScheme, DefaultForbidScheme, DefaultSignInScheme,
DefaultSignOutScheme nor DefaultScheme point to an instance of the
OpenIddict ASP.NET Core server handler
I cannot find what I'm doing wrong.
Here is my Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
{
// Configure the context to use Microsoft SQL Server.
options.UseInMemoryDatabase("Identity");
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
options.UseOpenIddict<Guid>();
});
AddIdentityCoreServices(services);
services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>()
.ReplaceDefaultEntities<Guid>();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the token endpoint (required to use the password flow).
options.SetTokenEndpointUris("/connect/token");
// Allow client applications to use the grant_type=password flow.
options.AllowPasswordFlow();
// Mark the "email", "profile" and "roles" scopes as supported scopes.
//options.RegisterScopes(OpenIddictConstants.Scopes.Email,
// OpenIddictConstants.Scopes.Profile,
// OpenIddictConstants.Scopes.Roles);
// Accept requests sent by unknown clients (i.e that don't send a client_id).
// When this option is not used, a client registration must be
// created for each client using IOpenIddictApplicationManager.
options.AcceptAnonymousClients();
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough() // Add this line.
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement(); // During development, you can disable the HTTPS requirement.
})
// Register the OpenIddict validation components.
.AddValidation(options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
});
// ASP.NET Core Identity should use the same claim names as OpenIddict
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme;
});
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private static void AddIdentityCoreServices(IServiceCollection services)
{
var builder = services.AddIdentityCore<ApplicationUser>();
builder = new IdentityBuilder(
builder.UserType,
typeof(ApplicationRole),
builder.Services);
builder.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddSignInManager<SignInManager<ApplicationUser>>();
}
Please assist me on what I'm doing wrong.

I finally figured out where I went wrong. #Train Thanks for pointing me in the right direction.
changing the services.AddAuthentication(...) from
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme;
});
to
services.AddAuthentication(options =>
{
options.DefaultScheme = OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});

What's you're method of authentication? Cookie? JWT?
You need to change this line of code. You can't set OpenIddictServerAspNetCoreDefaults.AuthenticationScheme; as the default scheme
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme;
});
Default Authentication Scheme
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
or overload
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});
Here are the docs on Authentication with a lot more to read up on.

Related

Implement multiple authorization in .net 6 web API

I have auth0 authentication implemented for my webAPIs. But now due to some requirement change few of APIs need to be authorized with another scheme. So I need below specified different authorization schemes to authorize my API
Auth0 scheme (already authorizing api's)
Azure AD B2C
I have implemented Azure AD B2C and which is working fine when used alone but when I am trying to add it enable it with a previous scheme it is causing issues.
public static IServiceCollection AddSecurityPolicy(this IServiceCollection services, ConfigurationManager config)
{
const string ClientPortalScheme = "ClientPortalBearerScheme";
//from https://auth0.com/blog/securing-aspnet-minimal-webapis-with-auth0/
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = config["AuthenticationSettings:Domain"];
options.Audience = config["AuthenticationSettings:Audience"];
}).AddJwtBearer(ClientPortalScheme, ClientPortalScheme, options =>
{
options.Authority = config["AzureADB2CSettings:Domain"];
options.Audience = config["AzureADB2CSettings:Tenant"];
});
//services.AddMicrosoftIdentityWebApiAuthentication(config, "AzureADB2CSettings");
//By default, require an authenticated user
//Only one JWT bearer authentication is registered with the default authentication scheme JwtBearerDefaults.AuthenticationScheme.
//Additional authentication has to be registered with a unique authentication scheme.
//see https://docs.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-6.0
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme,
ClientPortalScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
return services;
}
This is how my code looks like.
Issue is when I am calling my endpoint it says
Unable to obtain configuration from: 'https://mydomain.auth0.com/.well-known/openid-configuration'.
---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'https://mydomain.auth0.com/.well-known/openid-configuration'.
Please let me know if any information is required int this regard
I tried to reproduce the issue in my environment.
It occurs when app config is not receiving OpenIDmeta data properly
The issue was due to TLS configuration in my case as TLS 1.1 or TLS 1.0 are depreciated.
Please make sure to set TLS to 1.2 or greater.
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
......
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;;
....
app.UseAuthentication();
app.UseAuthorization();
....
}
Please make sure your backend API refers to /.well-known/openid-configuration
Or check this way.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
MetadataAddress = "http[s]://{IdentityServer}/.well-known/openid-configuration"
...
});
In AzureAdB2c make sure to set the Authority or (Domain and instance ) property correctly .
Authority being the combination of Instance and domain
Appsettings.json
Authority : https://[yourb2ctenant}.b2clogin.com/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0
Also see if Instance can be https://<tenant>.b2clogin.com/tfp/
Domain : <b2ctenant>.onmicrosoft.com
Then the authentication can be carried on successfully:

Having issues with IAntiforgery in ASP.NET Core 6.0

Please look at my long answer at the end about how I resolved this. I had gotten too frustrated and after another day with a fresh perspective and more sleep, I got to a solution.
I did this in 5.0 with no issues in the Startup.Configure method.
Basically I created a header for the request on a protected route. I'm using React as the front end. I'm finding when I place everything in Program.cs the dependency injection, authorization doesn't work right so I split up into separate Program and Startup files.
But I can't use the following signature in 6.0 like I did in 5.0:
example that worked in 5.0:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAntiforgery antiforgery)
{
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("antiforgery/token", context =>
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Headers.Append("XYZ", tokens.RequestToken!);
return Task.FromResult(StatusCodes.Status200OK);
});
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
}
Program.cs (my attempt to split up program and startup - 6.0)
var startup = new dolpassword.Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);
var app = builder.Build();
startup.Configure(app,app.Environment);
Saw this example on Microsoft website:
app.UseRouting();
app.UseAuthorization();
// app.Services syntax error in Configure for 6.0
var antiforgery = **app.Services.GetRequiredService<IAntiforgery>();**
app.Use((context, next) =>
{
var requestPath = context.Request.Path.Value;
if (string.Equals(requestPath, "/",
StringComparison.OrdinalIgnoreCase)
|| string.Equals(requestPath, "/index.html",
StringComparison.OrdinalIgnoreCase))
{
var tokenSet = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN",
tokenSet.RequestToken!,
new CookieOptions { HttpOnly = false });
}
return next(context);
});
I was able to successfully do this in 6.0 so I will share some of the code and how I resolved it. I also had Windows authentication baked in with a policy-based authorization. The reason I'm putting all the authentication/authorization wireup in this post is because the entire solution relies on authentication, authorization and antiforgery.
First I set up my services. I get IAntiforgery by default by adding ControllersWithViews but I want to use my own header name, which is X-XSRF-TOKEN instead of the .AspNet.Antiforgery.xxxx or whatever the default is. I also needed options.DefaultAuthenticateScheme = NegotiateDefaults.AuthenticationScheme; to get Windows auth working.
string CorsPolicy = "CorsPolicy";
//===================================formerly Configure Services
WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
ConfigurationManager _configuration = builder.Configuration;
// Add services to the container.
**IServiceCollection? services = builder.Services;
services.AddAntiforgery(options => { options.HeaderName = "X-XSRF-TOKEN";
options.Cookie.HttpOnly = false; });**
services.AddTransient<IActiveDirectoryUserService, ActiveDirectoryUserService>();
services.AddControllersWithViews();
services.AddAuthentication(options => {//needed for Windows authentication
options.DefaultAuthenticateScheme = NegotiateDefaults.AuthenticationScheme;
});
Adding more...
I'm using Windows auth so I'm using the Negotiate provider. Then I set up my Authorization. I insert my own authorization policy and also add my claims transformers to get the authenticated user into a claim. The fallback policy in Authorization was causing an Authentication exception.
services.AddAuthorization(options =>
{
// options.FallbackPolicy = options.DefaultPolicy;//authorization bombs if you include this line
options.AddPolicy("AuthenticatedOnly", policy => {
policy.Requirements.Add(new AuthenticatedRequirement(true));
});
});
services.AddTransient<IClaimsTransformation, MyClaimsTransformer>();
services.AddTransient<IAuthorizationHandler, AppUserRoleHandler>();
services.AddTransient<IAuthorizationHandler, AuthenticatedRoleHandler>();
services.AddCors(options =>
{
options.AddPolicy(CorsPolicy,
builder => builder
.WithOrigins("https://localhost:7021","https://localhost:44414")
//Note: The URL must be specified without a trailing slash (/).
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
Now I'm in the middleware territory...as you know, order matters in your middleware! In 5.0 you could add IAntiforgery to your constructor and DI would handle the rest. In program.cs you don't have that luxury. Fortunately you can just grab it out of your services collection and you see that in the following code.
//==============formerly Startup.Configure=====================
WebApplication app = builder.Build();
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseCors(CorsPolicy);
IAntiforgery? antiforgery = app.Services.GetRequiredService<IAntiforgery>();
Now when I'm setting up my endpoint routing. Found out that UseRouting and Use.Endpoints are married at the hip and need to be paired.
I also create a protected route "/auth" (protected by my authorization policy) to grab the antiforgery request token generated when we added it in the services collection. So this header won't be persisted from request to request like a cookie would. The minimal API allows me to create a route without creating the controller and action in a separate controller class.
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/auth", context =>
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Headers.Append("XYZ", tokens.RequestToken!);
return Task.FromResult(Results.Ok());
}).RequireAuthorization("AuthenticatedOnly");
endpoints.MapControllerRoute(
name: "default"
pattern: "{controller}/{action=Index}/{id?}");
});
My React front end will use a fetch get request to get the token from the headers collection and then stick into a second post request and voila it works.
BTW, React doesn't provide Antiforgery functionality out of the box like Angular, in step with it's minimalist API ethos.
The action I'm posting to looks like this:
[HttpPost]
[Authorize(Policy="AuthenticatedOnly")]
[ValidateAntiForgeryToken]
public string Update()
I fully realize there are other ways to do this.

How to check user-agent in ASP.NET Core health check calls when using own authentication, authorization?

I used the accepted answer to How to check user-agent in ASP.NET Core health check calls (MapHealthChecks)? , with one difference in requirement:
My application is not using App services authentication and authorization. Therefore, I needed to allow anonymous access for healthcheck as per documentation.
Here are changes to Startup.cs
//other services
services.AddHttpContextAccessor();
services.AddScoped<IAuthorizationHandler, UserAgentAuthorizationHandler>();
services.AddHealthChecks()
.AddCheck<HealthCheckFoo>("health_check_foo")
.AddCheck<HealthCheckBar>("health_check_bar");
//other services.AddAuthorization
services.AddAuthorization(options =>
{
options.AddPolicy("HealthCheckPolicy", builder =>
{
builder.AddRequirements(new UserAgentRequirement("HealthCheck/1.0"));
});
});
//...
app.UseEndpoints(endpoints =>
{
//other endpoints...
endpoints.MapHealthChecks("/health", new HealthCheckOptions { AllowCachingResponses = false })
.RequireAuthorization("HealthCheckPolicy");
.WithMetadata(new AllowAnonymousAttribute());
My expectation is that when testing locally, https://localhost:5001/health return an error. It does not.
It looks as your startup class has a mistake on the endpoints.MapHealthChecks adds a RequireAuthorization but as the same time you also add the AllowAnonymousAttribute.
Try with:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHealthChecks("/health", new HealthCheckOptions()
{
AllowCachingResponses = false,
})
.RequireAuthorization("HealthCheckPolicy");
});

How do I add AllowAnonymousAttribute to a route/request from custom midleware?

I have an ASP.NET Core website that is build with static files and ASP.NET Core routing middleware, so no MVC.
I requiring all request to be authenticated by default.
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
I have a mix of middlewares and routing configuration - but how do I specify that some routes of the static files and my custom middleware needs to Allow Anonymous?
Is there a way to app.Use(...) and tell the context that authorization has been applied and it should not use the fallback? So my custom middlewares can be set up correctly or am I required to set everything up with endpoint routing and added metadata to the routes?
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<NextJSMiddleware>();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseEndpoints(config =>
{
///This is for detecting whether the application process has crashed/deadlocked. If a liveness probe fails, app can be stopped/restarted, or create a new one.
config.MapHealthChecks("/.well-known/live", new HealthCheckOptions { Predicate = _ => false }).WithMetadata(new AllowAnonymousAttribute()); ;
///Readiness probe. This is for detecting whether the application is ready to handle requests.
config.MapHealthChecks("/.well-known/ready").WithMetadata(new AllowAnonymousAttribute());
config.MapEAVFrameworkRoutes();
config.MapPost("/.auth/login/passwordless", async (httpcontext) =>
{
...
}).WithMetadata(new AllowAnonymousAttribute());
config.MapGet("/.auth/login/passwordless/callback", async (httpcontext) =>
{
...
});
});

Identityserver 4 and Ocelot

I'm trying to use Ocelot with IS4 following
https://ocelot.readthedocs.io/en/latest/features/authentication.html
When using
public void ConfigureServices(IServiceCollection services)
{
var authenticationProviderKey = "TestKey";
services.AddAuthentication()
.AddJwtBearer(authenticationProviderKey, x =>
{
});
}
and use "TestKey" in ocelot.json, it throws an error when starting the application
Unable to start Ocelot, errors are: TestKey,AllowedScopes:[] is unsupported authentication provider
Any idea what's wrong? Do I need set up something in particular in my IdentityServer app?
You need to add the options, e.g.:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = "https://demo.identityserver.io";
// name of the API resource
options.Audience = "api1";
});
More info at: http://docs.identityserver.io/en/latest/topics/apis.html#
You will also need to add an API resource to your Identity Server:
new ApiResource("api1", "Some API 1")
See:
http://docs.identityserver.io/en/latest/topics/resources.html and http://docs.identityserver.io/en/latest/reference/api_resource.html#refapiresource

Categories

Resources