Do not validate JWT expiration in tests - c#

I'm writing an integration test for my web service with JWT authentication. I'd like to test it with a token received from the real service. The problem is real tokens expire in 1 hour.
A possible way is to set options.TokenValidationParameters.ValidateLifetime inside AddJwtBearer of class Startup below.
However, Startup class is a also a code to be tested, so I don't want to change or replace it for testing.
Is there a neat way to test all JWT validation logic except expiration?
My project code:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("JWT")
.AddJwtBearer("JWT", options =>
{
options.Authority = "https://my-real-identity-server.com/";
options.Audience = "...";
// I don't want to disable lifetime validation in real life
// options.TokenValidationParameters.ValidateLifetime = false;
});
// Other stuff
}
public void Configure(IApplicationBuilder app) => app
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseEndpoints(endpoints => endpoints.MapControllers());
}
public class TestController : ControllerBase
{
[Authorize]
[HttpGet("/validate")]
public string Get() => "success";
}
My test code:
public class HostBuilderTests
{
private IHost testHost;
private CancellationTokenSource cancel;
private HttpClient client;
[SetUp]
public async Task ShouldReturnStatus()
{
testHost = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
webBuilder
.UseStartup<Startup>()
.UseTestServer())
.ConfigureServices(services => services
.AddLogging(b => b.ClearProviders().AddNUnit()))
.Build();
cancel = new CancellationTokenSource(10000);
var server = testHost.Services.GetRequiredService<IServer>()
.ShouldBeOfType<TestServer>();
await testHost.StartAsync(cancel.Token);
client = server.CreateClient();
}
[TearDown]
public async Task TearDown()
{
await testHost.StopAsync(cancel.Token);
client.Dispose();
testHost.Dispose();
cancel.Dispose();
}
[Test]
[TestCase("<<JWT token copied from the real service>>")]
public async Task StatusShouldBeOk(string realToken)
{
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", realToken);
using var response = await client.GetAsync("/validate", cancel.Token);
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
}

Finally I found a simple way to manage options for authentication handlers:
An authentication scheme is a name which corresponds to:
An authentication handler.
Options for configuring that specific instance of the handler.
See MSDN.
So it suffices to post-configure JwtBearerOptions specifying the authentication scheme name "JWT" as options instance name in the test setup:
testHost = Host.CreateDefaultBuilder()
// other setup
.ConfigureServices(services => services
.PostConfigure<JwtBearerOptions>("JWT",
op => op.TokenValidationParameters.ValidateLifetime = false)
// other setup
).Build();
Also it is possible to pass null instead of "JWT" as it written in comments:
// Null name is used to configure all named options.

Related

Self Hosted Asp Net Core Web server, client authentication with self-signed certificates

I am testing a self-hosted Asp Net Core Web server (Kestrel), and I am struggling with the client authentication using self-signed certificates.
This is my startup code
WebApplicationBuilder webBuilder = WebApplication.CreateBuilder();
var webHostBuilder = builder.WebHost;
X509Certificate2 rootCert = new X509Certificate2(hostCertFilePath, hostCertPassword);
webHostBuilder.ConfigureKestrel(o =>
{
o.ConfigureHttpsDefaults(o =>
{
o.ServerCertificate = rootCert;
o.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
});
webHostBuilder.UseKestrel(o =>
{
o.Listen(IPAddress.Parse(myHttpsEndPointIpAddr), myHttpsEndPointPort,
listenOptions =>
{
listenOptions.UseHttps();
});
o.Listen(IPAddress.Parse(myHttpEndPointIpAddr), myHttpEndPointPort);
});
var services = webBuilder.Services;
services.AddTransient<MyCustomCertificateValidationService>();
services
.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.AllowedCertificateTypes = CertificateTypes.SelfSigned;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService = context.HttpContext.RequestServices
.GetService<MyCustomCertificateValidationService>();
if (validationService.ValidateCertificate(context.ClientCertificate))
{
context.Success();
}
else
{
context.Fail("invalid cert");
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
context.Fail("invalid cert");
return Task.CompletedTask;
}
};
});
...
var app = webBuilder.Build();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
This my custom certification class
public class MyCustomCertificateValidationService
{
public bool ValidateCertificate(X509Certificate2 clientCertificate)
{
// todo: check certificate thumbnail
return false;
}
}
But even if MyCustomCertificateValidationService has a method ValidateCertificate() that returns false, the controller method is still called when a client accesses the url with the route to the controller method.
This is what is displayed in the log:
...
AspNetCore.Routing.EndpointRoutingMiddleware : Request matched endpoint ‘GetMyData…‘
AspNetCore.Authentication.Certificate.CertificateAuthenticationHandler : Certificate was not authenticated. Failure message: invalid cert
AspNetCore.Routing.EndpointMiddleware : Executing endpoint ‘GetMyData…‘
...
Any clue why the controller method is still called?
"There is a use-case for the application that in some test environment
also unauthorized calls (over http://...) should be allowed. I would
prefer to use, if possible, a settings parameter to decide dynamically
if http access is allowed or not, instead of "hardcode" it as
[Authorize] attribute"
Of course you can do that. There is a handy way to implement your requirement using middleware for sure. Please try the code snippe below:
Http/Https Request Middleare Based On Environment:
public class CustomHttpHttpsRequestMiddleware
{
private readonly RequestDelegate next;
public CustomHttpHttpsRequestMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
//env = "Production";
if (env == "Development")
{
await next(context);
}
else
{
if (!context.Request.IsHttps)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
await context.Response.WriteAsync("HTTPS required!");
}
}
}
}
Note: In application request context we are checking two important value, first if the request is secure means cIsHttps and the application environment, in Development environment we will allow http request. Therefore, other than, dev or any env based on our requirement we will reject http request.
Register Middleware on Program.cs:
app.UseMiddleware<CustomHttpHttpsRequestMiddleware>();
Note: Make sure you have followed the correct middleware order. In order to avoid short circuiting, you could place this middleware way down of your all current middleware.
Output:

How to mock Jwt bearer token for integration tests

I have created a microservice using .Net 5 which has some endpoints which can only be called with a jwtBearertoken.
The ConfigureServices and Configure methods in the StartUp class look like this:
public void ConfigureServices(IServiceCollection services)
{
ConfigureDatabaseServices(services);
ConfigureMyProjectClasses(services);
services.AddVersioning();
services.AddControllers();
services.AddAuthentication(_configuration);
// Add framework services.
var mvcBuilder = services
.AddMvc()
.AddControllersAsServices();
ConfigureJsonSerializer(mvcBuilder);
}
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment webEnv,
ILoggerFactory loggerFactory,
IHostApplicationLifetime applicationLifetime)
{
_logger = loggerFactory.CreateLogger("Startup");
try
{
app.Use(async (context, next) =>
{
var correlationId = Guid.NewGuid();
System.Diagnostics.Trace.CorrelationManager.ActivityId = correlationId;
context.Response.Headers.Add("X-Correlation-ID", correlationId.ToString());
await next();
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
applicationLifetime.ApplicationStopped.Register(() =>
{
LogManager.Shutdown();
});
}
catch (Exception e)
{
_logger.LogError(e.Message);
throw;
}
}
AuthenticationExtensions:
public static class AuthenticationExtensions
{
public static void AddAuthentication(this IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.Authority = configuration["Authorization:Authority"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
}
I'm using an authorization server for the microservice to validate the token.
After adding an [Authorize] attribute above the controllers postman returns 401 Unauthorized and the integration tests I had created before adding Authentication also return Unauthorized as expected.
Now I am trying to figure out how I can change my integration tests by adding a JwtBearerToken and mocking the response from the authorization server so my tests will pass again.
How can I achieve this?
My answer is not 100% integrated, because we will add an extra auth scheme. TL;DR: You are not testing if your auth works, but working around it.
It would be best to use an ACTUAL token, but perhaps this solution is a nice middle ground.
You could create another auth scheme like DevBearer where you can specify an account, for example if you send the auth header DevBearer Customer-John, the application would recognize you as Customer John.
I use this approach during development because it is very easy to just test different users quickly. My code looks something like this:
Startup.Auth.cs
private void ConfigureAuthentication(IServiceCollection services)
{
services.AddHttpContextAccessor();
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Audience = "Audience";
options.Authority = "Authority";
});
#if DEBUG
if (Environment.IsDevelopment())
{
AllowDevelopmentAuthAccounts(services);
return;
}
#endif
// This is custom and you might need change it to your needs.
services.AddAuthorization();
}
#if DEBUG
// If this is true, you can use the Official JWT bearer login flow AND Development Auth Account (DevBearer) flow for easier testing.
private static void AllowDevelopmentAuthAccounts(IServiceCollection services)
{
services.AddAuthentication("DevBearer").AddScheme<DevelopmentAuthenticationSchemeOptions, DevelopmentAuthenticationHandler>("DevBearer", null);
// This is custom and you might need change it to your needs.
services.AddAuthorization();
}
#endif
Custom Policies Hint
// Because my Policies/Auth situation is different than yours, I will only post a hint that you might want to use.
// I want to allow calls from the REAL flow AND DevBearer flow during development so I can easily call my API using the DevBearer flow, or still connect it to the real IDentityServer and front-end for REAL calls.
var policyBuilder = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser();
// The #IF adds an extra "security" check so we don't accidentally activate the development auth flow on production
#if DEBUG
if (_allowDevelopmentAuthAccountCalls)
{
policyBuilder.AddAuthenticationSchemes("DevBearer").RequireAuthenticatedUser();
}
#endif
return policyBuilder;
Auth handler
#if DEBUG
using System;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace NAMESPACE
{
public class DevelopmentAuthenticationHandler : AuthenticationHandler<DevelopmentAuthenticationSchemeOptions>
{
public DevelopmentAuthenticationHandler(
IOptionsMonitor<DevelopmentAuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Context.Request.Headers.TryGetValue("Authorization", out var authorizationHeader))
{
return AuthenticateResult.Fail("Unauthorized");
}
var auth = AuthenticationHeaderValue.Parse(authorizationHeader);
if (auth.Scheme == "Bearer")
{
// If Bearer is used, it means the user wants to use the REAL authentication method and not the development accounts.
return AuthenticateResult.Fail("Bearer requests should use the real JWT validation scheme");
}
// Dumb workaround for NSwag/Swagger: I can't find a way to make it automatically pass "DevBearer" in the auth header.
// Having to type DevBearer everytime is annoying. So if it is missing, we just pretend it's there.
// This means you can either pass "ACCOUNT_NAME" in the Authorization header OR "DevBearer ACCOUNT_NAME".
if (auth.Parameter == null)
{
auth = new AuthenticationHeaderValue("DevBearer", auth.Scheme);
}
IEnumerable<Claim> claims;
try
{
var user = auth.Parameter;
claims = GetClaimsForUser(user);
}
catch (ArgumentException e)
{
return AuthenticateResult.Fail(e);
}
var identity = new ClaimsIdentity(claims, "DevBearer");
var principal = new ClaimsPrincipal(identity);
// Add extra claims if you want to
await Options.OnTokenValidated(Context, principal);
var ticket = new AuthenticationTicket(principal, "DevBearer");
return AuthenticateResult.Success(ticket);
}
private static IEnumerable<Claim> GetClaimsForUser(string? user)
{
switch (user?.ToLowerInvariant())
{
// These all depend on your needs.
case "Customer-John":
{
yield return new("ID_CLAIM_NAME", Guid.Parse("JOHN_GUID_THAT_EXISTS_IN_YOUR_DATABASE").ToString(), ClaimValueTypes.String);
yield return new("ROLE_CLAIM_NAME", "Customer", ClaimValueTypes.String);
break;
}
default:
{
throw new ArgumentException("Can't set specific account for local development because the user is not recognized", nameof(user));
}
}
}
}
public class DevelopmentAuthenticationSchemeOptions : AuthenticationSchemeOptions
{
public Func<HttpContext, ClaimsPrincipal, Task> OnTokenValidated { get; set; } = (context, principal) => { return Task.CompletedTask; };
}
}
#endif
With something like this, you could do an API call with an authorization header like DevBearer Customer-John and it would add the ID and role claim to the context, allowing auth to succeed :)

How to protect swagger endpoint in .NET Core API?

I have an api build in .net core 2.1. To restrict access to various endpoints, I use IdentityServer4 and [Authorize] attribute. However, my goal during development is to expose the api swagger documentation to our developers so that they may use it no matter where they work from. The challenge that I face is how do I protect the swagger index.html file so that only they can see the details of the api.
I have created a custom index.html file in the wwwroot/swagger/ui folder and that all works, however, that file uses data from /swagger/v1/swagger.json endpoint which is not protected. I would like to know how can I override the return value for that specific endpoint so that I may add my own authentication to it?
EDIT:
Currently, I have achieved the above with the following middleware:
public class SwaggerInterceptor
{
private readonly RequestDelegate _next;
public SwaggerInterceptor(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var uri = context.Request.Path.ToString();
if (uri.StartsWith("/swagger/ui/index.html"))
{
var param = context.Request.QueryString.Value;
if (!param.Equals("?key=123"))
{
context.Response.StatusCode = 404;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("{\"result:\" \"Not Found\"}", Encoding.UTF8);
return;
}
}
await _next.Invoke(context);
}
}
public class Startup
{
//omitted code
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<SwaggerInterceptor>();
//omitted code
}
}
What I don't like about this approach as it will inspect every single request. Is there a better way to achieve this? The above only protects the index.html file, but I can adjust it to protect the json endpoint in the similar fashion.
You can choose some options:
basic authorization
OpenId Connect authorization using identity server
Basic Authorization
In this case you just close your swagger endpoints.
// Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication()
.AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>("Basic", _ => {});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
...
var pipeline = endpoints.CreateApplicationBuilder().Build();
var basicAuthAttr = new AuthorizeAttribute { AuthenticationSchemes = "Basic" };
endpoints
.Map("/swagger/{documentName}/swagger.json", pipeline)
.RequireAuthorization(basicAuthAttr);
endpoints
.Map("/swagger/index.html", pipeline)
.RequireAuthorization(basicAuthAttr);
});
}
}
// BasicAuthenticationHandler.cs
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
...
}
OIDC Authorization Using IdentityServer4
I have written the article for this case: https://medium.com/dev-genius/csharp-protecting-swagger-endpoints-82ae5cfc7eb1
Here it is using OpenIdConnect and Swashbuckle in Asp.Net Core 3.1. Now, if I type https://myurl.com/swagger I get routed to my normal login page and after logging in successfully, I can see the swagger.
public class Startup
{
//<snip/>
public void Configure(IApplicationBuilder app)
{
//<snip/>
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("v1/swagger.json", "Some name"); });
app.UseEndpoints(routes =>
{
var pipeline = routes.CreateApplicationBuilder().Build();
routes.Map("/swagger", pipeline).RequireAuthorization(new AuthorizeAttribute {AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme});
routes.Map("/swagger/index.html", pipeline).RequireAuthorization(new AuthorizeAttribute {AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme});
routes.Map("/swagger/v1/swagger.json", pipeline).RequireAuthorization(new AuthorizeAttribute { AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme });
routes.Map("/swagger/{documentName}/swagger.json", pipeline).RequireAuthorization(new AuthorizeAttribute { AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme });
routes.MapDefaultControllerRoute();
});
}
}
EDIT:
Somehow I thought the below was working, but when I retested it later it turned out that actually it was giving error: The request reached the end of the pipeline without executing the endpoint. So, I changed to include a fixed set of endpoints under /swagger that contain afaik the key data.
routes.Map("/swagger/{**any}", pipeline).RequireAuthorization(new AuthorizeAttribute {AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme});
Note: this {**any} part of the route template protects all files under /swagger as well, so for example /swagger/index.html, /swagger/v1/swagger.json etc etc.
I believe your best option is what you already did. Build your own middleware, as I don't know any middleware for validate authentication on static files. You could add the basePath to avoid enter at this specific middleware when its not necessary. Like the code below
app.Map("/swagger", (appBuilder) =>
{
appBuilder.UseMiddleware<SwaggerInterceptor>();
});
Also this article could help you to build a more generic middleware for validate authentication on static files.
https://odetocode.com/blogs/scott/archive/2015/10/06/authorization-policies-and-middleware-in-asp-net-5.aspx
After some search I solved and it works for me.
you must define a middle-ware class to authenticate the user whom wants to visit your swagger-ui view page like this.
public class SwaggerBasicAuthMiddleware
{
private readonly RequestDelegate next;
public SwaggerBasicAuthMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/swagger"))
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic "))
{
// Get the credentials from request header
var header = AuthenticationHeaderValue.Parse(authHeader);
var inBytes = Convert.FromBase64String(header.Parameter);
var credentials = Encoding.UTF8.GetString(inBytes).Split(':');
var username = credentials[0];
var password = credentials[1];
// validate credentials
if (username.Equals("Test") && password.Equals("Test"))
{
await next.Invoke(context).ConfigureAwait(false);
return;
}
}
context.Response.Headers["WWW-Authenticate"] = "Basic";
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
await next.Invoke(context).ConfigureAwait(false);
}
}
}
For simplicity I am using hardcoded credentials but same can enhanced to use it from database also.
Also I have to create a extension method in a static class like this
public static class AuthorizedSampleClass
{
public static IApplicationBuilder UseSwaggerAuthorized( this IApplicationBuilder builder )
{
return builder.UseMiddleware<SwaggerBasicAuthMiddleware>( );
}
}
in startup.cs remove the env.IsDevelopment() part and add app.UseSwaggerAuthorized( ); right before app.UseSwagger( ); because authentication middleware will be called before accessing swagger ui.
app.UseSwaggerAuthorized( );
app.UseSwagger( );
app.UseSwaggerUI( c => c.SwaggerEndpoint( "/swagger/v1/swagger.json", "Broker.Rest v1" ) );
now press F5 and rout to swagger view, it has done.
press "Test" and "Test" as username and password to enter.

Manually call the authorization flow from a middleware in mvc

I've setup oauth authorization in an MVC aspnet core 2 application.
It works as intended when I use the [Authorize] attribute but I can't get it to work with my middleware RequestHandler.
I tried creating a service that calls the context.ChallengeAsync() method but it fails when called from the middleware (the call never redirects).
If the user isn't already logged in the consent page is never shown and the token returned is null.
If the user was already logged in the call returns the token.
The service does work when called from inside a controller (instead of using [Authorize])
So how do I get it to work ? I want to make sure the user is authorized (so that i have access to a token) before continuing...
Here are the relevant code sections:
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OAuthOptionsDefaults.SchemeName;
})
.AddCookie()
.AddOAuth(OAuthOptionsDefaults.SchemeName,
options => { options.SaveTokens = true; /*other options*/ });
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IOAuthService, OAuthService>();
services.AddTransient<IRequestHandler, RequestHandler>();
}
public void Configure(IApplicationBuilder app, IRequestHandler requestHandler, ..)
{
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseAuthentication();
app.Map(new PathString("/myapi"), appbuilder =>
{
appbuilder.Run(async (context) =>
{
await requestHandler.Handle(context, "url to an api", "an api key");
});
});
app.UseMvcWithDefaultRoute();
}
}
RequestHandler.cs
public class RequestHandler : IRequestHandler
{
//[Authorize] doesn't work
public async Task Handle(HttpContext context, string apiUrl, string apiKey)
{
//injected IOAuthService _service;
//tried with and without context as parameter
var accessToken = await _service.GetAccesTokenAsync();
//do stuff ...
}
}
OAuthService.cs
public class OAuthService : IOAuthService
{
private async Task<string> GetAccesTokenAsyncHelper(HttpContext context)
{
var isAuthenticated = ((ClaimsIdentity)context.User.Identity)?.IsAuthenticated ?? false;
if (!isAuthenticated)
{
//tried with and without setting schemename
await context.ChallengeAsync(OAuthOptionsDefaults.SchemeName);
}
return await context.GetTokenAsync(OAuthOptionsDefaults.SchemeName, "access_token");
}
public async Task<string> GetAccesTokenAsync()
{
//injected IHttpContextAccessor _accessor;
var context = _accessor.HttpContext;
return await GetAccesTokenAsyncHelper(context);
}
public async Task<string> GetAccesTokenAsync(HttpContext context)
{
return await GetAccesTokenAsyncHelper(context);
}
}
Edit: made the question shorter and more to point in the hopes of someone answering it.
You need to add Identity to the user property of HttpContext
context.User.AddIdentity((ClaimsIdentity) principal.Identity);
I have added a tutorial here explaining how to authorize HttpContext in Middleware with JWT from the URL
hope this will be helpful :)

How to log authentication result with OWIN Jwt Bearer Authentication

I want to log stats for all calls to my .netcore webapi.
I have added a IAsyncActionFilter for this purpose and it picks up on all the actions.
But I also have Jwt Bearer Authentication enabled and am using the AuthorizeAttribute on my controller to limit access. When access is denied the Action filter will not be hit.
Whats the best way to add some custom logging (statsd) for authentication in general and failures in particular?
public void ConfigureServices(IServiceCollection services)
{
....
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = Configuration["Authority"]; ;
// name of the API resource
options.Audience = "myAudience";
});
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication();
...
}
I Notice that JwtBearerOptions has JwtBearerEvents Events but I cant get this to work.
Edit : It looks like I am hitting the api with no token at all and the JWT Auth handler returns AuthenticateResult.NoResult() without calling the Events.AuthenticationFailed
https://github.com/aspnet/Security/blob/ba1eb281d135400436c52c17edc71307bc038ec0/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L63-L83
Edit 2 : Very frustrating. Looks like the correct place to log would be in Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter But this is automatically added when you use [Authorize] and is impossible to override, remove or replace?
The JwtBearerOptions.cs class exposes an JwtBearerEvents parameter where you can declare your events like this
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = Configuration["Authority"]; ;
// name of the API resource
options.Audience = "myAudience";
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
//Log failed authentications
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
//Log successful authentications
return Task.CompletedTask;
}
};
});
I don´t have any experience with Jwt Bearer Authentication - but we´ve had a similar situation. We´ve ended up with a new BaseController where we´re able to Log basic user information and distinguish between [AllowAnonymous] and [Authorize]:
public class NewBaseController : Controller
{
protected UserManager<MyUser> UserManager;
protected MyUser CurrentUser;
protected ILogger Logger;
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
UserManager = context.HttpContext.RequestServices.GetService<UserManager<MyUser>>();
var loggerFactory = context.HttpContext.RequestServices.GetService<ILoggerFactory>();
Logger = loggerFactory.CreateLogger(GetType());
// Check if Action is annotated with [AllowAnonymous]
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
var anonymousAllowed = controllerActionDescriptor.MethodInfo
.GetCustomAttributes(inherit: true)
.Any(a => a.GetType().Equals(typeof(AllowAnonymousAttribute)));
if (!anonymousAllowed)
{
ApplicationUser = UserManager.GetUserAsync(User).Result;
if (ApplicationUser == null)
// do some stuff
Logger.LogInformation("User is {UserId}", CurrentUser.Id);
}
else
{
Logger.LogInformation("User is {User}", anonymous);
}
}
}
Bonus: Every Controller that derives from this base already has an instance of UserManager, ILogger and CurrentUser.
Hope that comes close to what you´re looking for...

Categories

Resources