.Net 6 - exception middleware not catching authorization error [duplicate] - exception

I'm developing ASP Core Web API using dotnet core v3.1.
I'm using JWT tokens for authentication. And for authorization I use the [Authorize] attribute.
How can I create my own response if the user is not logged in (while trying to access the action marked with the [Authorize] attribute) or the user's token is not authenticated.
I came across a solution using a custom authorization attribute inherited from the default one. And in this example, the HandleUnauthorizedRequest method is overridden. But I don't see such a method inside the AuthorizeAttribute class.
Is there a way to create custom unauthorized responses with http body?

Since you are using JWT bearer authentication, one way to override the default Challenge logic (which executes to handle 401 Unauthorized concerns) is to hook a handler to the JwtBearerEvents.OnChallenge callback in Startup.ConfigureServices:
services.AddAuthentication().AddJwtBearer(options =>
{
// Other configs...
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
// Call this to skip the default logic and avoid using the default response
context.HandleResponse();
// Write to the response in any way you wish
context.Response.StatusCode = 401;
context.Response.Headers.Append("my-custom-header", "custom-value");
await context.Response.WriteAsync("You are not authorized! (or some other custom message)");
}
};
});
This will override the default challenge logic in JwtBearerHandler.HandleChallengeAsync, which you can find here for reference purposes.
The default logic does not write any content to response (it only sets the status code and set some headers). So to keep using the default logic and add content on top of it, you can use something like this:
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
context.Response.OnStarting(async () =>
{
// Write to the response in any way you wish
await context.Response.WriteAsync("You are not authorized! (or some other custom message)");
});
return Task.CompletedTask;
}
};

For .net core 5 web api project with jwt authentication use this middleware in Configure method of Startup.cs for show ErrorDto in Swagger:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LoginService v1"));
}
app.ConfigureExceptionHandler();
app.UseHttpsRedirection();
app.UseRouting();
// Unauthorized (401) MiddleWare
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized) // 401
{
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(new ErrorDto()
{
StatusCode = 401,
Message = "Token is not valid"
}.ToString());
}
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
ErrorDto :
public class ErrorDto
{
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}

This is what I came up with for responding with the same ProblemDetails you would get from returning Unauthorized() in an ApiController:
.AddJwtBearer(options =>
{
// Other configs...
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
// Call this to skip the default logic and avoid using the default response
context.HandleResponse();
var httpContext = context.HttpContext;
var statusCode = StatusCodes.Status401Unauthorized;
var routeData = httpContext.GetRouteData();
var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
var factory = httpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = factory.CreateProblemDetails(httpContext, statusCode);
var result = new ObjectResult(problemDetails) { StatusCode = statusCode };
await result.ExecuteResultAsync(actionContext);
}
};
});

Related

Exception handling using Hellang middleware in .Net Core MVC

I've used Hellang Middleware for exception handling as the global exception handling mechanism in my MVC application.
I've added the following code in the ConfigureServices method in Startup.cs:
services.AddProblemDetails(opts =>
{
// Control when an exception is included
opts.IncludeExceptionDetails = (ctx, ex) =>
{
// Fetch services from HttpContext.RequestServices
var env = ctx.RequestServices.GetRequiredService<IHostEnvironment>();
return env.IsDevelopment() || env.IsStaging();
};
opts.ShouldLogUnhandledException = (ctx, e, d) =>
{
return (d.Status.HasValue && d.Status.Value >= 500);
};
});
Also I've added UseProblemDetails() in Configure method.
However I came to know that if am using UseProblemDetails(), then UseExceptionHandler() won't work!
Hence I'am not able to figure out a method for navigating user to a common error view page.
Is there any way to redirect users to an error page while sticking on to Hellang Middleware for exception handling and logging ?
See the answer here:
https://stackoverflow.com/a/40153711/90287
You have to distinguish between the type of request, if it's an API request or a UI request to determine if a problem+details JSON should be returned or if a web page should be returned, respectively.
This is what I do near the top of the Configure method of Startup.cs:
app.UseWhen(context => context.IsApiRequest(), branch =>
{
branch.UseProblemDetails();
});
app.UseWhen(context => !context.IsApiRequest(), branch =>
{
branch.UseExceptionHandler("/Home/Error");
});
You can define your own custom HttpContext extension method:
public static class HttpContextExtensions
{
public static bool IsApiRequest(this HttpContext context)
{
return context.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase)
|| (context.Request.Headers["X-Requested-With"] == "XMLHttpRequest"); // AJAX request
}
}
I had a similar problem. I solved it like the following. In this example logging a custom business fault exception:
services.AddProblemDetails(setup =>
{
setup.Map<FaultException<BusinessFault>>((context, exception) =>
{
// resolve logger
var logger = context.RequestServices.GetRequiredService<ILogger<ProblemDetails>>();
// log exception to Seq
logger.LogError(exception, "{#Exception} occurred.", exception);
// return the problem details map
return new ProblemDetails
{
Title = exception.Message,
Detail = exception.Detail.FaultMessage,
Status = exception.Detail.FaultType.ToHttpStatus(),
Type = exception.Detail.FaultType.ToString(),
Instance = exception.Detail.FaultReference
};
});
});
This is not exactly the answer to your question, but I had a similar issue in a Web API application regarding using ExceptionHandler middleware and Hellang ProblemDetails Middleware and I also came to realize I could't use them both because both change the response in their own way and affect one another.
Based on the documentation here you can use one of the configuration options of the ProblemDetails package to excute code before changing response and there you can log all the information you need.
services.AddProblemDetails(options =>
{
options.IncludeExceptionDetails = (context, ex) =>
{
var environment = context.RequestServices.GetRequiredService<IWebHostEnvironment>();
return environment.IsDevelopment();
};
options.Map<IdentityException>(exception => new ProblemDetails()
{
Title = exception.Title,
Detail = exception.Detail,
Status = StatusCodes.Status500InternalServerError,
Type = exception.Type,
Instance = exception.ToString()
});
options.OnBeforeWriteDetails = (ctx, pr) =>
{
//here you can do the logging
logger.LogError("Exception Occurred!!!!");
logger.LogError(pr.Detail);
logger.LogError(pr.Instance);
};
});
Here, I use a custom exception with extra fields that are needed for problem details object in response, and I use the Instance field to hold the exception and log it.

dot net Core 3.1 API HttpRequest returns usually bad request without even sending the request

I have a strange issue with my HttpRequest, i have 2 application one is clientside and the other one is RESTAPI, the issue is i am trying to update my entity by sending a request which the content is Json
public async Task<bool> Update(string url, T obj, string id)
{
var request = new HttpRequestMessage(HttpMethod.Put, url+id);
if (obj == null || String.IsNullOrEmpty(id))
{
return false;
}
request.Content = new StringContent(JsonConvert.SerializeObject(obj),
Encoding.UTF8, "application/json");
var client = _client.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("bearer", GetBearerToken());
HttpResponseMessage response = await client.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
{
return true;
}
return false;
}
And here is my clientapp controller below;
[HttpPost]
public async Task<IActionResult> EditUser([FromForm] UserDTO userDTO ,string id)
{
if (!ModelState.IsValid)
{
return RedirectToAction("ErrorPage", "Error");
}
userDTO.Id = id;
await _userRepository.Update(EndPoints.UserEndPoint,userDTO,id);
return RedirectToAction("GetUsers");
}
and i dont know if it is necessary because it doesnt hit even the breakpoint but i am also showing my RESTAPI code below;
/// <summary>
/// Update user
/// </summary>
/// <param name="id"></param>
/// <param name="userDTO"></param>
/// <returns></returns>
[HttpPut("{id}")]
[Authorize(Roles = "Administrator")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> UpdateUser(string id, [FromBody] UserDTO userDTO)
{
var location = GetControllerActionNames();
try
{
_logger.LogInfo($"{location}: Requested an Update for id: {id} ");
if (string.IsNullOrEmpty(id) || userDTO == null || id != userDTO.Id)
{
_logger.LogError($"{location}: Request for Id: {id} is not sucessful");
return BadRequest();
}
if (!ModelState.IsValid)
{
_logger.LogWarn($"{location}: Data was incomplete!");
return BadRequest(ModelState);
}
var isExist = await _userRepo.IsExist(id);
if (!isExist)
{
_logger.LogWarn($"{location}: with Id: {id} is not exisist");
return NotFound();
}
var usermap = _mapper.Map<CompanyUser>(userDTO);
if (usermap == null)
{
_logger.LogWarn($"{location}: Data is empty");
return BadRequest();
}
var response = await _userRepo.Update(usermap);
if (!response)
{
_logger.LogError($"{location}: Update is failed ");
return NotFound();
}
_logger.LogInfo($"User is Updated");
return NoContent();
}
catch (Exception e)
{
return InternalError($"{location} - {e.Message} - {e.InnerException}");
}
}
RESTAPI code is working when i try with PostMan.
But from the client side where i send the request it sometimes works but usually gives bad request as response instanly i mean not even go to my RESTAPI. Can you help to resolve this strange problem.
I fixed the issue, on my API Login
Because i was using Microsoft Identity and when i use await PasswordEmailSignInAsync(userName, password, false, false); it automatically genereates application cookie on my API side and i used fiddler to capture requests and i saw there when i get an error or on my API side when the thread exits the application cookie also expires after that when i made a new request from my Client to My API it was giving the bad request on my client side instantly.
So i changed my signin method to var user = await _userManager.FindByEmailAsync(userDTO.Email); var result = await _userManager.CheckPasswordAsync(user, userDTO.Password);
in order to avoid from the application cookie creation. I had already JWT token structure in my application but was useless because default authorized attribute was not using bearer schema and i modified my startup.cs a little help from [Authorize Attribute not working with JWT Access Token in ASP.Net Core1
and now everything works without any problem!.
[Route("login")]
[HttpPost]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> Login([FromBody] UserLoginDTO userDTO)
{
var location = GetControllerActionNames();
try
{
var userName = userDTO.Email;
var password = userDTO.Password;
_logger.LogInfo($"{location}: User:{userName} - Attempted to Login");
//var result = await PasswordEmailSignInAsync(userName, password, false, false);
var user = await _userManager.FindByEmailAsync(userDTO.Email);
var result = await _userManager.CheckPasswordAsync(user, userDTO.Password);
if (result)
{
_logger.LogInfo($"{location}: User:{userName} Logged in Succesfully");
var tokenstring = await GenerateJSONWebToken(user);
return Ok(new { token = tokenstring });
}
_logger.LogWarn($"{location}: User:{userName} couldnt logged in ");
return Unauthorized(userDTO);
}
catch (Exception e)
{
return InternalError($"{location} - {e.Message} - {e.InnerException}");
}
}

Is there any way within middleware running on ASP.NET Core 2.2 to detect if the request is for an ApiController?

I have an application with both MVC and 'new' ApiController endpoints in ASP.NET Core 2.2 co-existing together.
Prior to adding the API endpoints, I have been using a global exception handler registered as middleware using app.UseExceptionHandler((x) => { ... } which would redirect to an error page.
Of course, that does not work for an API response and I would like to return an ObjectResult (negotiated) 500 result with a ProblemDetails formatted result.
The problem is, I'm not sure how to reliably determine in my 'UseExceptionHandler' lambda if I am dealing with an MVC or a API request. I could use some kind of request URL matching (eg. /api/... prefix) but I would like a more robust solution that won't come back to bite me in the future.
Rough psuedo-code version of what I'm trying to implement is:
app.UseExceptionHandler(x =>
{
x.Run(async context =>
{
// extract the exception that was thrown
var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error;
try
{
// generically handle the exception regardless of what our response needs to look like by logging it
// NOTE: ExceptionHandlerMiddleware itself will log the exception
// TODO: need to find a way to see if we have run with negotiation turned on (in which case we are API not MVC!! see below extensions for clues?)
// TODO: ... could just use "/api/" prefix but that seems rubbish
if (true)
{
// return a 500 with object (in RFC 7807 form) negotiated to the right content type (eg. json)
}
else
{
// otherwise, we handle the response as a 500 error page redirect
}
}
catch (Exception exofex)
{
// NOTE: absolutely terrible if we get into here
log.Fatal($"Unhandled exception in global error handler!", exofex);
log.Fatal($"Handling exception: ", ex);
}
});
});
}
Any ideas?
Cheers!
This might be a bit different than what you expect, but you could just check if the request is an AJAX request.
You can use this extension:
public static class HttpRequestExtensions
{
public static bool IsAjaxRequest(this HttpRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (request.Headers == null)
return false;
return request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
}
And then middleware with an invoke method that looks like:
public async Task Invoke(HttpContext context)
{
if (context.Request.IsAjaxRequest())
{
try
{
await _next(context);
}
catch (Exception ex)
{
//Handle the exception
await HandleExceptionAsync(context, ex);
}
}
else
{
await _next(context);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
//you can do more complex logic here, but a basic example would be:
var result = JsonConvert.SerializeObject(new { error = "An unexpected error occurred." });
context.Response.ContentType = "application/json";
context.Response.StatusCode = 500;
return context.Response.WriteAsync(result);
}
see this SO answer for a more detailed version.
If you want to check whether the request is routed to ApiController, you could try IExceptionFilter to hanlde the exceptions.
public class CustomExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (IsApi(context))
{
HttpStatusCode status = HttpStatusCode.InternalServerError;
var message = context.Result;
//You can enable logging error
context.ExceptionHandled = true;
HttpResponse response = context.HttpContext.Response;
response.StatusCode = (int)status;
response.ContentType = "application/json";
context.Result = new ObjectResult(new { ErrorMsg = message });
}
else
{
}
}
private bool IsApi(ExceptionContext context)
{
var controllerActionDesc = context.ActionDescriptor as ControllerActionDescriptor;
var attribute = controllerActionDesc
.ControllerTypeInfo
.CustomAttributes
.FirstOrDefault(c => c.AttributeType == typeof(ApiControllerAttribute));
return attribute == null ? false : true;
}
}
Thanks to all of the advice from others, but I have realised after some more thought and ideas from here that my approach wasn't right in the first place - and that I should be handling most exceptions locally in the controller and responding from there.
I have basically kept my error handling middleware the same as if it was handling MVC unhandled exceptions. The client will get a 500 with a HTML response, but at that point there isn't much the client can do anyway so no harm.
Thanks for your help!

Auth0 and Asp.Net Core 2.0 Razor Pages LoginPath Issue

I'm creating a Razor Pages app with Auth0 as the authentication provider and I'm running into a LoginPath issue. I've seen other StackOverflow answers that say you should put this into the ConfigureServices method:
services.ConfigureApplicationCookie(options => options.LoginPath = "/Index/Login");
I tried putting that below the services.AddAuthentication section of code, but that doesn't redirect to /Index/Login. I'm not seeing anywhere else how to properly get an [Authorize] attribute failure to redirect to the Auth0 login page. I figured if I could get the path set to the Index page this code would run:
public async void OnGetLogin(string returnUrl = "/")
{
await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { RedirectUri = returnUrl });
}
My full ConfigureServices code is:
public void ConfigureServices(IServiceCollection services)
{
// Add authentication services
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options => {
// Set the authority to your Auth0 domain
options.Authority = $"https://{Configuration["Auth0:Domain"]}";
// Configure the Auth0 Client ID and Client Secret
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
// Set response type to code
options.ResponseType = "code";
// Configure the scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("groups");
options.Scope.Add("profile");
options.Scope.Add("email");
// Set the callback path, so Auth0 will call back to http://localhost:5000/signin-auth0
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
options.CallbackPath = new PathString("/signin-auth0");
// Configure the Claims Issuer to be Auth0
options.ClaimsIssuer = "Auth0";
options.Events = new OpenIdConnectEvents
{
// handle the logout redirection
OnRedirectToIdentityProviderForSignOut = (context) =>
{
var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = context.Request;
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
}
logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
}
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
});
services.ConfigureApplicationCookie(options => options.LoginPath = "/Index/Login");
services.AddMvc();
}
Anyone know how to do this properly in 2.0?
You need to add this snippet in order to work:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/");
options.Conventions.AllowAnonymousToPage("/Account/Login");
});
When I added this, my code started to work and redirect to the right login page.

GetRequestToken is not working in TweetSharp on Windows Phone

I can't use GetRequestToken in TwitterService anymore
and also GetAccessToken!
TwitterService service = new TwitterService("ConsumerKey", "ConsumerKeySecret");
service.GetRequestToken(Constants.CallbackUri, (request, response) =>
{
if (response.StatusCode == HttpStatusCode.OK)
{
Request = request;
var uri = service.GetAuthorizationUri(request);
Dispatcher.BeginInvoke(() => AuthBrowser.Navigate(uri));
}
});
it gives me:
'TweetSharp.TwitterService' does not contain a definition for 'GetRequestToken' and no extension method 'GetRequestToken' accepting a first argument of type 'TweetSharp.TwitterService' could be found (are you missing a using directive or an assembly reference?)
I solved it by getting Request Token via Hammock(https://github.com/danielcrenna/hammock)
and here is the code
/// <summary>
/// Gets Twitter Request Token
/// </summary>
private void GetTwitterToken()
{
var credentials = new OAuthCredentials
{
Type = OAuthType.RequestToken,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
ConsumerKey = "Your Consumer Key",
ConsumerSecret = "Your Consumer Secret",
Version = TwitterSettings.OAuthVersion,
CallbackUrl = TwitterSettings.CallbackUri
};
var client = new RestClient
{
Authority = "https://api.twitter.com/oauth",
Credentials = credentials,
HasElevatedPermissions = true,
};
var request = new RestRequest
{
Path = "/request_token"
};
client.BeginRequest(request, new RestCallback(TwitterRequestTokenCompleted));
}
and
private void TwitterRequestTokenCompleted(RestRequest request, RestResponse response, object userstate)
{
_oAuthToken = GetQueryParameter(response.Content, "oauth_token");
_oAuthTokenSecret = GetQueryParameter(response.Content, "oauth_token_secret");
var authorizeUrl = TwitterSettings.AuthorizeUri + "?oauth_token=" + _oAuthToken;
if (String.IsNullOrEmpty(_oAuthToken) || String.IsNullOrEmpty(_oAuthTokenSecret))
{
Dispatcher.BeginInvoke(() => MessageBox.Show("error calling twitter"));
return;
}
Dispatcher.BeginInvoke(() => AuthBrowser.Navigate(new Uri(authorizeUrl)));
}
and You can do the same with access token.
Have you checked to see if the TweetSharp Library supports Windows Phone 8?