Ewan
Ewan

Reputation: 1285

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?

Upvotes: 6

Views: 5649

Answers (2)

Marcus Höglund
Marcus Höglund

Reputation: 16801

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;
            }
        };

    });

Upvotes: 7

Joshit
Joshit

Reputation: 1325

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...

Upvotes: 0

Related Questions