OData Web Api with Caching - c#

I have a problem and hope someone can help :)
I'm using OData Web Api (Microsoft.AspNetCore.OData) and I'd like to do some request caching. I have the following considerations:
I know my data only updates every 15 min, which is when I'd invalidate my cache
I've tried Microsoft.AspNetCore.ResponseCaching which works for me as long as there's no Authentication Header. But half of my request contain Authentication headers containing a signed JWT of a authenticated user from a trusted service (all internal services/users)
I'm trying to use a Resource Filter but the problem I'm having here is the OData Controllers return Queryables, not only do I think it doesn't make sense to cache them but the get disposed at end of the request.
Here is my OData controller, pretty simple:
[ODataRoute()]
public IQueryable<T> Get(ODataQueryOptions opts)
{
return _dbContext.Set<T>().AsQueryable();
}
And here is my attempted ResourceCaching filter:
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
namespace Service.Helpers
{
public class ResourceCacheFilter : IActionFilter
{
private IMemoryCache _memoryCache;
public ResourceCacheFilter(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public void OnActionExecuting(ActionExecutingContext context)
{
var path = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
if (_memoryCache.TryGetValue(path, out ObjectResult value))
{
context.Result = value;
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null) return;
var path = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
var cacheEntryOpts = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(15));
_memoryCache.Set(path, context.Result, cacheEntryOpts);
}
}
}
Yes I realize the path is probably not the best key, I'll take suggestions :)
Any help on how I could make progress on my problem would be appreciated! Thank you.

Related

Attribute To Secure Web Api

I am working with a web api where it should have a request key and depending upon it, the api controller will do
specific task. I am using rest client program in vs code and did the following for testing:
GET http://localhost:PortNo/WeatherForecast/GetAllTeams
test: "12345678910" //Key
So in the controller, I did this to get the key value:
[HttpGet]
public async Task<ActionResult<IEnumerable<TeamDetails>>> GetAllTeams()
{
string Token = Request.Headers["test"]; //Getting the key value here
var teams = _service.GetAllTeams();
return Ok(teams)
}
But I've few things in mind and doing R & D like how can I make the above with an attribute. Say each controller
will have an attribute as follows and make the request invalid if no proper key found:
[InvalidToken] //This is the attribute
[HttpGet]
public async Task<ActionResult<IEnumerable<TeamDetails>>> GetAllTeams()
{
var teams = _service.GetAllTeams();
return Ok(teams)
}
I am not sure if this is going to make the api secure and my plan is to valid every http request (In my case, a simple form submission at the moment), so it should say the request is generated from the web api app.
N.B: I worked with web api earlier in small sections but now a broader thing to implement, so expecting few suggestions that can help me to guide for better design.
try it:
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
..
public class InvalidToken : Attribute, IActionFilter
{
public InvalidToken( )
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
var Authorization = context.HttpContext.Request.Headers["test"];
if ( Authorization != "12345678910")
{
context.ModelState.AddModelError("Authorization", "Authorization failed!");
return;
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// "OnActionExecuted"
}
}
Startup.cs
services.AddScoped<InvalidToken>();
// add filter to whole api
services.AddControllers(options =>
{
options.Filters.Add<InvalidToken>();
});

Sending a http response through a custom attribute in dotnet

I'm creating a custom attribute in dotnet that is supposed to check the authorization header. If it is the same as some hard coded string it is supposed to pass but else the user should not be able to use the specified route.
I think I'm getting the response header correctly but I'm not sure how to send a HTTP response if it fails.
public class CustomAuthorization : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
var httpContext = context.HttpContext;
string authHeader = httpContext.Request.Headers["Authorization"];
if(authHeader == "Kawaii")
{
return;
//do nothing cause its fine
}
else
{
httpContext.Response.WriteAsync("The authorization header was incorrect, is should be Kawaii");
}
}
}
Any help would be greatly appreciated!
From what you've described, it sounds like you should be using OnActionExecuting instead of OnActionExecuted. Within the body, instead of writing to context.HttpContext.Response, you set context.Result to an ActionResult representing the response
public class CustomAuthorization : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
string authHeader = context.HttpContext.Request.Headers["Authorization"];
if(authHeader == "Kawaii")
return;
context.Result = new UnauthorizedResult();
}
}
However, this approach sounds like a better fit for an AuthorizationFilter instead of an ActionFilter. Have a look at the filter pipeline documentation for a list of the different types of filters and what they do.

Unit testing and multiple parameters to constructors in Asp.Net Core MVC, Passing token to every request

I am trying to add one more parameter to my constructors in my Asp.Net Core MVC application, but facing some difficulties to do so. Here is what my implementation looks like.
Login action:
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public IActionResult Login(LoginViewModel loginModel, string returnUrl = null)
{
returnUrl = string.IsNullOrWhiteSpace(returnUrl) ? ApiConstants.Dashboard : returnUrl;
ViewData["ReturnUrl"] = returnUrl;
if (!ModelState.IsValid) return View(loginModel);
var token = Service.Login(loginModel);
if (string.IsNullOrWhiteSpace(token)) return View(loginModel);
TempData["token"] = token;
AddCookie(token);
return RedirectToAction("Index", "Dashboard");
}
private void AddCookie(string token)
{
HttpContext.Response.Cookies.Append("token", token,new CookieOptions()
{
Expires = DateTimeOffset.Now.AddDays(-1)
});
}
Controller:
private readonly INozzleService _nozzleService;
public NozzleController(INozzleService nozzleService)
{
var token = HttpContext.Request.Cookies["token"];
_nozzleService = nozzleService;
}
Nozzle Service:
private static INozzleAdapter Adapter { get; set; }
public NozzleService(INozzleAdapter adapter)
{
Adapter = adapter;
}
Nozzle Adapter:
private readonly string _token;
public NozzleAdapter(string token)
{
_token = token;
}
Once I get the token in the adapter, I will be adding the token to the HttpClient header.
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
ConfigureServices in Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
services.AddDistributedMemoryCache();
services.AddSession();
services.AddTransient<IAccountService, AccountService>();
services.AddTransient<IAccountAdapter, AccountAdapter>();
services.AddTransient<INozzleService, NozzleService>();
services.AddTransient<INozzleAdapter, NozzleAdapter>();
services.AddMvc();
}
Can you please let me know what could be the best way to achieve this in Asp.Net core 2.0 MVC application? I have read a post saying that using multiple constructors is not a good idea in Asp.Net Core MVC application, so I don't want to use multiple constructors.
At the same time, I want to make sure all of my classes are unit testable with DI. What should be the best approach here?
Please let me know if anyone needs more information.
Update:
As per Shyju's solution, I was able to implement the cookie, however, I am still in a need to pass two parameters to one of my controllers.
private readonly IAccountService _service;
private readonly ITokenProvider _tokenProvider;
public AccountController(IAccountService service, ITokenProvider tokenProvider)
{
_service = service;
_tokenProvider = tokenProvider;
}
So that I can, use the method AddToken as below.
_tokenProvider.AddToken(token);
You may consider abstracting out the logic to get the token to a separate class and inject that as needed.
public interface ITokenProvider
{
/// <summary>
/// Gets the token
/// </summary>
/// <returns></returns>
string GetToken();
}
Now create an implementation of this, which will be reading the token from the cookie. Here is a simple implementation, which reads the token from the cookies collection
public class CookieTokenProvider : ITokenProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public CookieTokenProvider(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetToken()
{
if (httpContextAccessor.HttpContext.Request.Cookies
.TryGetValue("token", out string tokenValue))
{
return tokenValue;
}
return null;
}
}
Now, you can inject the ITokenProvider implementation to anyplace you want and call the GetToken method to get the token value. For example, you may inject this to the NozzleAdapter class constructor.
private readonly ITokenProvider tokenProvider;
public NozzleAdapter(ITokenProvider tokenProvider)
{
tokenProvider=tokenProvider;
}
public string SomeOtherMethod()
{
var token = this.tokenProvider.GetToken();
//Do null check and use it
}
Make sure you register this in the ConfigureServices method in Startup class
services.AddTransient<ITokenProvider, CookieTokenProvider>();
Regarding your comment about getting the token and persisting it, it is up to you where you want to do it. You can do that in the CookieTokenProvider implementation. Read the value and store it somewhere ( a local db, in memory cache etc) and get it from there if exists (the next time)
Now, for your unit tests you can create a MockTokenProvider which does not use HttpContext, but simply return a mock value for your testing,
public class MockTokenProvider : ITokenProvider
{
public string GetToken() => "FakeToken";
}

ValidateAntiForgeryToken in Ajax request with AspNet Core MVC

I have been trying to recreate an Ajax version of the ValidateAntiForgeryToken - there are many blog posts on how to do this for previous versions of MVC, but with the latest MVC 6, none of the code is relevant. The core principle that I am going after, though, is to have the validation look at the Cookie and the Header for the __RequestVerificationToken, instead of comparing the Cookie to a form value. I am using MVC 6.0.0-rc1-final, dnx451 framework, and all of the Microsoft.Extensions libraries are 1.0.0-rc1-final.
My initial thought was to just inherit ValidateAntiForgeryTokenAttribute, but looking at the source code, I would need to return my own implementation of an an Authorization Filter to get it to look at the header.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateAjaxAntiForgeryTokenAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter
{
public int Order { get; set; }
public bool IsReusable => true;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<ValidateAjaxAntiforgeryTokenAuthorizationFilter>();
}
}
As such, I then made my own version of ValidateAntiforgeryTokenAuthorizationFilter
public class ValidateAjaxAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy
{
private readonly IAntiforgery _antiforgery;
private readonly ILogger _logger;
public ValidateAjaxAntiforgeryTokenAuthorizationFilter(IAntiforgery antiforgery, ILoggerFactory loggerFactory)
{
if (antiforgery == null)
{
throw new ArgumentNullException(nameof(antiforgery));
}
_antiforgery = antiforgery;
_logger = loggerFactory.CreateLogger<ValidateAjaxAntiforgeryTokenAuthorizationFilter>();
}
public async Task OnAuthorizationAsync(AuthorizationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (IsClosestAntiforgeryPolicy(context.Filters) && ShouldValidate(context))
{
try
{
await _antiforgery.ValidateRequestAsync(context.HttpContext);
}
catch (AjaxAntiforgeryValidationException exception)
{
_logger.LogInformation(1, string.Concat("Ajax Antiforgery token validation failed. ", exception.Message));
context.Result = new BadRequestResult();
}
}
}
protected virtual bool ShouldValidate(AuthorizationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
return true;
}
private bool IsClosestAntiforgeryPolicy(IList<IFilterMetadata> filters)
{
// Determine if this instance is the 'effective' antiforgery policy.
for (var i = filters.Count - 1; i >= 0; i--)
{
var filter = filters[i];
if (filter is IAntiforgeryPolicy)
{
return object.ReferenceEquals(this, filter);
}
}
Debug.Fail("The current instance should be in the list of filters.");
return false;
}
}
However, I cannot find the proper Nuget package and namespace that contains IAntiforgeryPolicy. While I found the interface on GitHub - what package do I find it in?
My next attempt was to instead go after the IAntiforgery injection, and replace the DefaultAntiforgery with my own AjaxAntiforgery.
public class AjaxAntiforgery : DefaultAntiforgery
{
private readonly AntiforgeryOptions _options;
private readonly IAntiforgeryTokenGenerator _tokenGenerator;
private readonly IAntiforgeryTokenSerializer _tokenSerializer;
private readonly IAntiforgeryTokenStore _tokenStore;
private readonly ILogger<AjaxAntiforgery> _logger;
public AjaxAntiforgery(
IOptions<AntiforgeryOptions> antiforgeryOptionsAccessor,
IAntiforgeryTokenGenerator tokenGenerator,
IAntiforgeryTokenSerializer tokenSerializer,
IAntiforgeryTokenStore tokenStore,
ILoggerFactory loggerFactory)
{
_options = antiforgeryOptionsAccessor.Value;
_tokenGenerator = tokenGenerator;
_tokenSerializer = tokenSerializer;
_tokenStore = tokenStore;
_logger = loggerFactory.CreateLogger<AjaxAntiforgery>();
}
}
I got this far before I stalled out because there is no generic method on ILoggerFactory for CreateLogger<T>(). The source code for DefaultAntiforgery has Microsoft.Extensions.Options, but I cannot find that namespace in any Nuget package. Microsoft.Extensions.OptionsModel exists, but that just brings in the IOptions<out TOptions> interface.
To follow all of this up, once I do get the Authorization Filter to work, or I get a new implementation of IAntiforgery, where or how do I register it with the dependency injection to use it - and only for the actions that I will be accepting Ajax requests?
I had similar issue. I don't know if any changes are coming regarding this in .NET but, at the time, I added the following lines to ConfigureServices method in Startup.cs, before the line services.AddMvc(), in order to validate the AntiForgeryToken sent via Ajax:
services.AddAntiforgery(options =>
{
options.CookieName = "yourChosenCookieName";
options.HeaderName = "RequestVerificationToken";
});
The AJAX call would be something like the following:
var token = $('input[type=hidden][name=__RequestVerificationToken]', document).val();
var request = $.ajax({
data: { 'yourField': 'yourValue' },
...
headers: { 'RequestVerificationToken': token }
});
Then, just use the native attribute [ValidadeAntiForgeryToken] in your Actions.
I've been wrestling with a similar situation, interfacing angular POSTs with MVC6, and came up with the following.
There are two problems that need to be addressed: getting the security token into MVC's antiforgery validation subsystem, and translating angular's JSON-formatted postback data into an MVC model.
I handle the first step via some custom middleware inserted in Startup.Configure(). The middleware class is pretty simple:
public static class UseAngularXSRFExtension
{
public const string XSRFFieldName = "X-XSRF-TOKEN";
public static IApplicationBuilder UseAngularXSRF( this IApplicationBuilder builder )
{
return builder.Use( next => context =>
{
switch( context.Request.Method.ToLower() )
{
case "post":
case "put":
case "delete":
if( context.Request.Headers.ContainsKey( XSRFFieldName ) )
{
var formFields = new Dictionary<string, StringValues>()
{
{ XSRFFieldName, context.Request.Headers[XSRFFieldName] }
};
// this assumes that any POST, PUT or DELETE having a header
// which includes XSRFFieldName is coming from angular, so
// overwriting context.Request.Form is okay (since it's not
// being parsed by MVC's internals anyway)
context.Request.Form = new FormCollection( formFields );
}
break;
}
return next( context );
} );
}
}
You insert this into the pipeline with the following line inside the Startup.Configure() method:
app.UseAngularXSRF();
I did this right before the call to app.UseMVC().
Note that this extension transfers the XSRF header on any POST, PUT or DELETE where it exists, and it does so by overwriting the existing form field collection. That fits my design pattern -- the only time the XSRF header will be in a request is if it's coming from some angular code I've written -- but it may not fit yours.
I also think you need to configure the antiforgery subsystem to use the correct name for the XSRF field name (I'm not sure what the default is). You can do this by inserting the following line into Startup.ConfigureServices():
services.ConfigureAntiforgery( options => options.FormFieldName = UseAngularXSRFExtension.XSRFFieldName );
I inserted this right before the line services.AddAntiforgery().
There are several ways of getting the XSRF token into the request stream. What I do is add the following to the view:
...top of view...
#inject Microsoft.AspNet.Antiforgery.IAntiforgery af
...rest of view...
...inside the angular function...
var postHeaders = {
'X-XSRF-TOKEN': '#(af.GetTokens(this.Context).FormToken)',
'Content-Type': 'application/json; charset=utf-8',
};
$http.post( '/Dataset/DeleteDataset', JSON.stringify({ 'siteID': siteID }),
{
headers: postHeaders,
})
...rest of view...
The second part -- translating the JSON data -- is handled by decorating the model class on your action method with [FromBody]:
// the [FromBody] attribute on the model -- and a class model, rather than a
// single integer model -- are necessary so that MVC can parse the JSON-formatted
// text POSTed by angular
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult DeleteDataset( [FromBody] DeleteSiteViewModel model )
{
}
[FromBody] only works on class instances. Even though in my case all I'm interested in is a single integer, I still had to dummy up a class, which only contains a single integer property.
Hope this helps.
Using a anti forgery token in a Ajax call is possible but if you are trying to secure a Api I really would suggest using a Access Token instead.
If you are relying on a identity token stored in a cookie as authentication for your Api, you will need to write code to compensate for when your cookie authentication times out, and your Ajax post is getting redirected to a login screen. This is especially important for SPAs and Angular apps.
Using a Access Token implementation instead, will allow you to refresh you access token (using a refresh token), to have long running sessions and also stop cookie thiefs from accessing your Apis.. and it will also stop XSRF :)
A access token purpose is to secure resources, like Web Apis.

Owin SelfHosted WebApp does not fulfill HEAD requests

I'm self hosting a web app using Microsoft.Owin.Hosting.WebApp, but after making a HEAD request to the server, it throws a 500 error. When trying to pull a JSON file, the error changes to 504.
I've seen many solutions, but none applying to WebApp. If hosting with NancyFX, I could set AllowChunkedEncoding to false to make it work. But that doesn't seems like a good option.
Code snippet:
var options = new StartOptions("http://localhost:8080")
{
ServerFactory = "Microsoft.Owin.Host.HttpListener"
};
WebApp.Start<Startup>(options);
Implementation of Startup:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseNancy();
}
}
Both calling the browser or using Fiddle causes a failure:
I haven't added the Nancy Module implementation here because it's not where the problem should be fixed, as I also want to serve static content, but allowing HEAD request on them.
Does anyone knows how to serve HEAD verbs from a Self Hosted OWIN?
I just ran into a very similar issue like this. I learned that HEAD method responses should be identical to GET responses but with no content.
Here's the relevant RFC: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
Example I have for my self-hosted Web api app:
[HttpHead]
[HttpGet]
[ResponseType(typeof(string))]
public HttpResponseMessage LiveCheck(HttpRequestMessage request)
{
HttpResponseMessage response;
response = request.CreateResponse(HttpStatusCode.OK);
if (request.Method == HttpMethod.Get)
{
response.Content = new StringContent("OK", System.Text.Encoding.UTF8, "text/plain");
}
return response;
}
I had a similar issue with a self-hosted SignalR app where HEAD requests caused an app crash and returned error code 500. The solution I found was to write a custom OWIN middleware layer to intercept HEAD requests and return code 200.
Create a new class in your project called HeadHandler.cs
using Microsoft.Owin;
using System.Threading.Tasks;
namespace YourProject
{
public class HeadHandler : OwinMiddleware
{
public HeadHandler(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
if (context.Request.Method == "HEAD")
{
context.Response.StatusCode = 200;
}
else
{
await Next.Invoke(context);
}
}
}
}
In your OWIN Startup class, add a line before mapping any other middleware to use the new HeadHandler middleware.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use<HeadHandler>();
//The rest of your original startup class goes here
//app.UseWebApi()
//app.UseSignalR();
}
}

Categories