For my ASP.NET MVC application I'm using ADFS authentication. It is set up in the following manner
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
MetadataAddress = ConfigurationManager.AppSettings.Get("MetadataAddress"),
Wtrealm = ConfigurationManager.AppSettings.Get("Realm"),
});
Due to something beyond my control, occasionally the metadata at the MetadataAddress is unreachable. In situations like that, I would like to redirect users to a custom view rather than the default error view. How would one accomplish this?
What I've ended up doing is creating Owin Middleware that captures the error that is thrown by invalid metadata that looks like the following and redirects the user to a route describing the issue:
public class LoggingMiddleware : OwinMiddleware
{
public LoggingMiddleware(OwinMiddleware next)
: base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
} catch(Exception e)
{
Logger.Error($"Encountered error in authenticating {e.ToString()}");
if(e.Source.Equals("Microsoft.IdentityModel.Protocols"))
{
context.Response.Redirect("/Home/OwinError");
} else
{
throw e;
}
}
}
}
The middleware can simply be added in the startup.cs file with the following line:
app.Use(typeof(LoggingMiddleware));
Related
In my ASP .NET Core 3.1 MVC app, I use endpoint routing like so
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "access",
pattern: "access/",
defaults: new { controller = "Home", action = "Access" });
});
So browsing to /access, launches the Access action, where the app checks if user complies with some access requirements.
if (access checks...)
{
return View();
}
Now I would prefer having this check in a custom middleware (or possibly a custom authorize attribute) instead of having it in the Controller. So my question to you is, how should I rewrite the UseEndPoints call, to include a custom middleware for the /access area?
Authorization policy extending [Authorize]
You could do this using authorization policies. Configure these in your Startup.cs inside ConfigureServices(IServiceCollection services) like so:
services.AddAuthorization(options =>
{
// Create your own policy and make the "access checks" in there
options.AddPolicy("MyAccessPolicy", policy => policy.RequireAssertion(httpCtx =>
{
if (access checks...)
return true;
else
return false;
}));
});
Then you simply decorate your controller action with the Authorize attribute like so:
[Authorize(Policy = "MyAccessPolicy")]
public IActionResult Access()
{
return View();
}
Now, whenever you try to go to /access this policy will run, and if the policy returns false, the user will be met with an HTTP 403 (Forbidden) status code.
Custom middleware mapped to route
In response to your comment here's an example of a middleware and how to map it to a specific route.
An example from my own project with a global error handling middleware (some irrelevant parts stripped out):
public class ExceptionHandlingMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
// Call next middleware
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception ex)
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
ErrorDetails error = null;
if (ex is FileNotFoundException || ex is DirectoryNotFoundException)
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
error = _localizer.FilesOrFoldersNotFound();
}
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(
new CustomResponse(false, error ?? _localizer.DefaultError()),
_serializerSettings));
}
}
To only use this middleware for specific routes you could do as suggested here:
// Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Map("path/where/error/could/happen",
b => b.UseMiddleware<ExceptionHandlingMiddleware>());
// ...
}
Or check the path inside the middleware itself:
// ExceptionHandlingMiddleware.cs
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (!context.Request.Path.StartsWithSegments("path/where/error/could/happen"))
{
// Skip doing anything in this middleware and continue as usual
await next(context);
return;
}
// Use middleware logic
try
{
// Call next middleware
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
You can Extending AuthorizeAttribute along with IAuthorizationFilter in Asp.Net Core
1.Create a class which extends AuthorizeAttribute, this will used on top of controller or action like Asp.Net core’s inbuilt [Authorize] attribute.
2.Implement the method OnAuthorization(AuthorizationFilterContext context) which is part of IAuthorizationFilter interface.
3.Call return keyword without any additional operation for authorized user.
4.Set AuthorizationFilterContext result as Unauthorized for unauthorized users as context.Result = new UnauthorizedResult()
public class SampleAuthorizePermission : AuthorizeAttribute, IAuthorizationFilter
{
public string Permissions { get; set; }
public void OnAuthorization(AuthorizationFilterContext context)
{
if (string.IsNullOrEmpty(Permissions))
{
context.Result = new UnauthorizedResult();
return;
}
var userName = context.HttpContext.User.Identity.Name;
var assignedPermissionsForUser =
MockData.UserPermissions
.Where(x => x.Key == userName)
.Select(x => x.Value).ToList();
var requiredPermissions = Permissions.Split(",");
foreach (var x in requiredPermissions)
{
if (assignedPermissionsForUser.Contains(x))
return;
}
context.Result = new UnauthorizedResult();
return;
}
}
and in your controller
[SampleAuthorizePermission(Permissions = "CanRead")]
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
Taking the middleware specific approach in .NET Core 3.1, we can conditionally add middleware using the following- In configure method-
app.UseWhen(
context=>context.Request.Path.StartsWithSegments("your-route-url"),
branch=>branch.useMiddleware(););
There are a few ways how the pipeline branching can happen, follow the docs for more information- https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0#branch-the-middleware-pipeline
I have this attribute:
public class ValidateCertAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext context)
{
// Read certificate from the HTTP Request and
// check its various attributes against known
// values from a config file.
if (true) // certificate is invalid
{
context.Response = new HttpResponseMessage(HttpStatusCode.Forbidden)
{
ReasonPhrase = "Invalid certificate"
};
}
else
{
base.OnAuthorization(context);
}
}
}
and this action:
[HttpGet]
[Route("TestAuth")]
[ValidateCert]
public HttpResponseMessage TestAuth()
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
ReasonPhrase = "In Test method without any authorization."
};
}
This action is inside a .NET Core Web Api controller:
[ApiController]
public class TestAuthController : ControllerBase
On the other hand, Startup.cs contains:
app.UseMvc();
so that seems odd, since this should just be a Web Api controlller, and not an MVC web application. Apparently, though, that's just for routing, but I thought it was worth mentioning.
I've set a breakpoint on the first line of the attribute code, but it's not getting hit. I believe it should get hit before the action executes and that the action should never execute because I'm setting the Response inside the attribute. Why isn't the attribute executing?
This can be accomplished using policy-based authorization.
The idea essentially is that you have a requirement that needs to be met (valid certificate), a handler for the requirement (how to validate the certificate) and a policy that enforces this requirement and is applied during authorization.
If your code is simple enough, you can just provide a Func<AuthorizationHandlerContext, bool> to the policy that is applied for evaluation. Here is how to setup the policy (in Startup.cs, ConfigureServices()):
services.AddAuthorization(options =>
{
options.AddPolicy("ValidateCertificate", policy =>
policy.RequireAssertion(context =>
{
var filterContext = (AuthorizationFilterContext)context.Resource;
var Response = filterContext.HttpContext.Response;
var message = Encoding.UTF8.GetBytes("Invalid certificate");
Response.OnStarting(async () =>
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await Response.Body.WriteAsync(message, 0, message.Length);
});
return false;
}));
});
This will do what you want.
Now, if you want to go the more structured route, you can implement each piece:
First, create your requirement (more like a marker for reference, really):
public class ValidCertificateRequirement : IAuthorizationRequirement
{
}
Then setup the policy that needs to be applied (Startup.cs, ConfigureServices()):
services.AddAuthorization(options =>
{
options.AddPolicy("ValidateCertificate", policy =>
{
policy.Requirements.Add(new ValidCertificateRequirement());
});
});
Now you need to create your requirement handler:
public class ValidCertificateHandler : AuthorizationHandler<ValidCertificateRequirement>
{
public ValidCertificateHandler()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidCertificateRequirement requirement)
{
var filterContext = (AuthorizationFilterContext)context.Resource;
var Response = filterContext.HttpContext.Response;
var message = Encoding.UTF8.GetBytes("Invalid certificate");
Response.OnStarting(async () =>
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await Response.Body.WriteAsync(message, 0, message.Length);
});
context.Fail();
return Task.CompletedTask;
}
}
The handler then needs to be registered on startup (in ConfigureServices()):
//Register handler
services.AddSingleton<IAuthorizationHandler, ValidCertificateHandler>();
Lastly, for either approach (assertion or implementation), apply the Authorize attribute to your actions, indicating the policy to apply:
[Authorize(Policy = "ValidateCertificate")]
public HttpResponseMessage TestAuth()
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
ReasonPhrase = "In Test method without any authorization."
};
}
You can read more about it here:
Policy-based authorization in ASP.NET Core
UPDATE: Unfortunately, a Windows reboot solved this issue -.-
In our ASP.NET Core (1.0 RC2) application, we have the following requirement: only users from the internal network should be able to access some "Debug" pages (hosted by MVC Core). It's a public website and we don't have user logins, instead we managed it until now with a custom IP-address based authorization (note: this is not a security risk in our case, because we have a proxy in between, so the IP address cannot be spoofed from outside).
We want to implement such an IP-address based authorization in ASP.NET Core, as well. We use a custom policy "DebugPages" for this and corresponding [Authorize(Policy="DebugPages")] definitions on the MVC controller. Then we noticed, that we must have an authenticated user to get the AuthorizeAttribute to jump in and we create one in the request pipeline, which yields to the following code in Startup.cs (shortened for brevity):
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(options =>
{
options.AddPolicy(
"DebugPages",
policy => policy.RequireAssertion(
async context => await MyIPAuthorization.IsAuthorizedAsync()));
});
}
public void Configure(IApplicationBuilder app)
{
...
app.Use(async (context, next) =>
{
context.User = new ClaimsPrincipal(new GenericIdentity("anonymous"));
await next.Invoke();
});
...
}
Now this works fine when run in Debug by Visual Studio 2015 (with IIS Express).
But unfortunately it doesn't work when run directly by dotnet run (with Kestrel) from the command line. In this case we get the following exception:
InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
The same error occurs when we provide the current Windows principal instead of the principal with a custom anonymous identity -- so everytime when the user is automatic-ally authenticated...
So, why is there a difference between hosting in IIS Express and Kestrel? Any suggestions how to solve the issue?
So, after some research, as I mentioned in the comments, I have found that httpContext.Authentication.HttpAuthhenticationFeature.Handler is null, when you starts the application under the "selfhosted" kestrel. But when you use IIS the Handler has instantiated by Microsoft.AspNetCore.Server.IISIntegration.AuthenticationHandler. This specific handler implementation is part of the .UseIISIntegration() in Program.cs.
So, I've decided to use a part of this implementation in my App and handle nonauthenticated requests.
For my WebAPI (without any Views) service I use IdentityServer4.AccessTokenValidation that uses behind the scenes OAuth2IntrospectionAuthentication and JwtBearerAuthentication.
Create files
KestrelAuthenticationMiddleware.cs
public class KestrelAuthenticationMiddleware
{
private readonly RequestDelegate _next;
public KestrelAuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var existingPrincipal = context.Features.Get<IHttpAuthenticationFeature>()?.User;
var handler = new KestrelAuthHandler(context, existingPrincipal);
AttachAuthenticationHandler(handler);
try
{
await _next(context);
}
finally
{
DetachAuthenticationhandler(handler);
}
}
private void AttachAuthenticationHandler(KestrelAuthHandler handler)
{
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
if (auth == null)
{
auth = new HttpAuthenticationFeature();
handler.HttpContext.Features.Set(auth);
}
handler.PriorHandler = auth.Handler;
auth.Handler = handler;
}
private void DetachAuthenticationhandler(KestrelAuthHandler handler)
{
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
if (auth != null)
{
auth.Handler = handler.PriorHandler;
}
}
}
KestrelAuthHandler.cs
internal class KestrelAuthHandler : IAuthenticationHandler
{
internal KestrelAuthHandler(HttpContext httpContext, ClaimsPrincipal user)
{
HttpContext = httpContext;
User = user;
}
internal HttpContext HttpContext { get; }
internal ClaimsPrincipal User { get; }
internal IAuthenticationHandler PriorHandler { get; set; }
public Task AuthenticateAsync(AuthenticateContext context)
{
if (User != null)
{
context.Authenticated(User, properties: null, description: null);
}
else
{
context.NotAuthenticated();
}
if (PriorHandler != null)
{
return PriorHandler.AuthenticateAsync(context);
}
return Task.FromResult(0);
}
public Task ChallengeAsync(ChallengeContext context)
{
bool handled = false;
switch (context.Behavior)
{
case ChallengeBehavior.Automatic:
// If there is a principal already, invoke the forbidden code path
if (User == null)
{
goto case ChallengeBehavior.Unauthorized;
}
else
{
goto case ChallengeBehavior.Forbidden;
}
case ChallengeBehavior.Unauthorized:
HttpContext.Response.StatusCode = 401;
// We would normally set the www-authenticate header here, but IIS does that for us.
break;
case ChallengeBehavior.Forbidden:
HttpContext.Response.StatusCode = 403;
handled = true; // No other handlers need to consider this challenge.
break;
}
context.Accept();
if (!handled && PriorHandler != null)
{
return PriorHandler.ChallengeAsync(context);
}
return Task.FromResult(0);
}
public void GetDescriptions(DescribeSchemesContext context)
{
if (PriorHandler != null)
{
PriorHandler.GetDescriptions(context);
}
}
public Task SignInAsync(SignInContext context)
{
// Not supported, fall through
if (PriorHandler != null)
{
return PriorHandler.SignInAsync(context);
}
return Task.FromResult(0);
}
public Task SignOutAsync(SignOutContext context)
{
// Not supported, fall through
if (PriorHandler != null)
{
return PriorHandler.SignOutAsync(context);
}
return Task.FromResult(0);
}
}
And in the Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMiddleware<KestrelAuthenticationMiddleware>();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = Configuration[AppConstants.Authority],
RequireHttpsMetadata = false,
AutomaticChallenge = true,
ScopeName = Configuration[AppConstants.ScopeName],
ScopeSecret = Configuration[AppConstants.ScopeSecret],
AutomaticAuthenticate = true
});
app.UseMvc();
}
When using ASP.Net WebAPI, I used to have a custom Authorize attribute I would use to return either an HTTP 403 or 401 depending on the situation. e.g. if the user is not authenticated, return a 401; if the user is authenticated but doesn't have the appropriate permissions, return a 403. See here for more discussion on that.
It seems now, in the new ASP.Net Core, they don't want you overriding the Authorize attribute anymore instead favoring a policy-based approach. However, it seems Core MVC suffers from the same "just return 401 for all auth errors" approach its predecessors have.
How do I override the framework to get the behavior I want?
After opening an issue here, it appears this actually should work...sort of.
In your Startup.Configure, if you just call app.UseMvc() and don't register any other middleware, you will get 401 for any auth-related errors (not authenticated, authenticated but no permission).
If, however, you register one of the authentication middlewares that support it, you will correctly get 401 for unauthenticated and 403 for no permissions. For me, I used the JwtBearerMiddleware which allows authentication via a JSON Web Token. The key part is to set the AutomaticChallenge option when creating the middleware:
in Startup.Configure:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
app.UseMvc();
AutomaticAuthenticate will set the ClaimsPrincipal automatically so you can access User in a controller. AutomaticChallenge allows the auth middleware to modify the response when auth errors happen (in this case setting 401 or 403 appropriately).
If you have your own authentication scheme to implement, you would inherit from AuthenticationMiddleware and AuthenticationHandler similar to how the JWT implementation works.
I ended up doing it with middleware:
public class AuthorizeCorrectlyMiddleware
{
readonly RequestDelegate next;
public AuthorizeCorrectlyMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
await next(context);
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
if (context.User.Identity.IsAuthenticated)
{
//the user is authenticated, yet we are returning a 401
//let's return a 403 instead
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}
which should be registered in Startup.Configure before calling app.UseMvc().
I followed the guide for Custom Authorization Policy Providers using IAuthorizationPolicyProvider in ASP.NET Core and also wanted to create a custom response.
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-5.0
The guide I followed for that was Customize the behavior of AuthorizationMiddleware
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/customizingauthorizationmiddlewareresponse?view=aspnetcore-5.0
My code finally looked like this:
public class GuidKeyAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler
DefaultHandler = new AuthorizationMiddlewareResultHandler();
public async Task HandleAsync(
RequestDelegate requestDelegate,
HttpContext httpContext,
AuthorizationPolicy authorizationPolicy,
PolicyAuthorizationResult policyAuthorizationResult)
{
if (policyAuthorizationResult.Challenged && !policyAuthorizationResult.Succeeded && authorizationPolicy.Requirements.Any(requirement => requirement is GuidKeyRequirement))
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
// Fallback to the default implementation.
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy,
policyAuthorizationResult);
}
}
Startup.cs:
services.AddSingleton<IAuthorizationMiddlewareResultHandler,
GuidKeyAuthorizationMiddlewareResultHandler>();
You can also edit your AuthorizationHandler and access httpContext
via IHttpContextAccessor. However this feels more like a hack.
internal class GuidKeyAuthorizationHandler : AuthorizationHandler<GuidKeyRequirement>
{
private readonly ILogger<GuidKeyAuthorizationHandler> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
public GuidKeyAuthorizationHandler(ILogger<GuidKeyAuthorizationHandler> logger, IHttpContextAccessor httpContextAccessor)
{
_logger = logger;
_httpContextAccessor = httpContextAccessor;
}
// Check whether a given GuidKeyRequirement is satisfied or not for a particular context
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, GuidKeyRequirement requirement)
{
var httpContext = _httpContextAccessor.HttpContext; // Access context here
var key = System.Web.HttpUtility.ParseQueryString(httpContext.Request.QueryString.Value).Get("key");
if (!string.IsNullOrWhiteSpace(key))
{
// If the user guid key matches mark the authorization requirement succeeded
if (Guid.TryParse(key, out var guidKey) && guidKey == requirement.Key)
{
_logger.LogInformation("Guid key is correct");
if (requirement.RequireRefererHeader)
{
_logger.LogInformation("Require correct referer header");
httpContext.Request.Headers.TryGetValue("Referer", out var refererHeader);
if (requirement.RefererHeader == refererHeader)
{
_logger.LogInformation("Referer header is correct");
context.Succeed(requirement);
return Task.CompletedTask;
}
else
{
_logger.LogInformation($"Referer header {refererHeader} is not correct");
}
}
else
{
_logger.LogInformation("Correct referer header is not needed");
context.Succeed(requirement);
return Task.CompletedTask;
}
}
else
{
_logger.LogInformation($"Guid key {guidKey} is not correct");
}
}
else
{
_logger.LogInformation("No guid key present");
}
var msg = "Invalid Guid";
var bytes = Encoding.UTF8.GetBytes(msg);
httpContext.Response.StatusCode = 403;
httpContext.Response.ContentType = "application/json";
httpContext.Response.Body.WriteAsync(bytes, 0, bytes.Length);
return Task.CompletedTask;
}
}
Found that solution here:
https://stackoverflow.com/a/61861098/3850405
I'm developing a Web API 2 application and I'm currently trying to format error resposnes in a uniform way (so that the consumer will also know what data object/structure they can inspect to get more info about the errors). This is what I've got so far:
{
"Errors":
[
{
"ErrorType":5003,
"Message":"Error summary here",
"DeveloperAction":"Some more detail for API consumers (in some cases)",
"HelpUrl":"link to the docs etc."
}
]
}
This works fine for exceptions thrown by the application itself (i.e inside controllers). However, if the user requests a bad URI (and gets a 404) or uses the wrong verb (and gets a 405) etc, Web Api 2 spits out a default error message e.g.
{
Message: "No HTTP resource was found that matches the request URI 'http://localhost/abc'."
}
Is there any way of trapping these kinds of errors (404, 405 etc.) and formatting them out into the error response in the first example above?
So far I've tried:
Custom ExceptionAttribute inherting ExceptionFilterAttribute
Custom ControllerActionInvoker inherting ApiControllerActionInvoker
IExceptionHandler (new Global Error Handling feature from Web API 2.1)
However, none of these approaches are able to catch these kinds of errors (404, 405 etc). Any ideas on how/if this can be achieved?
...or, am I going about this the wrong way? Should I only format error responses in my particular style for application/user level errors and rely on the default error responses for things like 404?
You can override the DelegatingHandler abstract class and intercept the response to the client. This will give you the ability to return what you want.
Here's some info on it.
http://msdn.microsoft.com/en-us/library/system.net.http.delegatinghandler(v=vs.118).aspx
Here's a poster of the Web Api pipeline that shows what can be overriden.
http://www.asp.net/posters/web-api/asp.net-web-api-poster.pdf
Create a Handler class like this to override the response
public class MessageHandler1 : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine("Process request");
// Call the inner handler.
var response = base.SendAsync(request, cancellationToken);
Debug.WriteLine("Process response");
if (response.Result.StatusCode == HttpStatusCode.NotFound)
{
//Create new HttpResponseMessage message
}
;
return response;
}
}
In your WebApiConfig.cs class add the handler.
config.MessageHandlers.Add(new MessageHandler1());
UPDATE
As Kiran mentions in the comments you can use the OwinMiddleware to intercept the response going back to the client. This would work for MVC and Web Api running on any host.
Here's an example of how to get the response and change it as it goes to the client.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(MyMiddleware));
}
}
public class MyMiddleware : OwinMiddleware
{
public MyMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
if(context.Response.StatusCode== 404)
{
context.Response.StatusCode = 403;
context.Response.ReasonPhrase = "Blah";
}
}
}
I have done in same way as #Dan H mentioned
public class ApiGatewayHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.NotFound)
{
var objectContent = response.Content as ObjectContent;
return await Task.FromResult(new ApiResult(HttpStatusCode.NotFound, VmsStatusCodes.RouteNotFound, "", objectContent == null ? null : objectContent.Value).Response());
}
return response;
}
catch (System.Exception ex)
{
return await Task.FromResult(new ApiResult(HttpStatusCode.BadRequest, VmsStatusCodes.UnHandledError, ex.Message, "").Response());
}
}
}
Added routing like below and now it hits the try catch for invalid url
config.Routes.MapHttpRoute(name: "DefaultApi",routeTemplate: "api/{controller}/{id}",defaults: new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(name: "NotFound", routeTemplate: "api/{*paths}", defaults: new { controller = "ApiError", action = "NotFound" });