Response is null if middleware return AuthenticateResult.Fail()

Tanul 1,251 Reputation points
2020-12-18T07:16:12.683+00:00

Team,

I have a custom authentication handler like this

 public class MyAuthenticationSchemeOptions: AuthenticationSchemeOptions
    {

    }
 public class MyAuthenticationHandler: AuthenticationHandler<MyAuthenticationSchemeOptions>
    {
        private ICacheProviderService _cacheService;
        private readonly IConfiguration _configuration;
        public MyAuthenticationHandler(
            IOptionsMonitor<MyAuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            ICacheProviderService cacheService,
            IConfiguration configuration)
            : base(options, logger, encoder, clock)
        {
            _cacheService = cacheService;
            _configuration = configuration;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
           if (!Request.Headers.ContainsKey("Header")){
                 Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                //await Context.Response.WriteAsync("request doesn't contains header");
                return AuthenticateResult.Fail("request doesn't contains header");            
              }
    //further logic of success
        }

  public static class AuthenticateExtensions
    {
        public static AuthenticationBuilder AddAuth(this AuthenticationBuilder builder, Action<GlobalGroupAuthenticationSchemeOptions> configureOptions, string scheme)
        {
            return builder.AddScheme<MyAuthenticationSchemeOptions, MyAuthenticationHandler>(scheme,"abcd", configureOptions);
        }
    }

Controller is decorated with

[Authorize(AuthenticationSchemes = "MyAuthenticationScheme")]

Startup.cs

ConfigureServices(IServiceCollection services){
services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = "MyAuthenticationScheme";
                options.DefaultChallengeScheme = "MyAuthenticationScheme";
            }).AddAuth(x => { }, "MyAuthenticationScheme");
}

Configure(IApplicationBuilder app, IWebHostEnvironment env,ILogger<Startup> logger){
        //Other logic is removed as not required here
        app.UseRouting();            
        app.UseAuthorization();
}

This code returns only status code as Unauthorized. None of the error message returns in response if request doesn't have header. The moment I uncomment this line await Context.Response.WriteAsync("request doesn't contains header"); I immediately receive an unhandled exception. Any suggestion please on the way to return a message in response in the case of custom AuthenticationHandler service

info: Middleware.MyAuthenticationHandler[7]
   MyAuthenticationScheme was not authenticated. Failure message: Authentication Header Not Found
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: StatusCode cannot be set because the response has already started.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException(String value)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode(Int32 value)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode(Int32 value)
         at Microsoft.AspNetCore.Http.DefaultHttpResponse.set_StatusCode(Int32 value)
         at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.HandleChallengeAsync(AuthenticationProperties properties)
         at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.ChallengeAsync(AuthenticationProperties properties)
         at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
         at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
warn: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[2]
      The response has already started, the error handler will not be executed.
fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HM53499HNTTT", Request id "0HM53499HNTTT:00000002": An unhandled exception was thrown by the application.
      System.InvalidOperationException: StatusCode cannot be set because the response has already started.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException(String value)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode(Int32 value)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode(Int32 value)
         at Microsoft.AspNetCore.Http.DefaultHttpResponse.set_StatusCode(Int32 value)
         at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.HandleChallengeAsync(AuthenticationProperties properties)
         at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.ChallengeAsync(AuthenticationProperties properties)
         at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
         at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,158 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,237 questions
.NET Runtime
.NET Runtime
.NET: Microsoft Technologies based on the .NET software framework.Runtime: An environment required to run apps that aren't compiled to machine language.
1,119 questions
0 comments No comments
{count} votes

Accepted answer
  1. Michael Wang-MSFT 1,051 Reputation points
    2020-12-18T09:29:52.537+00:00

    Hi, @Tanul

    System.InvalidOperationException: StatusCode cannot be set, response has already started.>

    According to the description above, I have a suggestion that you could check if the 'response has already started' before setting StatusCode: The code is shown below:

     protected override async Task<AuthenticateResult> HandleAuthenticateAsync()  
            {  
                if (!Request.Headers.ContainsKey("Header"))  
                {  
    
                    if (!Context.Response.HasStarted)  
                    {  
                        string result;  
                        Context.Response.StatusCode = StatusCodes.Status401Unauthorized;  
                        result = JsonConvert.SerializeObject(new { error = "Request doesn't contains header" });  
                        Context.Response.ContentType = "application/json";  
                        await Context.Response.WriteAsync(result);  
                    }  
                    else  
                    {  
                        await Context.Response.WriteAsync(string.Empty);  
                    }  
      
                    return AuthenticateResult.Fail("request doesn't contains header");  
                }  
                //further logic of success  
            }  
    
          
    

    If the answer doesn’t solve your issue, please provide more details of error that will help us track down what’s happening.


    If the answer is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best Regards,
    Michael Wang


1 additional answer

Sort by: Most helpful
  1. Tanul 1,251 Reputation points
    2020-12-18T18:25:25.97+00:00

    @Michael Wang-MSFT , Hey Michael. Thanks for helping here. Actually if we call either of these 2

     1. Context.Response.WriteAsync  
     2. AuthenticateResult.Fail  
    

    They start the response and lock the stream hence do not allow to re-write again which is a correct behavior. So, to resolve this rather that using WriteAsync I have updated the response using custom Func in event Context.Response.OnStarting

    Now, it intercept the response after completion of middleware and update my request and working as expected. Thank you. Take care. :)

    0 comments No comments