I'm trying to change an existing web API to allow switching between IdentityServer and JWT based authentication. Both forms of authentication work on their own when set to be the DefaultAuthenticateScheme in services.AddAuthentication but I just can't work out how to switch between the two on a per-request basis.
My StartUp.cs is shown below, a few unimportant things have been removed i.e. swagger, error reporting etc.
public class Startup
{
public Startup(IWebHostEnvironment hostEnv)
{
AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
Config.Environment = hostEnv;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddCors()
.AddMvcCore()
.AddNewtonsoftJson()
.AddApiExplorer()
.AddAuthorization(options =>
{
options.AddPolicy("AccessTokenIsOptional",
policy => policy.Requirements.Add(new IsAuthorizationOptional(true))
);
});
services.AddAuthentication(options =>
{
//// Set the default scheme to IdentityServer or JWT
//options.DefaultAuthenticateScheme = "JwtBearer";
//options.DefaultScheme = "JwtBearer";
//options.DefaultChallengeScheme = "JwtBearer";
options.DefaultAuthenticateScheme = "IdentityServer";
options.DefaultScheme = "IdentityServer";
options.DefaultChallengeScheme = "IdentityServer";
})
.AddIdentityServerAuthentication("IdentityServer", options =>
{
options.Authority = Config.IdentityServerBaseUri;
options.RequireHttpsMetadata = false;
options.ApiName = Config.DafAppApiName;
options.ApiSecret = Config.LeisureClientSecret;
})
.AddJwtBearer("JwtBearer", cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Config.CustomClaimPrefix,
ValidAudience = Config.CustomClaimPrefix,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Config.LeisureClientSecret))
};
});
// Inject the AccessTokenIsOptional Authorization policy
services.AddSingleton<IAuthorizationHandler, AuthorizationOptionalHandler>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
// Return static files and terminate the pipeline.
app.UseStaticFiles();
app.UseRouting();
app.UseCors(builder => builder
.WithOrigins("http://localhost:5000", "http://localhost:5001")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
// UseAuthentication adds the authentication middleware to the pipeline so authentication will be performed automatically on every call into the host.
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run(async (context) =>
{
context.Response.StatusCode = 404;
await context.Response.WriteAsync("Unable to route the request");
});
}
}
It's been suggested elsewhere that by adding the following either immediately before or after app.UseAuthentication() and app.UseAuthorization() I can intercept and change the authentication type. If I put it before the calls to app.UseAuthentication() and app.UseAuthorization() the code is hit for all requests (both authorised and unauthorised) whereas if I put it after, it is only called for a request with a valid auth token matching the scheme currently set as the DefaultAuthenticateScheme.
app.Use(async (context, next) =>
{
var authMethod = context.Request.Headers["auth-method"].FirstOrDefault();
if (!string.IsNullOrEmpty(authMethod))
{
// Dynamically set the authentication scheme based on the "auth-method" header
var scheme = authMethod.Equals("jwt", StringComparison.OrdinalIgnoreCase) ? "JwtBearer" : "IdentityServer";
context.Request.Headers["Authorization"] = new Microsoft.Extensions.Primitives.StringValues($"{scheme} {context.Request.Headers["Authorization"].FirstOrDefault()}");
}
// Continue on to the next middleware
await next.Invoke();
});
The code above doesn't seem to work and unless I'm missing something, all it does is change a request with an auth token from Authorization Bearer xxx.yyy.zzz to Authorization Bearer JwtBearer xxx.yyy.zzz. Is this correct?!
Any help is very much appreciated.