I've written a fairly basic AuthenticationHandler<T>
derived class for performing custom authentication for my REST services.
I had assumed (yes, I know, bad idea) that ApplyResponseChallengeAsync
would only be called if I actually need to apply my challenge - e.g. it's described as:
Override this method to dela(sic) with 401 challenge concerns, if an authentication scheme in question deals an authentication interaction as part of it's request flow. (like adding a response header, or changing the 401 result to 302 of a login page or external sign-in location.)
Which sounded to me that it would only be called if a 401 was being issued. However, in some limited testing, we're seeing some exceptions as follows:
System.Web.HttpException (0x80004005): Server cannot append header after HTTP headers have been sent.
at System.Web.HttpHeaderCollection.SetHeader(String name, String value, Boolean replace)
at Microsoft.Owin.Host.SystemWeb.CallHeaders.AspNetResponseHeaders.Set(String key, String[] values)
at Microsoft.Owin.Infrastructure.OwinHelpers.AppendHeader(IDictionary`2 headers, String key, String values)
at OurAuthHandler.ApplyResponseChallengeAsync()
at Microsoft.Owin.Security.Infrastructure.AuthenticationHandler.<ApplyResponseCoreAsync>d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Owin.Security.Infrastructure.AuthenticationHandler.<TeardownAsync>d__5.MoveNext()
--- And so on
So, wanting to investigate this, I changed the code inside our method slightly so that I could use the debugger to inspect the circumstances for this exception occurring:
protected override Task ApplyResponseChallengeAsync()
{
try
{
foreach (var uri in Options.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris)
{
Response.Headers.Append("WWW-Authenticate", "Bearer realm=\"" + uri + "\"");
}
return base.ApplyResponseChallengeAsync();
}
catch
{
throw; //Set a breakpoint here
}
}
And, lo and behold, when my breakpoint is hit, I'm seeing that the Response
s status code is 200/OK.
So, the question is, am I meant to have to check the status code myself, is there some flag I have to pass/set somewhere so that this method is only called for 401s, or am I missing something else?
Yes, you have to check the status code yourself. The documentation is misleading.
Note how each of the existing AuthenticationHandler
s in the Katana project check the status code, too:
public class OpenIdConnectAuthenticationHandler : AuthenticationHandler<OpenIdConnectAuthenticationOptions>
{
...
protected override async Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
....
}
}
...
}
internal class TwitterAuthenticationHandler : AuthenticationHandler<TwitterAuthenticationOptions>
{
...
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream.Dispose is idempotent")]
protected override async Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode != 401)
{
return;
}
}
...
}
public class WsFederationAuthenticationHandler : AuthenticationHandler<WsFederationAuthenticationOptions>
{
...
protected override async Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
...
}
}
...
}
I also checked the source code of the Katana project: there's no way to change this behaviour via a flag or something.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With