Attribute To Secure Web Api - c#

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>();
});

Related

C# .Net Core Web API Check CustomSignature

I am new the API in general, let me give you the background of the API and what I want it to do.
I have a API have that are external facing and so every incoming request are required to check the signature from header. literality my code in every controller call are checking the signature and created many duplicated code.
my question is how can reduces those duplicated code ? do I use Custom Attributes, or AuthorizeAttribute
here are some of the example code:
[Route("[controller]")]
[ApiController]
public class ExampleController : ControllerBase
{
public async Task<Result> Call_1(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
public async Task<Result> Call_2(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
}
above code is just for showing, the actual Sinature checking logic is around 20 lines of code on every single controller method.
Yes, you can do that using action filters. It's described in documentation
Put your code for checking into OnActionExecuting method. So, you can write Result in the action filter if the signature isn't valid.
In case you need specific result structure you can create your own ObjectResult:
public class ForbiddenObjectResult : ObjectResult
{
public string Message { get; private set; }
public ForbiddenObjectResult(object value, string message)
: base(value)
{
StatusCode = StatusCodes.Status403Forbidden;
Message = message;
}
}
...
string signaturel;
signature = Util.getHeaderSignature(context.HttpContext.Request);
if(!unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
context.Result = new ForbiddenObjectResult(filterContext.ModelState, "InvalidSinaturemessage");
}
And to register it for all your endpoints(if needed):
services.AddControllersWithViews(options =>
{
options.Filters.Add<YourActionFilter>();
});
You can use token based authentication or filter method. For reference
Token based authentication
Custom Filter

Aurelia Browser Console Error - POST Request - Response to preflight request doesn't pass access control check

Good day StackOverflow! I've opened up a new question because I am a total beginner with Web Services and the current topics similar to my question doesn't make any sense to me at the moment. I am very welcome to learn something new. I would be happy to receive response and support from the community.
Currently I am having a Web Development training in a company and one of our task is to create a Web Service using "Microsoft ASP.NET Core 2.0 Web API" using MVC and enabling CORS for our Aurelia application.
My Aurelia app is hosted in http://localhost:9000/ and the webservice is in http://localhost:5000/ as tests.
Here are the problems that I've encountered and my observations:
Whenever I run my Aurelia app, I am getting this error on the browser console: "Failed to
load http://localhost:5000/api/sample: Response to preflight request
doesn't pass access control check: No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin
'http://localhost:9000' is therefore not allowed access." Is there any configuration on my C# code that i need to add in order for
this error to go away?
I used PostMan in order to check if the web service is working, and yes it did work. So I was wondering what was wrong if I access
the web service from my Aurelia application, it generates an error. I
guess the error is in the client side? Here is the screenshot of the
PostMan request and response.
If I pass an object from my aurelia app to the web service as HTTP POST request, does the web service understands/maps right away the
object values received?
And also on the Web API Debug Console, it says: "Request method POST not allowed in CORS policy."
To make it simpler, I have this code on my Aurelia app.ts written in TypeScript which requests the sample data through HTTP Post verb:
import { inject } from 'aurelia-framework';
import { HttpClient } from 'aurelia-http-client';
#inject(HttpClient)
export class WebAPITest {
private httpClient: HttpClient;
private static readonly BASE_URL = `http://localhost:5000/api/`;
private message = `Web API Access Test! Pls. check the console.`;
constructor(httpClient: HttpClient) {
this.httpClient = httpClient;
this.httpClient.configure(requestBuilder => {
requestBuilder.withBaseUrl(WebAPITest.BASE_URL);
requestBuilder.withHeader('Content-Type', 'application/json'); // (?) Need clarifications.
});
}
activate() {
let sampleData = new SampleData();
return this.httpClient.post(`sample`, sampleData)
.then(response => {
if (response.isSuccess) {
this.data = response.content;
console.log(`SampleData Web Service Call SUCCEED!`);
} else {
console.log(`SampleData Web Service Call FAILED!`);
}
});
}
}
export class SampleData {
public name: string;
public age: number;
constructor() {
this.name = "Garfield";
this.age = 5;
}
}
Here is the code of my ASP.NET Core 2.0 MVC Web API: (Startup.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace Syslog.Web.GradeSheet.Backend
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddCors();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// For now it only accepts requests from localhost port 9000 which is the seat of our Aurelia applications.
app.UseCors(corsPolicyBuilder => {
corsPolicyBuilder.WithOrigins("http://localhost:9000");
});
app.UseMvc();
// Normally this will be fired up when no route has been matched.
app.Run(async (context) =>
{
await context.Response.WriteAsync("Welcome To GradeSheet Web Service! MVC has not found any Route that has been matched yet.");
});
}
}
}
Here is the code of my ASP.NET Core 2.0 MVC Web API: (SampleController.cs):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Syslog.Web.GradeSheet.Backend.Controllers
{
[Route("api/[controller]")]
public class SampleController : ControllerBase
{
[HttpPost]
public IActionResult getSampleObject([FromBody] SampleData sampleData) {
if(ModelState.IsValid) {
System.Diagnostics.Debug.WriteLine($"PRINTED: {sampleData.name} is {sampleData.age} years old.");
} else {
System.Diagnostics.Debug.WriteLine("ModelState is not Valid.");
}
return Ok($"Ok, got it, {sampleData.name}! You are {sampleData.age} years old.");
}
}
public class SampleData {
public string name { get; set; }
public int age { get; set; }
}
}
Thank you very much for the time reading my problem. I would appreciate any
solutions, recommendations, additional information or criticisms on my code. Have a nice day.
The issue is in the MVC Startup here. You're not fully configuring your CORS builder, you're only configuring the allowed origins, but not the rest of the configuration.
If you change it to this, it should work fine:
app.UseCors(corsPolicyBuilder => {
corsPolicyBuilder.WithOrigins("http://localhost:9000").AllowAnyHeader().AllowAnyMethod().AllowCredentials();
});

OData Web Api with Caching

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.

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();
}
}

ASP.NET WebApi FormsAuthentication 401 (Unauthorized) issue

I have been learning how authorization works in ASP.Net WebApi and I came across an answer by Darin Dimitrov in another posting ( ASP.NET Web API Authentication ) and I need some help understanding why I'm getting a 401.
Following Darin's code, I have created a WebApi project and added the following controllers and model:
AccountController.cs
using System.Web.Http;
using System.Web.Security;
using AuthTest.Models;
namespace AuthTest.Controllers
{
public class AccountController : ApiController
{
public bool Post(LogOnModel model)
{
if (model.Username == "john" && model.Password == "secret")
{
FormsAuthentication.SetAuthCookie(model.Username, false);
return true;
}
return false;
}
}
}
UsersController.cs
using System.Web.Http;
namespace AuthTest.Controllers
{
[Authorize]
public class UsersController : ApiController
{
public string Get()
{
return "This is top secret material that only authorized users can see";
}
}
}
LogOnModel.cs
namespace AuthTest.Models
{
public class LogOnModel
{
public string Username { get; set; }
public string Password { get; set; }
}
}
I have created a Web Forms app with two buttons and a label for testing purposes.
Default.aspx.cs
using System;
using System.Net.Http;
using System.Threading;
namespace AuthTestWebForms
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void ButtonAuthorizeClick(object sender, EventArgs e)
{
using (var httpClient = new HttpClient())
{
var response = httpClient.PostAsJsonAsync(
"http://localhost/authtest/api/account",
new { username = "john", password = "secret" },
CancellationToken.None
).Result;
response.EnsureSuccessStatusCode();
bool success = response.Content.ReadAsAsync<bool>().Result;
if (success)
{
//LabelResponse.Text = #"Credentials provided";
var secret = httpClient.GetStringAsync("http://localhost/authtest/api/users");
LabelResponse.Text = secret.Result;
}
else
{
LabelResponse.Text = #"Sorry, you provided the wrong credentials";
}
}
}
protected void ButtonTestAuthClick(object sender, EventArgs e)
{
using (var httpClient = new HttpClient())
{
var secret = httpClient.GetStringAsync("http://localhost/authtest/api/users");
LabelResponse.Text = secret.Result;
}
}
}
}
When I click the button and run ButtonAuthorizeClick() it fires the controller for Account and then fires the controller for Users and everything is fine.
If I then click the ButtonTestAuthClick(), I get a 401 (Unauthorized) error.
When I look for the ASPXAUTH cookie in Chrome or FireFox, I don't see one, so I'm not 100% sure why ButtonAuthorizeClick() works and what I need to do to make ButtonTestAuthClick() work.
Thanks for any help anybody can throw my way.
I was having a similar problem, though not via a Web Forms client page but rather with JavaScript and AJAX calls. Turns out I had left the authentication mode in the web.config left at "None". Obviously, you have to turn on Forms Authentication here in order for the FormsAuthentication.SetAuthCookie() method to have any effect.
<authentication mode="Forms" />
Once I fixed this oversight, everything starting working fine. :-)
You are calling web api with authentication in the middle. Why not your don't authenticate the user on the client side by ajax?
The problem here is everytime your send a request to web api by HttpClient, it actually is a new web request handled by server. All the cookie information won't be kept in the current request. In order to support this scenario, you need to handle the cookie by yourself.
For example: Set cookie ASPXAUTH to asp.net headers in ButtonAuthorizeClick method if the response has it.
Set cookie ASPXAUTH to HttpRequestMessage and send it by HttpClient.
Web api recently added a support to use HttpServer to create in-proc server and can directly send request to the current message handler in current process. So you can write code like:
HttpClient c = new HttpClient(new HttpServer(GlobalConfiguration.DefaultHandler));
c.GetStringAsync("http://localhost/api/Values").Wait();
To send your request in-proc, so that the cookie header set in the web api action will still in the pipeline of the current request. The check-in seems not in the RTM release. You may try it's nightly build http://aspnetwebstack.codeplex.com/discussions/353867.
Though it is late, The client in ButtonTestAuthClick is not the browser. It is the httpClient object here. Thus, you need to programmatically set the cookie generated from other button.

Categories