Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Can't set Response.StatusCode to 401 in JwtBearerEvents #1154

Closed
onedevteam opened this issue Mar 16, 2017 · 9 comments
Closed

Can't set Response.StatusCode to 401 in JwtBearerEvents #1154

onedevteam opened this issue Mar 16, 2017 · 9 comments
Labels

Comments

@onedevteam
Copy link

onedevteam commented Mar 16, 2017

I have following code in my Startup.Auth.cs partial:

        app.UseJwtBearerAuthentication(new JwtBearerOptions
          {
              IncludeErrorDetails = true,
              AutomaticAuthenticate = true,
              AutomaticChallenge = true,
              TokenValidationParameters = tokenValidationParameters,
              Events = new JwtBearerEvents()
              {
                  OnAuthenticationFailed = c =>
                  {
                      c.HandleResponse();
                      c.Response.StatusCode = xxx;
                      c.Response.ContentType = "text/plain";
                      return c.Response.WriteAsync("Hello an error!");
                  } 
              }
          });

Goal is to intercept unauthorized requests and return 401 code instead of login form for API requests.

Problem is, when i set c.response.statuscode to 401, i get runtime exception

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

Every other response works (200, 201, 202, 300, 400, 402, 403... etc.. tested with postman) except one i need.

@Eilon
Copy link
Contributor

Eilon commented Mar 16, 2017

@onedevteam can you show us the entire stack trace? This is definitely quite odd. It would be even more helpful if you can upload your app to GitHub so we can see what's going on.

@onedevteam
Copy link
Author

onedevteam commented Mar 17, 2017

Can't post whole project.

I'm following stormpath tutorial to implement jwt.

After some research, i managed to make it work by returning return Task.FromResult(c);. When i return c.Response.WriteAsync, OnAuthenticationFailed handler execute's 3 times and crash on 3rd attempt.

Here's exception:

{System.ObjectDisposedException: The response has been aborted due to an unhandled application exception. ---> System.InvalidOperationException: StatusCode cannot be set, response has already started.
   at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.ThrowResponseAlreadyStartedException(String value)
   at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.set_StatusCode(Int32 value)
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.<HandleUnauthorizedAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.<HandleAutomaticChallengeIfNeeded>d__57.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.<FinishResponseOnce>d__55.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.<OnStartingCallback>d__54.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.<FireOnStarting>d__177.MoveNext()
   --- End of inner exception stack trace ---
   at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.ThrowResponseAbortedException()
   at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.<InitializeResponseAwaited>d__192.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.<WriteAsyncAwaited>d__183.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.<HandleAuthenticateAsync>d__1.MoveNext()}

@Eilon
Copy link
Contributor

Eilon commented Mar 17, 2017

@onedevteam thanks for posting the stack trace.

@Tratcher any ideas?

@Tratcher Tratcher added the bug label Mar 17, 2017
@Tratcher
Copy link
Member

Tratcher commented Mar 17, 2017

Ok, that makes a bit more sense. OnAuthenticationFailed has executed and set a 401 (with a body) and returned. As the pipeline unwinds then the middleware calls

Bug: HandleAutomaticChallengeIfNeeded should check Response.HasStarted before calling HandleUnauthorizedAsync.

It's your OnAuthenticationFailed event that sets the 401, correct?

Workarounds:
A) OnAuthenticationFailed should not write to the response body.
B) OnAuthenticationFailed should call HttpContext.Authentication.Challenge("JwtBearer") rather than just setting a 401.
C) Disable JwtBearerOptions.AutomaticChallenge.

@onedevteam
Copy link
Author

@Tratcher It's your OnAuthenticationFailed event that sets the 401, correct?

Yes. I have checked it in step-by-step debugger, and on first pass, before i set it to 401, value of c.Response.StatusCode is 200. Then, on second pass, it's 401 (set manualy, in 1st pass), and in 3rd pass it throws an error...

I also tested with JwtBearerOptions.AutomaticChallenge, off, and it passed.

@renaudcalmont
Copy link

Hi, we just tried to analyse a similar issue today.
The root of the problem is in Security/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieAuthenticationEvents.cs where a method line #104 tries to guess wether the request is AJAX or not.
Since the MVC and WebAPI request now follow the same path, this (I imagine legacy) class catches and modifies the response just as it were a normal web request.
Maybe you should remove the cookie middleware from your pipeline?

@Eilon
Copy link
Contributor

Eilon commented Apr 6, 2017

Closing for the following reasons:

  1. The response body should not be written to from an auth event because there is no way to know what code will run after that - such as some other code that also wants to set a header, status code, etc.
  2. With the mega-change that @HaoK is working on, much of this behavior will be changing (such as no automatic challenges caused by a 401 status code), so this failure shouldn't happen anymore anyway.

@Eilon Eilon closed this as completed Apr 6, 2017
@Zonciu
Copy link

Zonciu commented Jul 3, 2017

@Eilon
I want to return the error information by using response body when server fails to authenticate the jwt, what should I do?

@Tratcher
Copy link
Member

Tratcher commented Jul 5, 2017

@Zonciu we don't track closed issues, please open a new issue and describe your scenario.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants