ASP.NET C# ApiController and Static Config Class - c#

I have an issue with an ASP.NET C# API application.
It uses the AuthApiAttribute class to check authorization, using 2 HTTP header to authenticate the query.
Once I have validated the credentials, I put some configuration linked to those credentials in a class with static attributes. That class is named ApiKeyConfig. That parts works correctly.
My problem is when the ApiController handles the response, the value of the attributes of ApiKeyConfig are the values of the previous API call.
So if I call the API 4 time with userA, userB, userC and userA again, the result will be:
Call for userA: Has no info if server is fresh, last call if not
Call for userB: Will have info of userA
Call for userC: Will have info of userB
Call for userA: Will have info of userC
I was expecting the static values of the ApiKeyConfig class not to survive from one query to another. I thought it would be static for the query API call only.
And from that behaviour, I suppose that the AuthApiAttribute class call is done AFTER the controller method has executed ?
In my controller, I have defined [AuthApi] above my public class CustomerController : ApiController.
So what would be the best way to pass to my controller configuration that are specific to the API-key for the current call ?
Also, is there a way to prevent values to be kept from API call to API call ? Like in this case, what would I do to make sure ApiKeyConfig don't have the value of the previous request?
Edit:
My AuthApiAttribute class:
public class AuthApiAttribute : Attribute, IAuthorizationFilter
{
public bool AllowMultiple => true;
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
// I have the logic to check if user is valid
// [...]
List<ApiKey> keys; // Is assigned the valid API keys, skip that code below to avoid long comment
// I have some logic here to stock the valid keys in "keys"
// [...]
foreach (ApiKey apikey in keys)
{
if (key == apikey.key && auth == apikey.auth)
{
// FIXME: Would need to do somehting here to assign that key to something for me to be able to use that value once I'm in the controller's method
return response;
}
}
response.Result.StatusCode = System.Net.HttpStatusCode.Forbidden;
response.Result.Content = new StringContent("Access forbidden. Make sure your credentials are valid.");
return response;
}
}
The class ApiKeyConfig is just a class with attributes representing the settings of the API key in use (somewhat like a user's profile)
Here, an example of a Controller in which I want to refer to the ApiKey for the current request.
[AuthApi]
public class CustomerController : ApiController
{
public Models.Response Get(string id)
{
// FIXME: Here, I want to access the value of ApiKey for the current session.
try
{
// I have some logic here to get the Customer requested
// [...]
return new Models.Response
{
Status = "Success",
Data = Customer
};
}
catch (Exception e)
{
return new Models.Response
{
Status = "Error",
Message = e.Message,
Stack = e.StackTrace
};
}
}
}
Solution:
Based on Athanasios Kataras answer, in AuthApiAttribute:
actionContext.ControllerContext.Configuration.Properties.TryAdd("apikey", apikey);
And then, in my Controller's Method accessing this value with:
Configuration.Properties.TryGetValue("apikey", out object config);
ApiKeyConfig keyConfig = (ApiKeyConfig)config;
if (keyConfig.value.Equals(""))
{
// Handle session undefined
}

You should not use static variables for these types of communication.
When you have multiple concurrent users, the static variable might change in your authorization, before the request is handled by the controller. This will lead to bugs that can't be easily identified.
Maybe you could use something like this to share data between filters and controllers. WebApi: how to pass state from filter to controller?
Also make sure that you extend the https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api authorization attribute for your authorization action, as this will certainly run before your controller.

Related

Getting 415 Unsupported media type when executing from address line in browser provifing JSON as parameter for route in .NET Core 3

I'm executing the URL
https://localhost:44310/api/Licensee/{"name":"stan"}
in address field of my browser, getting the error
"title": "Unsupported Media Type", "status": 415
which is described as
... the origin server is refusing to service the request because the payload
is in a format not supported by this method on the target resource.
The suggested troubleshot is
... due to the request's indicated Content-Type or Content-Encoding, or as a result of inspecting the data ...
I can't really control what header the browser provides. Due to intended usage, I can't rely on Postman or a web application. It needs to be exected from the URL line. The parameter will differ in structure, depending what search criteria that are applied.
The controller looks like this.
[HttpGet("{parameters}")]
public async Task<ActionResult> GetLicensee(LicenseeParameters parameters)
{
return Ok(await Licensee.GetLicenseeByParameters(parameters));
}
I considered decorating the controller with [Consumes("application/json")] but found something dicouraging it. I tried to add JSON converter as suggested here and here but couldn't really work out what option to set, fumbling according to this, not sure if I'm barking up the right tree to begin with.
services.AddControllers()
.AddJsonOptions(_ =>
{
_.JsonSerializerOptions.AllowTrailingCommas = true;
_.JsonSerializerOptions.PropertyNamingPolicy = null;
_.JsonSerializerOptions.DictionaryKeyPolicy = null;
_.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
});
My backup option is to use query string specifying the desired options for a particular search. However, I'd prefer to use the object with parameters for now.
How can I resolve this (or at least troubleshoot further)?
The reason is that there might be a loooot of parameters and I don't want to refactor the controller's signature each time
Actually, you don't have to change the controller's signature each time. ASP.NET Core Model binder is able to bind an object from query string automatically. For example, assume you have a simple controller:
[HttpGet("/api/licensee")]
public IActionResult GetLicensee([FromQuery]LicenseeParameters parameters)
{
return Json(parameters);
}
The first time the DTO is:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
}
What you need is to send a HTTP Request as below:
GET /api/licensee?name=stan&note=it+works
And later you decide to change the LicenseeParameters:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
public List<SubNode> Children{get;set;} // a complex array
}
You don't have to change the controller signature. Just send a payload in this way:
GET /api/licensee?name=stan&note=it+works&children[0].nodeName=it&children[1].nodeName=minus
The conversion is : . represents property and [] represents collection or dictionary.
In case you do want to send a json string within URL, what you need is to create a custom model binder.
internal class LicenseeParametersModelBinder : IModelBinder
{
private readonly JsonSerializerOptions _jsonOpts;
public LicenseeParametersModelBinder(IOptions<JsonSerializerOptions> jsonOpts)
{
this._jsonOpts = jsonOpts.Value;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var name= bindingContext.FieldName;
var type = bindingContext.ModelType;
try{
var json= bindingContext.ValueProvider.GetValue(name).FirstValue;
var obj = JsonSerializer.Deserialize(json,type, _jsonOpts);
bindingContext.Result = ModelBindingResult.Success(obj);
}
catch (JsonException ex){
bindingContext.ModelState.AddModelError(name,$"{ex.Message}");
}
return Task.CompletedTask;
}
}
and register the model binder as below:
[HttpGet("/api/licensee/{parameters}")]
public IActionResult GetLicensee2([ModelBinder(typeof(LicenseeParametersModelBinder))]LicenseeParameters parameters)
{
return Json(parameters);
}
Finally, you can send a json within URL(suppose the property name is case insensive):
GET /api/licensee/{"name":"stan","note":"it works","children":[{"nodeName":"it"},{"nodeName":"minus"}]}
The above two approaches both works for me. But personally I would suggest you use the the first one as it is a built-in feature.

How do you pass thread safe data between the methods of a System.Web.Http.Filters.ActionFilterAttribute?

I have a custom attribute extension of System.Web.Http.Filters.ActionFilterAttribute that I am using for logging with Web API controllers. I am experiencing an issue that indicates that the attribute object is being reused call to call. Data in my public properties from an initial call will appear in the logged information for a subsequent call and so on.
I read in this post that I "should never store instance state in an action filter that will be reused between the different methods." He goes on to say,"This basically means that the same instance of the action filter can be reused for different actions and if you have stored instance state in it it will probably break."
My custom attribute is apparently "break" ing. Thus began my search to answer the question …
How do you pass thread safe data between the methods of a System.Web.Http.Filters.ActionFilterAttribute?
An example is given in the post I referenced above of how data should be passed method to method using the HttpContext.Items dictionary. That's great and I can see how that would help but I'm not using ASP.net MVC'sSystem.Web.Http.Mvc.ActionFilterAttribute – which the poster uses in his answer. I'm doing Web API and the context object passed into the OnActionExecuting method is of type HttpActionContext and not of type ActionExecutingContext. I do not have access to the HttpContext.Items dictionary through the passed context, however, I believe that it is safe to access the HttpContext like this:
HttpContext.Current.Items[key]
Is that safe?
I do not have access to that dictionary in the constructor however and since that is where I receive my parameterized message string as a positional parameter, I am seemingly dependent on stored instance state.
So what to do?
In this post – also dependent on ASP.net MVC's System.Web.Http.Mvc.ActionFilterAttributeand its ActionExecutingContext– the poster uses the ActionParameters property of that context object to get at the parameters passed to the attribute, but I cannot find any equivalent in Web API's HttpActionContext. If I could, this would seem to be the answer! But alas…
How can I safely get to the positional parameter value passed into my constructor and the named parameter value passed in through a public property within the OnActionExecuting method?
Posts I have researched:
Are ActionFilterAttributes reused across threads? How does that work?
MVC Action Filter and Multiple Threads
passing action method parameter to ActionFilterAttribute in asp.net mvc
Why is my ASP.NET Web API ActionFilterAttribute OnActionExecuting not firing?
System.Web.Mvc.ActionFilterAttribute vs System.Web.Http.Filters.ActionFilterAttribute
Web Api 2 HttpContext or HttpActionContext
Background: In the constructor, I pass a parameterized message string that includes placeholders for the values of arguments passed to the method the attribute is applied to. I also have an optional LogAllResponses property that is set through a named parameter to the attribute that I use to decide how much information I will log. The public properties that receive these values are set through the constructor and attribute invocation like this:
[LogAction("Retrieve information for all ad week items with storeId: {storeId}.", LogAllResponses = false)]
The important parts of the implementation of my action filter appear below:
public class LogActionAttribute : ActionFilterAttribute
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string ParameterizedMessage { get; set; }
public bool LogAllResponses { get; set; } = true;
public LogActionAttribute(string parameterizedMessage)
{
ParameterizedMessage = parameterizedMessage;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
HttpContext.Current.Items["__Parameterized_Message__"] = ParameterizedMessage;
HttpContext.Current.Items["__Log_All_Responses__"] = LogAllResponses.ToString();
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var contextualizedMessage = HttpContext.Current.Items["__Parameterized_Message__"] as string ?? "";
var logAllResponsesAsString = HttpContext.Current.Items["__Log_All_Responses__"] as string ?? "";
var logAllResponses = logAllResponsesAsString.CompareIgnoreCase("true") == 0;
// convert argument values with ID suffixes to identifiable names
var arguments = actionExecutedContext.ActionContext.ActionArguments;
//foreach (var arg in arguments)
// ...
// replace the placeholders in the parameterized message string with actual values
// log the contextualized message
//Log.Debug(...
base.OnActionExecuted(actionExecutedContext);
}
}

IAuthorizationFilter not called every time

We use an IAuthorizationFilter class to filter all request, and check if custom user claims are still present (multi-tenant app) in the authentication cookie. These information are essentials for the rest of the application. If these informations are not present, we redirect to the Login page.
public class TokenAuthorizationFilter : IAuthorizationFilter, IAsyncAuthorizationFilter
{
public TokenAuthorizationFilter()
{
// Some dependency injection ...
}
public void OnAuthorization(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
CheckToken(context);
}
public Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
return CheckToken(context);
}
}
And we register our filter like this
services.AddMvc(config =>
{
config.Filters.Add(typeof(TokenAuthorizationFilter));
});
And the controller's action that I want to access is very simple :
[Authorize(Policy = "TokenValid")]
public class HomeController : AjaxBaseController
{
public IActionResult Index()
{
return View();
}
}
We even not reached the Policy of our AuthorizeAttribute. As I can see in the stacktrace, Identity is attempting to create a Microsoft.AspNet.Identity.SignInManager somewhere in the middleware after checking for a CookieAuthenticationOptions, I assumed that he's attempting to re-login the user, but it's not checking for my Filter ? Login is very special in our application, so I don't want to let Identity log automatically our user. I can reproduced this issue when the authentication cookie expired.
Any ideas ? Thanks !
You also need to make TokenAuthorizationFilter inherit from AuthorizeAttribute for an authorization filter, and rename it as TokenAuthorizationFilterAttribute.
This will become an attribute that you will be able to call with [TokenAuthorizationFilter]:
[TokenAuthorizationFilter]
public class HomeController : AjaxBaseController
{
public IActionResult Index()
{
return View();
}
}
Be careful when implementing both IAuthorizationFilter and IAsyncAuthorizationFilter, as ASP.NET Core will only call the async method in this case: if you do not need any async call, then only implement the IAuthorizationFilter interface.
Also, if you keep to register the filter like this:
services.AddMvc(config =>
{
config.Filters.Add(typeof(TokenAuthorizationFilter));
});
You will notice that the filter will be called for every action, as it will force the authorization filter to be called every time, so in this case you do not need to add the attribute on top of your action.
Finally I found out the problem. Every 30 minutes, Identity is trying to validate the user through SecurityStamp validation, and that's making the app crash because it needed a database connection which doesn't exists at the time of the validation. We've desactive this validation in our startup by reimplementing the OnValidatePrincipal :
options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents { OnValidatePrincipal = (context) => Task.FromResult(0) };

Access object created in AuthenticationFilter from the Web API Controller

I have implemented a HMAC authentication filter (as per this article) in a Web API 2 project. The article uses static keys for demo purposes so I have modified the filter to look up the Private API Key from a database using an 'AppId' Guid. This works nicely as I'm able to load the appropriate account for that AppId entry. But I would like to know if it's possible to access the "Account" object created in the authentication filter class in my controller.
This is how I declared the object:
public class HMACAuthenticationAttribute : Attribute, IAuthenticationFilter
{
private static Dictionary<string, string> allowedApps = new Dictionary<string, string>();
public DummyAccount account = new DummyAccount();
}
And later down in the code:
// Load account and its private API Key
account = accountService.GetByAppId(Guid.Parse(appId), session);
if (account == null)
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
}
// If we find the account, we add to allowedApps the AppId/PrivateKey pair
allowedApps.Add(account.AppId.ToString(), account.ApiKey);
Now in my WebApi Controller, the code looks like this:
[HMACAuthentication]
[HttpPost]
public RatingDto Post(SearchTrackDto searchedTrack)
{
// Access 'account' object here?
}
My expectation is that I can somehow access the account object directly so I don't have to parse the request again and make a second database query. Would storing the object in the Request.Context be the way to go? What alternative is there?
You could try implementing a cache. The AuthenticationFilter is supposed to validate each Request, as the Context lifetime scope is as long as the requests dies.

How to specify attribute on call of method

I am having one action method.
Which is having 2 attribute
[Authorization]
[OutputCache]
ActionResult LoadImage()
I am calling LoadImage action from two method
say 1: Index 2: Create
When i call LoadImage action from Index, I want both attribute of LoadImage to execute.
When i call LoadImage action from Create, I want only Authorization attribute to be execute.
I don't want to use VaryByParam.
Please see my earlier answer and see if that satisfy your requirement. If you really have to achieve what you stated in your question, here is how...
Define a custom Authorization attribute. Check a value coming in Request.Params to make a decision about whether to apply the attribute or skip the authorization similar to what you achieve through AllowAnonymous attribute.
Example code (will require some changes as per your need):
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class ProspectProfileAuthorizationAttribute : AuthorizeAttribute
{
/// <summary>
/// Special authorization check based on whether request contain valid data or not.
/// </summary>
/// <param name="filterContext"></param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
Guard.ArgumentNotNull(filterContext, "filterContext");
Guard.ArgumentNotNull(filterContext.Controller, "filterContext.Controller");
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(
typeof(CustomAllowAnonymous), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(
typeof(CustomAllowAnonymous), inherit: true);
if (skipAuthorization)
{
return;
}
var request = filterContext.RequestContext.HttpContext.Request;
NameValueCollection parameterCollection = ReadQueryStringData(filterContext, request);
if (parameterCollection.Count < 3)
{
throw new InvalidOperationException("Request with invalid number of parameter");
}
// Check 1: Is request authenticated i.e. coming from browser by a logged in user
// No further check required.
if (request.IsAuthenticated)
{
return;
}
// Check 2: Request is coming from an external source, is it valid i.e. does it contains
// valid download code.
if (string.IsNullOrEmpty(downloadCode))
{
throw new InvalidOperationException(Constants.Invalid_Download_Code);
}
if (!userType.Equals(Constants.SystemIntegrationUserName))
{
var exportReportService = DependencyResolver.Current.GetService<IExportReportService>();
if (exportReportService != null)
{
if (!exportReportService.VerifyDownloadCode(downloadCode))
{
// Invalid partner key
throw new InvalidOperationException(Constants.Invalid_Download_Code);
}
}
}
}
private static NameValueCollection ReadQueryStringData(AuthorizationContext filterContext, HttpRequestBase request)
{
// Obtain query string parameter from request
//original
//var encryptedData = request.Params["data"];
// Applying the replace for space with + symb
var encryptedData = request.Params["data"].Replace(" ","+");
var decryptedData = EncryptionHelper.DecryptString(encryptedData);
// Validate the parameter
var dict = HttpUtility.ParseQueryString(decryptedData);
return dict;
}
}
As pointed by Peter Duniho, in this situation you should have two action methods with different attribute applied to each action method as applicable.
As far as redundancy is concerned you can have common logic in a private method. This private method can be called from public action method.
I am not offering a direct solution to your problem here however I thought its important to clarify that sometimes you have to make decision to choose one principle over other. In this case I think KISS Vs DRY.
The suggestion here is to keep it simple and have two methods. It does not directly violate DRY anyway.

Categories

Resources