Adding a header in ActionFilterAttribute - asp.net

I'm trying to assign a token to my header in the request and when the action has finished to save a new token. Problem is that it assigns it to the HttpContext.Request.Headers and not to the client that is calling the API.
ActionFilter
public class TokenFilter: ActionFilterAttribute {
private static string _token {
get;
set;
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
base.OnActionExecuting(filterContext);
if (_token != null) {
filterContext.HttpContext.Request.Headers.Add("AuthToken", "Token " + _token);
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext) {
base.OnActionExecuted(filterContext);
_token = filterContext.HttpContext.Request.Headers["AuthToken"];
_token = _token.Substring(_token.IndexOf(" "));
_token = _token.Remove(0, 1);
if (Client.DefaultRequestHeaders.Contains("AuthToken"))
}
}
Controller Action
[HttpPost]
[TokenFilter]
public async Task < IActionResult > LogIn(User user) {
try {
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(
Encoding.UTF8.GetBytes(user.Username + ":" + user.Password)));
HttpResponseMessage clientTask = await client.GetAsync("https://localhost:44324/api/Auth/LogIn");
if (clientTask.IsSuccessStatusCode) {
string txtBlock = await clientTask.Content.ReadAsStringAsync();
var tokenObject = JsonConvert.DeserializeObject < SessionAPI > (txtBlock);
client.DefaultRequestHeaders.Authorization = null;
client.DefaultRequestHeaders.Add("AuthToken", "Token " + tokenObject.Token);
return RedirectToAction("Index", "Home");
}
else return View("LogInIndex", user);
}
catch(Exception e) {
throw new Exception("An Error has occured" + e);
}
}
The main idea is to have a token that after LogIn is to be assigned to every request that is sent to the API untill Log Out. I know that i can use cookies but part of the assignment is not to use them. Currently i have the Token just being a static string for testing, but that eventually has to be moved somewhere dynamicly for every User.
EDIT
This is an example method that can be called after a successful Log In
[HttpGet]
[TokenFilter]
public async Task<IActionResult> ListAll()
{
try
{
//client.DefaultRequestHeaders.Add("AuthToken", "Token " + HttpContext.Request.RouteValues["token"]);
HttpResponseMessage clientTask = await client.GetAsync("https://localhost:44324/api/User/ListAll");
if (clientTask.IsSuccessStatusCode)
{
string txtBlock = await clientTask.Content.ReadAsStringAsync();
List<User> users = JsonConvert.DeserializeObject<List<User>>(txtBlock);
client.DefaultRequestHeaders.Authorization = null;
return View("ListAll", users);
}
else
return RedirectToAction("Index", "Home");
}
catch (Exception e)
{
throw new Exception("An Error has occured" + e);
}
}

You don't have to override OnActionExecuting
You can do this as follow
public class TokenFilter: ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext filterContext)
{
if (_token != null)
{
filterContext.Response.Headers.Add("AuthToken", "Token " + _token);
}
}
}

Related

How to create a custom error message for Authorize attribute?

I want only admins to have access to this controller and its actions, so I've written this code:
[Authorize(Roles = Helper.AdminRole)]
public class AdminController : Controller
{
public IActionResult AdminPanel()
{
return View();
}
//other actions only available to admins
}
If the user is not logged in and he's not in the specified role I get a 404 Not Found page and this in the URL:
..../AccessDenied?ReturnUrl=%2FAdmin%2FAdminPanel
How can I make a custom error page for this scenario where the user is asked to log in so he can confirm his role, and when he does log in successfully AND he is in the right role to be redirected to where he wanted to go, but if his role is invalid to be redirected elsewhere/ shown a custom error page?
Your error was caused due to lack of Loginpath settings,not wrong role or password.(So the error code was 404 not 401)
You could see the test Result:
If you want to custom error page,you could read the official document:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-5.0
I tried with the codes below:
ErrorResult class:
public class ErrorResult
{
public bool Success { get; set; } = true;
public string Msg { get; set; } = "";
public string Type { get; set; } = "";
public object Data { get; set; } = "";
public object DataExt { get; set; } = "";
}
ErrorHandlingMiddleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
var statusCode = context.Response.StatusCode;
if (ex is ArgumentException)
{
statusCode = 200;
}
await HandleExceptionAsync(context, statusCode, ex.Message);
}
finally
{
var statusCode = context.Response.StatusCode;
var msg = "";
if (statusCode == 401)
{
msg = "unauthorize";
}
else if (statusCode == 404)
{
msg = "NotFound";
}
else if (statusCode == 400)
{
msg = "BadRequest";
}
else if (statusCode != 200)
{
msg = "Unkonwn";
}
if (!string.IsNullOrWhiteSpace(msg))
{
await HandleExceptionAsync(context, statusCode, msg);
}
}
}
private static Task HandleExceptionAsync(HttpContext context, int statusCode, string msg)
{
var result = JsonConvert.SerializeObject(new ErrorResult() { Success = false, Msg = msg, Type = statusCode.ToString() });
context.Response.ContentType = "application/json;charset=utf-8";
return context.Response.WriteAsync(result);
}
}
public static class ErrorHandlingExtensions
{
public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ErrorHandlingMiddleware>();
}
}
in startup class:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseErrorHandling();
....
}
The Result:
Take cookie authentication as an example, you just need to configure it like this in program.cs(.Net 6):
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x =>
{
//When user doesn't login and he access to an action with [Authorize],
//He will redirect to the loginPath
x.LoginPath = "/{controller}/{action}";
//When user has loged in but the role is not the specified role,
//He will redicet to the AccessDeniedPath,
//Then you can custom your own error page in this path.
x.AccessDeniedPath = "/{controller}/{action}";
});

How to get request parameter in OnActionExecuted of action filter

As the title, is it possible to get the request parameter?
I try to get it from request body, but failed, the task.Result is zero. it seems the request body is empty.
Anyone can help me, it would be much appreciated.
The code as below:
public class LogFilter : Attribute, IActionFilter
{
private ILogger<LogFilter> _logger;
private ITestAService _service;
public LogFilter(ILogger<LogFilter> logger, ITestAService service)
{
_logger = logger;
_service = service;
}
public void OnActionExecuted(ActionExecutedContext context)
{
var content = new StringBuilder();
using (Stream sm = context.HttpContext.Request.Body)
{
int count = 0;
byte[] buffer = new byte[1024];
StringBuilder builder = new StringBuilder();
var task = sm.ReadAsync(buffer, 0, 1024);
if (task.Result > 0)
{
content.Append(Encoding.UTF8.GetString(buffer, 0, count));
}
}
}
}
Request Body is consumed and not available in the OnActionExecuted, I do it like this,
Read The Request Body and Store it HTTPContext
public void OnActionExecuting(ActionExecutingContext context)
{
try
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (controllerActionDescriptor != null)
{
var requestBody = FormatRequestBody(context.ActionArguments);
context.HttpContext.Items["LogRequestBody"] = requestBody;
}
}
catch (Exception ex)
{
_logger.Error("Error in LogServiceCallFilter", ex);
}
}
public string FormatRequestBody(IDictionary<string, object> actionArguments)
{
try
{
if (actionArguments != null)
return $"{JsonConvert.SerializeObject(actionArguments)}";
}
catch (Exception ex)
{
_logger.Error("Error in LogServiceCallFilter", ex);
}
return "";
}
Read the Request Body you already stored in HttpContext
public void OnActionExecuted(ActionExecutedContext context)
{
try
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (controllerActionDescriptor != null)
{
var actionName = context.ActionDescriptor.RouteValues["controller"] + "/" + context.ActionDescriptor.RouteValues["action"];
var requestBody = context.HttpContext.Items["LogRequestBody"] != null ? context.HttpContext.Items["LogRequestBody"].ToString() : "";
context.HttpContext.Items.Remove("LogRequestBody");
}
}
catch (Exception ex)
{
_logger.Error("Error in LogServiceCallFilter", ex);
}
}

Returning all json objects using asp net core 2.1

I am able to retrive one json object from a url. I need help in retrieving a page full of json objects. I found this site, https://jsoneditoronline.org/, to show the json architecture of the page I want to return:
enter image description here
Here is my code:
namespace iexName.Controllers
{
[Route("api/IexName")]
[ApiController]
public class IexNameController : ControllerBase
{
private IHttpClientFactory _httpClientFactory;
public IexNameController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[HttpGet]
public IEnumerable<Models.IexTradingStock> GetQuote()
{
string responseString = string.Empty;
var Client = _httpClientFactory.CreateClient();
try
{
responseString =
Client.GetStringAsync($"https://api.iextrading.com/1.0/stock/aapl/chart/1y").Result;
}
catch (HttpRequestException hre)
{
Console.WriteLine(hre.Message);
//TODO do something
}
catch (Exception e)
{
Console.WriteLine(e.Message);
//TODO do something
}
//quit if get content fail
if (responseString == string.Empty) return null;
try
{
var stock = JsonConvert.DeserializeObject<IexTradingStock>
(responseString);
return stock;
}
The error is on "return stock;". I realize I do not know how to return all of the json objects.
Soon after I typed this into stackflow I realized what to do. This worked.
namespace iexName.Controllers
{
[Route("api/IexName")]
[ApiController]
public class IexNameController : ControllerBase
{
private IHttpClientFactory _httpClientFactory;
public IexNameController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[HttpGet]
public IEnumerable<Models.IexTradingStock> GetQuote()
{
string responseString = string.Empty;
var Client = _httpClientFactory.CreateClient();
try
{
responseString =
Client.GetStringAsync($"https://api.iextrading.com/1.0/stock/aapl/chart/1y").Result;
}
catch (HttpRequestException hre)
{
Console.WriteLine(hre.Message);
//TODO do something
}
catch (Exception e)
{
Console.WriteLine(e.Message);
//TODO do something
}
//quit if get content fail
if (responseString == string.Empty) return null;
try
{` List<IexTradingStock> stock =
JsonConvert.DeserializeObject<List<IexTradingStock>>(responseString);
return stock;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
//TODO do something
return null;
}
}
}
}`

HttpContext.Current is null when using IAuthenticationFilter

I am uploading files using ng-file-upload and having some abnormal problem as the HttpContext.Current is null when using the IAuthenticationFilter. While everything working correctly when I comment the authentication filter in WebApiConfig.
Controller to Test
[HttpPost]
public IHttpActionResult Upload()
{
var current = HttpContext.Current;
if (current == null)
{
return Content(HttpStatusCode.BadRequest, Logger.Error("HttpContext.Current is null"));
}
if (current.Request != null && current.Request.Files != null)
{
var file = current.Request.Files.Count > 0 ? current.Request.Files[0] : null;
if (file != null)
{
file.SaveAs(#"C:\Temp\test.csv");
}
}
return Content(HttpStatusCode.BadRequest, Logger.Error("Should not reach here"));
}
IAuthenticationFilter
public class KeyAuthentication : Attribute, IAuthenticationFilter
{
// we only want to apply our authentication filter once on a controller or action method so return false:
public bool AllowMultiple
{
get { return false; }
}
// Authenticate the user by apiKey
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
string apiKey = ExtractApiKey(request);
bool IsValidCustomer = await ValidateKey(apiKey);
if (IsValidCustomer)
{
var currentPrincipal = new GenericPrincipal(new GenericIdentity(apiKey), null);
context.Principal = principal;
}
else
{
context.ErrorResult = new ErrorMessageResult("Missing API Key");
}
}
// We don't want to add challange as I am using keys authenticaiton
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
}
Extract API Key
public static string ExtractApiKey(HttpRequestMessage request)
{
if (!request.Headers.TryGetValues("x-api-key", out IEnumerable<string> keys))
return string.Empty;
return keys.First();
}
The solution was to include "targetFramework=4.5" in the web.config as commented by #Alfredo and more details in https://stackoverflow.com/a/32338414/3973463

Web API Authorize Attribute not working on Action

I am using the [Authorize] attribute on my WebAPI controller action and it's always coming back unauthorized.
Here is my action
[Authorize(Roles = "Admin")]
public IQueryable<Country> GetCountries()
{
return db.Countries;
}
Here is where I am setting the Authorization in a Global MessageHandler. This is for testing I'm putting in a test user.
public class AuthenticationHandler1 : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
HttpContext.Current.User = TestClaimsPrincipal();
}
return base.SendAsync(request, cancellationToken);
}
private ClaimsPrincipal TestClaimsPrincipal()
{
var identity = new ClaimsIdentity(HttpContext.Current.User.Identity.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, "some.user"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Supervisor"));
var testIdentity = new ClaimsIdentity(identity);
var myPrincipal = new ClaimsPrincipal(testIdentity);
return myPrincipal;
}
}
Registered in Global.asax.cs in Application_Start
GlobalConfiguration.Configuration.MessageHandlers.Add(new MyProject.AuthenticationHandler1());
It keeps showing this for a message
{"Message":"Authorization has been denied for this request."}
I made a Custom Authorization Attribute and it works.
public class AuthorizationAttribute : System.Web.Http.AuthorizeAttribute
{
public string Roles { get; set; }
protected override bool IsAuthorized(HttpActionContext actionContext)
{
ClaimsPrincipal currentPrincipal = HttpContext.Current.User as ClaimsPrincipal;
if (currentPrincipal != null && CheckRoles(currentPrincipal))
{
return true;
}
else
{
actionContext.Response =
new HttpResponseMessage(
System.Net.HttpStatusCode.Unauthorized)
{
ReasonPhrase = "Some message"
};
return false;
}
}
private bool CheckRoles(ClaimsPrincipal principal)
{
string[] roles = RolesSplit;
if (roles.Length == 0) return true;
return roles.Any(principal.IsInRole);
}
protected string[] RolesSplit
{
get { return SplitStrings(Roles); }
}
protected static string[] SplitStrings(string input)
{
if(string.IsNullOrWhiteSpace(input)) return new string[0];
var result = input.Split(',').Where(s=>!String.IsNullOrWhiteSpace(s.Trim()));
return result.Select(s => s.Trim()).ToArray();
}
}
Use it like this
[AuthorizationAttribute(Roles = "SomeRole,Admin")]
public IQueryable<Country> GetCountries()
{
}

Resources