When you create an Asp.Net Core project with individual accounts option, by default, some identity endpoints are created for you. Some people explicitly create views for the endpoints and some do not touch that and let them remain as is. Usually, all identity endpoints are used rarely. In this case, the rest of the unused endpoints are still accessible by typing their routes despite not creating any link to them on the website. This situation poses a security challenge for the websites. Fortunately, some measures can be taken into consideration to address this challenge. This tutorial describes how you can do so by creating a middleware redirecting requests, pertaining to unused identity routes, to the website’s homepage.

1- Creating the middleware

Imagine that you have created a personal blog for yourself. In this blog, because you are the only blogger, there is no need to let people register on your website. In this case, the only endpoints that you usually use are login and logout endpoints. Therefore, we want to create a middleware that redirect all routes to identity area except login and logout. The following is the source of the middleware written with the best practices mentioned in Microsoft’s documentation. As you can see, the middleware simply inspects the identity area. If the area is identity and the rest of the route does not belong to the collection of allowed routes, it simply redirects the request to the website’s homepage.

public class InvalidIdentityAccessHandlerMiddleware
{
  private readonly RequestDelegate _nextRequestDelegate;     
  private readonly string _targetArea = "identity";
  private readonly IList<string> _targetAllowedRoutes = 
    new ReadOnlyCollection<string>
    (
      new List<string> 
      {
        "/account/login",
        "/account/logout"
      }
    );

  public InvalidIdentityAccessHandlerMiddleware(RequestDelegate nextRequestDelegate)
  {
    _nextRequestDelegate = nextRequestDelegate;
  }
  public async Task InvokeAsync(HttpContext httpContext)
  {
    var area = httpContext.GetRouteValue("area") as string;
    var page = httpContext.GetRouteValue("page") as string;

    if (area?.ToLower() == _targetArea &&
      !_targetAllowedRoutes.Contains(page?.ToLower()!))
    {
      httpContext.Response.Redirect("/home/index");
      return;
    }

    await _nextRequestDelegate(httpContext);
  }
}
public static class InvalidIdentityAccessHandlerMiddlewareExtensions
{
  public static IApplicationBuilder UseInvalidIdentityAccessHandler(
    this IApplicationBuilder applicationBuilder)
  {
    return applicationBuilder.UseMiddleware<InvalidIdentityAccessHandlerMiddleware>();
  }
}

2- Adding the middleware to the pipeline

Although Microsoft mentions the best place for adding custom middleware after the UseAuthorization middleware in the Program.cs, we can do our redirect just after UseRouting middleware to shorten the execution of pipeline. The reason here is that what our middleware does can be done before the authentication and authorization stages of the pipeline.

app.UseInvalidIdentityAccessHandler();
app.UseAuthentication();
app.UseAuthorization();

3- Testing the middleware

In the below test cases, if the given area is identity and the rest of the route is equal to login and logout routes, the 200 HTTP status code is returned. Otherwise, the 302 status code (redirect code) is returned.

[Fact]
public async Task InvalidIdentityAccessHandlerMiddlewareShould_Redirect_GivenNotAllowedRoutes()
{
  //Arrange
  var middleware = new InvalidIdentityAccessHandler(
    nextRequestDelegate: (innerHttpContext) =>
    {
      return Task.CompletedTask;
    }
  );

  var context = new DefaultHttpContext();
  context.Response.Body = new MemoryStream();

  context.Request.RouteValues = new RouteValueDictionary
  {
      { "area", "identity"},
      { "page", "/account/Register" }
  };

  //Act
  await middleware.InvokeAsync(context);
       
  //Assert
  Assert.Equal(StatusCodes.Status302Found, context.Response.StatusCode);
}
[Fact]
public async Task InvalidIdentityAccessHandlerMiddlewareShould_Return200_GivenLoginRoute()
{
  //Arrange
  var middleware = new InvalidIdentityAccessHandler(
    nextRequestDelegate: (innerHttpContext) =>
    {
      return Task.CompletedTask;
    }
  );

  var context = new DefaultHttpContext();
  context.Response.Body = new MemoryStream();

  context.Request.RouteValues = new RouteValueDictionary
  {
      { "area", "identity"},
      { "page", "/account/login" }
  };

  //Act
  await middleware.InvokeAsync(context);

  //Assert
  Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
}