How to customize error code in custom AuthenticationHandler - c#

There is a customized AuthenticationHandler named CustomAuthenticationHandler which default error code is 401. But I have to response with different error code and error message in different conditions.
If the request should response 403 in some condition and current solution shows below:
public class CustomAuthenticationHandler: AuthenticationHandler<MSGraphAuthenticationOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (xxx)
{
var response = this.httpContextAccessor.HttpContext.Response;
response.StatusCode = StatusCodes.Status403Forbidden;
await response.WriteAsync("test");
return AuthenticateResult.NoResult();
}
}
}
The error code of the http response is 403 which is expected, but the reqeust still run into next() and it would throw error:
System.InvalidOperationException: StatusCode cannot be set because the response has already started.
at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContext.ThrowResponseAlreadyStartedException(String name)
at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContext.set_StatusCode(Int32 value)
at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContext.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.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.Management.Services.CloudPC.Api.Middlewares.MetricsMiddleware.InvokeAsync(HttpContext context, ILoggerX logger)
How can I stop the middlerware flow after await response.WriteAsync("test");?

If your authentication fails, you should call AuthenticateResult.Fail("<your custom message here>"); so the rest of the pipeline doesn't get executed.
Anyway, 403 error message is returned when Authorization fails, not in the case of Authentication fail, so you might set your authorization policies as described here: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/simple?view=aspnetcore-3.1

Related

Cannot write to a HttpContext.Response in AuthenticationHandler

When in my custom AuthenticationHandler I want to write some data into HttpContext.Response like this:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
await Context.Response.WriteAsJsonAsync(new SomeObject(), Context.RequestAborted);
return AuthenticateResult.Fail("something went wrong");
}
I always get exception:
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: StatusCode cannot be set because the response has already started.
With these stack traces:
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.Authentication.AuthenticationHandler1.HandleChallengeAsync(AuthenticationProperties properties) at Microsoft.AspNetCore.Authentication.AuthenticationHandler1.ChallengeAsync(AuthenticationProperties properties)
at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.<>c__DisplayClass0_0.<g__Handle|0>d.MoveNext()
--- End of stack trace from previous location ---
And:
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
at Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware.InvokeAsync(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
Thought with this error my request is aborted and I don't get any response. If after writing to response I call Response.CompleteAsync() then exception is still thrown, but I get response.
Additionally, I don't have any custom middleware. How can I fix this?

ASP.NET Core Web API - JWT Message Handler - No Registered Type Error

I have created a JWT Token Handler for my Web API project. The idea is that this API is merely a gateway to other APIs and a token is shared across them all. So when a call is made to my API and I am forwarding the call to another, I want to automatically forward the JWT that was attached to the initial request.
I made a custom MessageHandler as seen below:
public class JwtTokenHeaderHandler : DelegatingHandler
{
private readonly HttpContext httpContext;
public JwtTokenHeaderHandler(HttpContext httpContext)
{
this.httpContext = httpContext;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
string savedToken = await this.httpContext.GetTokenAsync("access_token");
request.Headers.Add("bearer", savedToken);
return await base.SendAsync(request, cancellationToken);
}
}
Then I registered it as a service in the Startup as below:
services.AddTransient<HttpMessageHandler>(p => p.GetRequiredService<JwtTokenHeaderHandler>());
IHttpClientBuilder serviceClientBuilder = services.AddHttpClient<IService, Service>(client =>
{
client.BaseAddress = new Uri(this.Configuration.GetValue<string>("ServiceURL"));
}).AddHttpMessageHandler<JwtTokenHeaderHandler>();
The application runs up until a request is made and then this exception comes up:
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: No service for type 'Application.API1.JwtTokenHeaderHandler' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.<>c__4`1.b__4_1(HttpMessageHandlerBuilder b)
at Microsoft.Extensions.Http.DefaultHttpClientFactory.<>c__DisplayClass17_0.g__Configure|0(HttpMessageHandlerBuilder b)
at Microsoft.Extensions.Http.LoggingHttpMessageHandlerBuilderFilter.<>c__DisplayClass3_0.b__0(HttpMessageHandlerBuilder builder)
First, injecting HttpContext directly will cause issues. Use IHttpContextAccessor and access the context in the Send method.
public class JwtTokenHeaderHandler : DelegatingHandler {
private readonly IHttpContextAccessor accessor;
public JwtTokenHeaderHandler(IHttpContextAccessor accessor) {
this.accessor = accessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
string savedToken = await accessor.HttpContext.GetTokenAsync("access_token");
request.Headers.Add("bearer", savedToken);
return await base.SendAsync(request, cancellationToken);
}
}
Next GetRequiredService throws that exception when the requested service is not registered with the collection so make sure to register the handler with the container
Note
The handler type must be registered as a transient service.
Reference AddHttpMessageHandler<THandler>(IHttpClientBuilder)
services.AddTransient<JwtTokenHeaderHandler>();
So that JwtTokenHeaderHandler can be resolved when requested from the service provider
services.AddTransient<HttpMessageHandler>(p => p.GetRequiredService<JwtTokenHeaderHandler>());
services.AddHttpClient<IService, Service>(client => {
client.BaseAddress = new Uri(this.Configuration.GetValue<string>("ServiceURL"));
}).AddHttpMessageHandler<JwtTokenHeaderHandler>();

How to get grpc to throw the original exception like WCF does?

I was wondering whether there is a way to have Grpc .Net Core throw the original exception of the server side back to the client.
The problem is the following. When calling a grpc service, for example:
Client:
using (var channel = GrpcChannel.ForAddress("http://localhost:10042"))
{
var greeterService = channel.CreateGrpcService<IGreetService>();
var result = await greeterService.Greet();
Console.WriteLine(result.Greet);
}
Server:
public async Task<Greeting> Greet()
{
throw new ArgumentException();
}
Running this, the following RpcException is raised:
Grpc.Core.RpcException: 'Status(StatusCode=Unknown, Detail="Exception was thrown by handler.")'
Now, I would very much like to get this in a direction of actually raising an ArgumentException on the client side so this can be handled better.
What is possible, so I figured, is to do the following in my Startup.cs:
services.AddCodeFirstGrpc(options => options.EnableDetailedErrors = true);
Result:
Grpc.Core.RpcException: 'Status(StatusCode=Unknown, Detail="Exception was thrown by handler. ArgumentException: Value does not fall within the expected range.")'
That's better, but still not perfect.
My question is - Can I somehow raise the servers exception on the client side? Something I read up on was the fact that grpc allows "Interceptors" to intercept grpc calls. Is this a possibility to maybe do something here?
You can set up an Interceptor to catch server-side exceptions, serialize the exception metadata, and pack it into a custom RpcException. The client can deserialize the metadata from the RpcException and recreate the exception.
Here is a blog post I found that steps through the process: ASP gRPC Custom Error Handling
The downside, as you can imagine, is that this needs to be done for every exception.
I've also been looking into the same question, and haven't found any straightforward ways to apply this to all exceptions.
Configure an interceptor:
services.AddGrpc(options =>
{
options.Interceptors.Add<GRPCInterceptor>();
});
Bare-bones interceptor:
public class GRPCInterceptor : Interceptor
{
private readonly ILogger logger;
public GRPCInterceptor(ILogger logger)
{
this.logger = logger;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
{
logger.Debug($"starting call");
var response = await base.UnaryServerHandler(request, context, continuation);
logger.Debug($"finished call");
return response;
}
}

Unable to fail in HandleRequirementAsync

I've got an authorization handler that works when succeeding, but fails when... failing.
Here it is:
public class HeaderHandler : AuthorizationHandler<HeaderRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected async override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
HeaderRequirement requirement)
{
var Request = _httpContextAccessor.HttpContext.Request;
try
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
context.Succeed(requirement);
}
catch (Exception ex)
{
context.Fail();
}
}
}
And it's configured this way:
services.AddAuthorization(options =>
{
options.AddPolicy(nameof(Policy.AuthorizationHeader), //an enum of mine...
policy => policy.Requirements.Add(new HeaderRequirement()));
});
And here's how I use it in a controller:
[Authorize(Policy = nameof(Policy.AuthorizationHeader))]
public async Task<IActionResult> CreateSpace([FromQuery] CreateSpaceViewModel viewModel)
{
//....
}
This works fine when succeeding, I'm reaching the code above. But when failing in the handler I get:
System.InvalidOperationException: No authenticationScheme was
specified, and there was no DefaultChallengeScheme found. The default
schemes can be set using either AddAuthentication(string
defaultScheme) or AddAuthentication(Action
configureOptions).
It's as if when failing, I hadn't setup the handler in Startup.cs. It mentions authentication even though I'm not using that...
From what I can read, Authentication is to let user in the application, and Authorization is restricting access to certain resources for users that have been let in.
But I'm coding an API, I don't care about the "application" part. I just want to decorate certain action with attributes... Like in .net 4.7.
Any idea ?
I will use an ActionFilter instead. Setting the response's content from an authorization handler isn't even supported yet so no point in getting it to work properly.

Get back Request Body within ActionFilter

For logging purposes, I am trying to monitor the requests being made through a WebAPI. I have created and I am looking for a way to get back the body sent through in a request after the request has been fulfilled and responded to. I am trying to do this through using a ActionFilter but thus far have failed in reading the body from the request.
Can anybody give some advice how I may access this information?
For context I am trying to do this within this code:
public class LoggingActionFilter : ActionFilterAttribute
{
public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
var test = actionExecutedContext.Request.Content.ReadAsStringAsync().Result;
return base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);
}
}
I have tried reading back the Content on the actionExecutedContext variable in order to get back the body but have found this to return just blank so far.
you're just dealing with request body so don't need to use OnActionExecutedAsync method, you can just override OnActionExecuting like this,
public override void OnActionExecuting(HttpActionContext actionContext)
{
var test = (actionContext.Request.Content as ObjectContent).Value.ToString();
// your logging code here
}
Another option available in WebAPI is DelegatingHandler. if you want to log just request body then override SendAsync method,
public class ApiLogHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestBody = request.Content.ReadAsStringAsync().Result;
// your logging code here
return base.SendAsync(request, cancellationToken);
}
}
If you decided to choose DelegatingHandler then you need to register that handler to Global message handlers.

Categories

Resources