Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return error on invalid or expired token

I'm trying to implement OAuth Bearer Authentication with Owin. When an invalid or expired token is passed, the default implementation is to log this as a warning and just don't set an Identity. I however would like to reject the whole request with an error in this case. But how would I do this?

After digging through the code I found out that in OAuthBearerAuthenticationHandler it will parse the token using a fallback mechanism when the provided AuthenticationTokenProvider did not parse any ticket (like the default implementation). This handler will log a warning when the token could not be parsed to any ticket or when it expired.

But I can't find any place to plug in my own logic to what happens when the token is invalid or expired. I could theoretically check this on my own in the AuthenticationTokenProvider, but then I would have to reimplement the logic (= copy it over) for creating and reading the token. Also this seems just out of place, as this class seems to be only responsible for creating and parsing tokens. I also don't see a way to plug in my own implementation of the OAuthBearerAuthenticationHandler in the OAuthBearerAuthenticationMiddleware.

Apparently my best and cleanest shot would be to reimplement the whole middleware, but this also seems very overkill.

What do I overlook? How would I go on about this the best?

edit:

For clarification. I know by not setting an identity the request will be rejected with 401 Unauthorized later in the Web API. But I personally see this as really bad style, silently swallowing an erroneous access token without any notification. This way you don't get to know that your token is crap, you just get to know you're not authorized.

like image 966
user3137652 Avatar asked Apr 03 '14 14:04

user3137652


2 Answers

I came across this problem recently. We wanted to return a JSON message if the user's access token had expired, allowing the consumer web application to silently refresh the access token and re-issue the API request. We also didn't want to rely on the exceptions thrown for token lifetime validation.

Not wanting to re-implement any middleware, we specified the Provider option inside JwtBearerAuthenticationOptions and added a delegate to handle the OnRequestTokenMethod. The delegate checks to see if it can read the token passed to the middleware and sets a boolean inside the OWIN context if it's expired.

app.UseJwtBearerAuthentication(
             new JwtBearerAuthenticationOptions
             {
                 AuthenticationMode = AuthenticationMode.Active,
                 TokenValidationParameters = tokenValidationParameters,                                             
                 Provider = new OAuthBearerAuthenticationProvider
                 {                         
                     OnRequestToken = (ctx) =>
                     {                             
                         if (!string.IsNullOrEmpty(ctx.Token))
                         {
                             JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
                             if (handler.CanReadToken(ctx.Token))
                             {
                                 JwtSecurityToken jwtToken = handler.ReadJwtToken(ctx.Token);
                                 
                                 if (jwtToken.IsExpired())
                                     ctx.OwinContext.Set<bool>("expiredToken", true);
                             }                                                                  
                         }

                         return Task.CompletedTask;
                     }
                 }
             });            

For convenience I added a quick extension method to check if a JWT expired:

    public static class JwtSecurityTokenExtensions
    {        
        public static bool IsExpired (this JwtSecurityToken token)
        {
            if (DateTime.UtcNow > token.ValidTo.ToUniversalTime())
                return true;

            return false;
        }
    }

We ended up using a middleware to check on the state of that boolean:

app.Use((context, next) =>
        {
            bool expiredToken = context.Get<bool>("expiredToken");
            
            if (expiredToken)
            {
                // do stuff
            }

            return next.Invoke();
        });
        app.UseStageMarker(PipelineStage.Authenticate);

Not exactly the most efficient code, since we're parsing the token again after the middleware already did and also introducing a new middleware to act on the result of the check, but it's a fresh perspective nonetheless.

like image 194
Just a random Avatar answered Sep 20 '22 13:09

Just a random


I had a similar issue, i think the answer is to late but someone will come here with a similar problem:

I used this nuget package for validate authentication, but i think any method can help: https://www.nuget.org/packages/WebApi.AuthenticationFilter. You can read its documentation in this site https://github.com/mbenford/WebApi-AuthenticationFilter

AuthenticationFilter.cs

public class AuthenticationFilter : AuthenticationFilterAttribute{
public override void OnAuthentication(HttpAuthenticationContext context)
{
    System.Net.Http.Formatting.MediaTypeFormatter jsonFormatter = new System.Net.Http.Formatting.JsonMediaTypeFormatter();
    var ci = context.Principal.Identity as ClaimsIdentity;

    //First of all we are going to check that the request has the required Authorization header. If not set the Error
    var authHeader = context.Request.Headers.Authorization;
    //Change "Bearer" for the needed schema
    if (authHeader == null || authHeader.Scheme != "Bearer")
    {
        context.ErrorResult = context.ErrorResult = new AuthenticationFailureResult("unauthorized", context.Request,
            new { Error = new { Code = 401, Message = "Request require authorization" } });
    }
    //If the token has expired the property "IsAuthenticated" would be False, then set the error
    else if (!ci.IsAuthenticated)
    {
        context.ErrorResult = new AuthenticationFailureResult("unauthorized", context.Request,
            new { Error = new { Code = 401, Message = "The Token has expired" } });
    }
}}

AuthenticationFailureResult.cs

public class AuthenticationFailureResult : IHttpActionResult{
private object ResponseMessage;
public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request, object responseMessage)
{
    ReasonPhrase = reasonPhrase;
    Request = request;
    ResponseMessage = responseMessage;
}

public string ReasonPhrase { get; private set; }

public HttpRequestMessage Request { get; private set; }

public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
    return Task.FromResult(Execute());
}

private HttpResponseMessage Execute()
{
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
    System.Net.Http.Formatting.MediaTypeFormatter jsonFormatter = new System.Net.Http.Formatting.JsonMediaTypeFormatter();
    response.Content = new System.Net.Http.ObjectContent<object>(ResponseMessage, jsonFormatter);
    response.RequestMessage = Request;
    response.ReasonPhrase = ReasonPhrase;
    return response;
}}

Response examples:

{"Error":{"Code":401,"Message":"Request require authorization"}}

{"Error":{"Code":401,"Message":"The Token has expired"}}

Fonts and inspiration documentation:

//github.com/mbenford/WebApi-AuthenticationFilter

//www.asp.net/web-api/overview/security/authentication-filters

like image 40
jleon Avatar answered Sep 17 '22 13:09

jleon