JianYA
JianYA

Reputation: 3024

ASP.NET Web Api 2 automatically add header on exception

I am currently following this tutorial to implement Jwt Refresh Tokens. Currently I'm trying to add a header called Token-Expired : "true" when I get a specific exception when responding to an API request.

In the tutorial, this section shows how to do it in the Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    //...

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = "bearer";
        options.DefaultChallengeScheme = "bearer";
    }).AddJwtBearer("bearer", options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,
            ValidateIssuer = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("the server key used to sign the JWT token is here, use more than 16 chars")),
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero //the default for this setting is 5 minutes
        };
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                {
                    context.Response.Headers.Add("Token-Expired", "true");
                }
                return Task.CompletedTask;
            }
        };
    });
}

The problem is I am using ASP.NET Web Api 2 and not .net core 2.1. How can I add this code to mine? One way that I think might work is that I can add it in my TokenValidation class but I don't know how to do so:

public class TokenValidationHandler : DelegatingHandler
{
    private static bool RetrieveToken(HttpRequestMessage request, out string token)
    {
        token = null;
        IEnumerable<string> authHeaders;
        if (!request.Headers.TryGetValues("Authorization", out authHeaders) || authHeaders.Count() > 1)
        {
            return false;
        }
        var bearerToken = authHeaders.ElementAt(0);
        token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
        return true;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpStatusCode statusCode;
        string token;
        //determine whether a jwt exists or not
        if (!RetrieveToken(request, out token))
        {
            statusCode = HttpStatusCode.Unauthorized;
            //allow requests with no token - whether a action method needs an authentication can be set with the claimsauthorization attribute
            return base.SendAsync(request, cancellationToken);
        }

        try
        {
            const string sec = HostConfig.SecurityKey;
            var now = DateTime.UtcNow;
            var securityKey = new SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(sec));


            SecurityToken securityToken;
            JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
            TokenValidationParameters validationParameters = new TokenValidationParameters()
            {
                ValidAudience = HostConfig.Audience,
                ValidIssuer = HostConfig.Issuer,
                //Set false to ignore expiration date
                ValidateLifetime = false,
                ValidateIssuerSigningKey = true,
                LifetimeValidator = this.LifetimeValidator,
                IssuerSigningKey = securityKey
            };
            //extract and assign the user of the jwt
            Thread.CurrentPrincipal = handler.ValidateToken(token, validationParameters, out securityToken);
            HttpContext.Current.User = handler.ValidateToken(token, validationParameters, out securityToken);

            return base.SendAsync(request, cancellationToken);
        }
        catch (SecurityTokenValidationException e)
        {
            statusCode = HttpStatusCode.Unauthorized;
        }
        catch (Exception ex)
        {
            statusCode = HttpStatusCode.InternalServerError;
        }
        return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode) { });
    }

    public bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
    {
        if (expires != null)
        {
            if (DateTime.UtcNow < expires) return true;
        }
        return false;
    }
}

Upvotes: 2

Views: 557

Answers (2)

Vetri Selvan
Vetri Selvan

Reputation: 235

Please add one more SecurityTokenExpiredException catch block to catch the token expiration error and add response header inside the catch block like below.

public class TokenValidationHandler : DelegatingHandler
{
    private static bool RetrieveToken(HttpRequestMessage request, out string token)
    {
        token = null;
        IEnumerable<string> authHeaders;
        if (!request.Headers.TryGetValues("Authorization", out authHeaders) || authHeaders.Count() > 1)
        {
            return false;
        }
        var bearerToken = authHeaders.ElementAt(0);
        token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
        return true;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = new HttpResponseMessage();
        string token;
        //determine whether a jwt exists or not
        if (!RetrieveToken(request, out token))
        {
            response.StatusCode = HttpStatusCode.Unauthorized;
            //allow requests with no token - whether a action method needs an authentication can be set with the claimsauthorization attribute
            return base.SendAsync(request, cancellationToken);
        }

        try
        {
            const string sec = HostConfig.SecurityKey;
            var now = DateTime.UtcNow;
            var securityKey = new SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(sec));


            SecurityToken securityToken;
            JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
            TokenValidationParameters validationParameters = new TokenValidationParameters()
            {
                ValidAudience = HostConfig.Audience,
                ValidIssuer = HostConfig.Issuer,
                //Set false to ignore expiration date
                ValidateLifetime = false,
                ValidateIssuerSigningKey = true,
                LifetimeValidator = this.LifetimeValidator,
                IssuerSigningKey = securityKey
            };
            //extract and assign the user of the jwt
            Thread.CurrentPrincipal = handler.ValidateToken(token, validationParameters, out securityToken);
            HttpContext.Current.User = handler.ValidateToken(token, validationParameters, out securityToken);

            return base.SendAsync(request, cancellationToken);
        }
        catch (SecurityTokenExpiredException e)
        {
            var expireResponse = base.SendAsync(request, cancellationToken).Result;
            response.Headers.Add("Token-Expired", "true");
            response.StatusCode = HttpStatusCode.Unauthorized;

        }
        catch (SecurityTokenValidationException e)
        {
            response.StatusCode = HttpStatusCode.Unauthorized;
        }
        catch (Exception ex)
        {
            response.StatusCode = HttpStatusCode.InternalServerError;
        }
        return Task<HttpResponseMessage>.Factory.StartNew(() => response);
    }

    public bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
    {
        if (expires != null)
        {
            if (DateTime.UtcNow < expires) return true;
        }
        return false;
    }


}

Upvotes: 1

PrathapG
PrathapG

Reputation: 789

You also need Microsoft.Owin.Host.SystemWeb package to install first.Then Create One Class With Name Startup.cs

this will help you..

public class Startup
{
   public void Configuration(IAppBuilder app)
   {
      //
   }
   public void ConfigureServices(IServiceCollection services)
   {
    services.AddMvc();
    //...

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = "bearer";
        options.DefaultChallengeScheme = "bearer";
    }).AddJwtBearer("bearer", options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,
            ValidateIssuer = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("the server key used to sign the JWT token is here, use more than 16 chars")),
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero //the default for this setting is 5 minutes
        };
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                {
                    context.Response.Headers.Add("Token-Expired", "true");
                }
                return Task.CompletedTask;
            }
        };
    });
}
}

In addition to this, if your startup class is somehow not in your default name space, add a web config line to the <appSettings> area like: <add key="owin:AppStartup" value="[NameSpace].Startup" />

To use ConfigureServices method you need to have Build-in dependency injection is only available in ASP.NET Core. You will need to use third party IoC container such as -

Autofac for Web API

or

Ninject

For that get below library.

Microsoft.Extensions.DependencyInjection

Upvotes: 0

Related Questions