Custom Lifetime Validation With AspNet.Security.OpenIdConnect.Server (ASP.NET vNext)
Asked Answered
B

1

1

I am using Visual Studio 2015 Enterprise Update 1 and ASP.NET vNext rc1-update1 to issue and consume JWT tokens as described here.

In our implementation we want to control token lifetime validation.

We tried several approaches, all of which had undesirable side effects. For example in one attempt we took over the TokenValidationParameters.TokenValidationParameters.LifetimeValidator event in the Configure method:

app.UseJwtBearerAuthentication
(
    options => 
    {
        options.TokenValidationParameters = new TokenValidationParameters()
        {
            LifetimeValidator = (DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) =>
            {
                // Pretend to do custom validation
                return false;
            }
        };
    }
);

That event causes validation to fail as we'd like but the client receives a 500 error whereas we would like to return a 400-series error and a small payload instead.

In another attempt we tried various implementations of TokenValidationParameters.Events, such as inspecting claims in the ValidatedToken event but found we were unable to prevent the middleware from invoking the controller action short of throwing an exception which got us back to the 500 error problem.

So my questions are:

  • What what is the best practices for taking over lifetime validation with OIDC?

  • Can we force OIDC not to include certain lifetime claims in the token like "nbf" since we won't need them anyway?

Bumpy answered 9/12, 2015 at 21:20 Comment(0)
S
1

Edit: this bug was fixed in ASP.NET Core RC2. The workaround described in this answer is no longer needed.


It's a known bug. Sadly, the workaround you could use in beta8 no longer works in RC1.

Your only option is to write a middleware catching the exception to prevent the server from returning a 500 response. Of course, it's ugly and will potentially hide important exceptions, but it's the only known workaround that works with RC1.

Here's an example (make sure to register it before the JWT bearer middleware):

app.Use(next => async context => {
    try {
        await next(context);
    }

    catch {
        // If the headers have already been sent, you can't replace the status code.
        // In this case, throw an exception to close the connection.
        if (context.Response.HasStarted) {
            throw;
        }

        context.Response.StatusCode = 401;
    }
});
Shad answered 9/12, 2015 at 22:14 Comment(4)
Thanks, @Pinpoint. Can you also please tell me if we can control which lifetime claims are included in the token that OIDC is generating? For example can we remove "iat", "nbf", etc.?Bumpy
You don't have direct access to these non-user claims, but there are better ways to control the lifetime of the tokens issued by the OIDC server: the easiest one is via the options (by updating AccessTokenLifetime/IdentityTokenLifetime/RefreshTokenLifetime). Another one is via the SerializeAccessToken event, where you can update the IssuedUtc/ExpiresUtc properties associated with the authentication ticket (via context.AuthenticationTicket.Properties).Fit
For anyone else reading these comments we found that catching SecurityTokenInvalidLifetimeException in the sample code above met our needs exactly.Bumpy
To avoid a 500 response when the access token is expired, catching SecurityTokenInvalidLifetimeException will work. But the security token handler also throws other types of exceptions (like InvalidOperationException) when the access token is malformed or altered.Fit

© 2022 - 2024 — McMap. All rights reserved.