I've built a RESTful API (using ASP.NET Web API 2) which is only meant to be consumed from a single end-point. This end-point is a basic front-end site containing only HTML/CSS/JS. Due to various reasons, the front-end site and the API are completely external from one-another, with the front-end site being whitelisted in the API's CORS configuration.
I'm now trying to lock-down the API so that it's only accessible from this particular end-point, without introducing a new login system, because the context of where this page lives ensures that anyone accessing it is already a trusted user (it's technically behind a login system, but the page consuming the API has almost no knowledge of this context).
At a high level, I'd like to introduce a statically defined API Key of some sort, that would be hardcoded into both the API and the JavaScript of the consuming page, to help ensure that it's the only end-point accessing the API. We can assume that all communications between the front-end page and the API will be over a secure SSL/TLS connection.
My question: for such a case where I want to authenticate API requests from a particular page with a statically-defined API Key, what would be my best option from an ease-of-implementation standpoint? Most of the articles that I've found on Web API Authorization pivot around a user login system and seem grossly over-engineered for my particular use-case. I'd consider myself a novice when it comes to the subject and so I'm really just hoping for someone to point me in the right direction.
Thanks!
It seems like you are looking for a global filter in this specific case.
An authentication filter is a component that authenticates an HTTP request
You would basically send the shared / static api key with every request in the Authorization header and the custom filter would process this and decide whether the request is valid or not.
A basic implementation of the filter:
public class ApiKeyAuthenticationAttribute : IAuthenticationFilter
{
public bool AllowMultiple { get; set; }
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
// Get Auth header
AuthenticationHeaderValue authorization = request.Headers.Authorization;
// Validate the static token
if (authorization?.Parameter == "123")
{
IPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new Claim("CLAIMTYPE", "CLAIMVALUE") }));
context.Principal = principal;
}
else
{
context.ErrorResult = new AuthenticationFailureResult(request);
}
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var challenge = new AuthenticationHeaderValue("Basic");
context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
return Task.FromResult(0);
}
}
And to enable it for all calls to your api add it to your WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Some more config here
config.Filters.Add(new IdentityBasicAuthenticationAttribute());
}
}
The AuthenticationFailureResult and AddChallengeOnUnauthorizedResult are implementations of IHttpActionResult. For comprehensiveness I will add them here.
AuthenticationFailureResult
class AuthenticationFailureResult : IHttpActionResult
{
private HttpRequestMessage _request;
public AuthenticationFailureResult(HttpRequestMessage request)
{
_request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.RequestMessage = _request;
response.Content = new StringContent("ACCESS DENIED MESSAGE");
return Task.FromResult(response);
}
}
AddChallengeOnUnauthorizedResult
class AddChallengeOnUnauthorizedResult : IHttpActionResult
{
public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
{
Challenge = challenge;
InnerResult = innerResult;
}
public AuthenticationHeaderValue Challenge { get; private set; }
public IHttpActionResult InnerResult { get; private set; }
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = await InnerResult.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
// Only add one challenge per authentication scheme.
if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
{
response.Headers.WwwAuthenticate.Add(Challenge);
}
}
return response;
}
}
This code is from or a derivative of this article Authentication Filters in ASP.NET Web API 2 and this article Authentication Filters in ASP.NET Web API 2
Related
For my application i need to make a named client for HttpRequests. I can create a named client in Startup. And to access it i inject an "IHttpClientFactory" and create a client from that. But the client needs to have an access token as an authorization header, and i cannot create the token in Startup. Therefor i need a way to create a named client outside of the Startup class. i have already tried injecting "IServiceCollection" into a controller. But this does not work.
Or is there maybe a way to edit a named client after it is already created in startup?
A similar solution to the one posted by #Ruben-J is to create a custom HttpMessageHandler which assigns an authorization header to requests made through the HttpClient at request-time.
You can create a custom HttpMessageHandler that can be assigned to a named HttpClient in Startup like so:
public class YourHttpMessageHandler : DelegatingHandler
{
private readonly IYourTokenProviderService _yourTokenProviderService;
public YourHttpMessageHandler(IYourTokenProviderService yourTokenProviderService)
: base()
{
_yourTokenProviderService = yourTokenProviderService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = SendAsyncWithAuthToken(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
await _yourTokenProviderService.RefreshTokenAsync();
response = SendAsyncWithAuthToken(request, cancellationToken);
}
return response;
}
private async Task<HttpResponseMessage> SendWithAuthTokenAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _yourTokenProviderService.Token);
return await base.SendAsync(request, cancellationToken);
}
}
You then configure your services and named HttpClient in Startup:
public virtual void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<IYourTokenProviderService, YourTokenProviderService>();
services.AddTransient<YourHttpMessageHandler>();
services.AddHttpClient<IYourNamedHttpClient, YourNamedHttpClient>()
.AddHttpMessageHandler<YourHttpMessageHandler>();
...
}
Its worth noting that the current implementation of Polly's AddPolicyHandler is also adding its own DelegatingHandler.
For more background see the Microsoft documentation on adding DelegatingHandler's. Here is also great series of articles from Steve Gordon.
You could use Polly to add a policy handler to your client. You can then add logic if a request returns a 401 Unauthorized. So for example get your service that uses the client to refresh a bearer token and also set it for the current request. This is just a quick solution and maybe there are more elegant solutions. But this will also come in handy if your token expires. Cause then it will be refreshed automatically.
services.AddHttpClient("YourClient")
.AddPolicyHandler((provider, request) =>
{
return Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(1, async (response, retryCount, context) =>
{
var service = provider.GetRequiredService<IYourService>();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await service.RefreshToken());
});
});
I program ASP.NET Framework MVC and Web API 2
I have to access a REST service for some information. The nature of the security requirements for this service require that I ask from a limited set of known IP addresses. The nature of my client requirements is that there will be an unknown number of them with IPs that are assigned by some DHCP. I think I need to stand up a proxy that will forward requests to the service and return responses to the client that asked. This server can be assigned a single static IP, that I can register with the target service. I don't want to try to duplicate the signatures of the target service and have to maintain my proxy whenever they decide to improve interfaces.
I would have the service that is restricting IPs and accepts a GET for http://S/action as an example. I would have the proxy at http://P/action. The client would send GET http://P/action and P would, in response, send GET http://S/action, collect the response, return it back to the client.
An attempt to implement this strategy, here is a handler I built for P that doesn't work:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
DelegatingHandler handler = new DelegatingHandlerProxy<ProxyHandler>();
config.MessageHandlers.Add(handler);
}
}
DelegatingProxyHandler is a way to get my dependency injection container involved:
public sealed class DelegatingHandlerProxy<THandler> : DelegatingHandler
where THandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
IDependencyScope scope = request.GetDependencyScope();
Task<HttpResponseMessage> task;
if (scope.GetService(typeof(THandler)) is DelegatingHandler handler)
{
if (!ReferenceEquals(handler.InnerHandler, InnerHandler))
{
handler.InnerHandler = InnerHandler;
}
HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
task = invoker.SendAsync(request, cancellationToken);
}
else
{
throw new InvalidOperationException("Handler not registered with DI container");
}
return task;
}
}
The ProxyHandler that I want to do the work is:
public class ProxyHandler: DelegatingHandler
{
public ProxyHandler(
ITransformRequest preProcessor,
ITransformResponse postProcessor,
IForwardRequest forwarder)
{
PreProcessor = preProcessor;
PostProcessor = postProcessor;
Forwarder = forwarder;
}
private ITransformRequest PreProcessor { get; }
private ITransformResponse PostProcessor { get; }
private IForwardRequest Forwarder { get; }
#region Overrides of DelegatingHandler
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (PreProcessor != null)
{
request.RequestUri = PreProcessor.Transform(request.RequestUri);
}
HttpResponseMessage response = await Forwarder.Forward(request, cancellationToken);
HttpResponseMessage transformedResponse = PostProcessor.Transform(response);
return transformedResponse;
}
#endregion
}
In this case, the DI container supplies a PreProcessor that changes host, port, and prefix of the request to the target service. The Forwarder sends the request to the target using HttpClient. The PostProcessor will be a noop.
I didn't build any controllers. My thinking is that if this pipeline behaves as I expect, there won't be any controller that needs invoking. When I send ant request to this, http://P/anything returns a 404, not htto://S/anything. What am I missing?
Any particular reason you're not just writing a set of matching controllers that accept client requests and then execute the equivalent request on the 3rd arty API using a service that implements a simple web client and then returning the responses - perhaps including some authentication & caching logic to lower the impact on their API?
If your 3rd party API provider is limiting requests by IP, that is likely because they trust (or explicitly require) you to manage requests to their API in order to protect it from excessive load and/or security risks. Directly forwarding all client requests without any logic in your middleware means you're negating this limitation.
If the only purpose of your application is to provide a static IP (and you do not need to add any logic in your code) then you should consider using one of the many off the shelf API gateway products - e.g. Kong, which is an open source and very well established with plenty of community support https://konghq.com/kong-community-edition/
We are developing an application with Windows Authentication that is used internally at a company. We have looked at ADFS but at the moment this is not an option. The problem is our test servers are entirely cloud based on Azure. I have been trying to find a way to activate a user but have not found a good solution.
My first idea was to turn off authentication completely. This works good but we have some resources that checks for user roles so I had to abandon that idea.
<system.web>
<authentication mode="None" />
</system.web>
Example method that returns 401 Unauthorized with authentication mode="None", obviously:
[Authorize(Roles = "Administrator")]
[HttpGet]
[Route("TestMethod")]
public IHttpActionResult TestMethod()
{
return Ok("It works!");
}
My second thought was to edit the WebApiConfig and try to add authentication headers in every request server side. However when I started looking at the NTLM Authentication Scheme for HTTP and the 4-way handshake I realized this would probably be impossible.
NTLM Authentication Scheme for HTTP
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Other code for WebAPI registerations here
config.MessageHandlers.Add(new AuthenticationHandler());
}
}
class AuthenticationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Add authentication to every request...
return base.SendAsync(request, cancellationToken);
}
}
Since there is no Owin (Katana) I can not edit the standard App_Start -> Startup.Auth.cs -> public void ConfigureAuth(IAppBuilder app) and try something there. I don't know how I would build up the "user object" anyway.
Is there anything we can do about this or do we have to test everything locally? If we could impersonate one user to be logged in for every request this would be fine in the test environment.
In terms of faking the authentication and authorisation you should be able to set a generic user principal with the appropriate roles using a FilterAttribute.
public class TestIdentityFilter : FilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
filterContext.Principal = new GenericPrincipal(
new GenericIdentity(),
new string [] {"Administrator"});
}
}
You will need to set <authentication mode="None" /> as you did previously otherwise this code will never be hit in your test environment.
Adding this as a Global filter will override any other existing authentication system (for example if you deploy it to an authenticated environment by mistake). Obviously you will need to be very careful about only using this in your test system.
This example is based on MVC, I think there are some very small differences with WebApi but the basic principal applies.
Big thanks to #ste-fu for pointing me in the right direction. Complete code:
public class AppSettingsDynamicRolesAuthorizeAttribute : AuthorizeAttribute
{
public AppSettingsDynamicRolesAuthorizeAttribute(params string[] roleKeys)
{
List<string> roles = new List<string>(roleKeys.Length);
foreach (var roleKey in roleKeys)
{
roles.Add(WebConfigurationManager.AppSettings[roleKey]);
}
this.Roles = string.Join(",", roles);
}
public override void OnAuthorization(HttpActionContext filterContext)
{
if (Convert.ToBoolean(WebConfigurationManager.AppSettings["IsTestEnvironment"]))
{
filterContext.RequestContext.Principal = new GenericPrincipal(
new GenericIdentity("Spoofed-Oscar"),
new string[] { WebConfigurationManager.AppSettings[Role.Administrator] });
}
base.OnAuthorization(filterContext);
}
}
public static class Role
{
public const string Administrator = "Administrator";
public const string OtherRole = "OtherRole";
}
Can then be used like this:
[AppSettingsDynamicRolesAuthorize(Role.Administrator, Role.OtherRole)]
[HttpGet]
[Route("Test")]
public IHttpActionResult Get()
{
var userName = RequestContext.Principal.Identity.Name;
var user = HttpContext.Current.User.Identity;
return Ok("It works!");
}
I've got a web application created with ASP.NET and a windows native client program written in c#.
The windows native program needs to send and fetch data from the ASP.NET web application.
I guess in the web application I'll need a controller for the external calls. And in the client Software I somehow Need to call them.
Is there a way to achieve calls with complex data types (lists of classes) as parameters?
How do I secure the calls from the client? Simple http-logon?
for example I'd like to transfer an instance of this class to or from the ASP.NET web application:
public class Address
{
public String Street {get;set;}
public String City {get;set;}
}
public class CustomerInformation
{
public String No {get;set;}
public String Name {get;set;}
public List<Address> Addresses {get;set;}
}
Of course the Windows client is running somewhere local while the ASP.NET Service is running in the web.
I would add API controller and put some methods there. For instance
// Addresses API
public class AddressController : ApiController
{
private readonly IRepository<Address> _repository;
public AddressController(IRepository<Address> repository)
{
_repository = repository;
}
[BasicAuthorize]
public IList<Address> GetList()
{
return _repository.GetAll();
}
}
// Constomer information API
public class CustomerInformationController : ApiController
{
private readonly IRepository<CustomerInformation> _repository;
public CustomerInformationController(IRepository<CustomerInformation> repository)
{
_repository = repository;
}
[BasicAuthorize]
public IList<CustomerInformation> GetList()
{
return _repository.GetAll();
}
}
To secure those methods you can use Basic authentication. This means that you can add authorization header for each request:
For example how it looks for user "myuser" with password "test"
Authorization: basic bXl1c2VyOnRlc3Q=
// Custom attribute for Basic authentication
public class BasicAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
private readonly string[] _permissionNames;
public BasicAuthorizeAttribute()
{
}
public BasicAuthorizeAttribute(params string[] permissionNames)
{
_permissionNames = permissionNames;
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
// check if user has been already authorized
if (base.IsAuthorized(actionContext))
return true;
var user = AuthenticateUser(actionContext);
// here you can check roles and permissions
return user != null;
}
private IUser AuthenticateUser(HttpActionContext context)
{
var request = context.Request;
AuthenticationHeaderValue authHeader = request.Headers.Authorization;
if (authHeader != null)
{
// RFC 2617 sec 1.2, "scheme" name is case-insensitive
if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) && authHeader.Parameter != null)
return AuthenticateUser(authHeader.Parameter);
}
return null;
}
private IUser AuthenticateUser(string credentials)
{
try
{
// parse values
var encoding = Encoding.GetEncoding("iso-8859-1");
credentials = encoding.GetString(Convert.FromBase64String(credentials));
var credentialsArray = credentials.Split(':');
var username = credentialsArray[0];
var password = credentialsArray[1];
// authentication
var membershipService = new IMembershipService();
return membershipService.ValidateUser(username, password);
}
catch (Exception)
{
// Credentials were not formatted correctly.
return null;
}
}
}
On client side you can use HttpClient to send async request
public async Task<Address[]> GetAddresses() {
var client = new HttpClient {BaseAddress = new Uri(_settingsService.GetHost())};
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var base64 = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "myuser", "test")));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",base64);
HttpResponseMessage response = await client.GetAsync("api/addresses");
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception(response.ReasonPhrase);
string content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Address[]>(content);
}
Is there a way to achieve calls with complex data types (lists of classes) as parameters?
Yes, The server application as ASP.NET or ASP.NET MVC or (preferably) ASP.NET WEB API can provide services with complex data types. In fact there is no limitation in declaring methods.
How do I secure the calls from the client? Simple http-logon?
There are wide ranage of authentication and authorization mechanism in ASP.NET (MVC, WEB API) which give you opportunity to choose one them.
The data transfers between your client and server via XML or JSON.
The "WebClient" class provides everything that you need to make a call from client to server.
More information:
http://www.codeproject.com/Articles/33798/HTTP-GET-with-NET-WebClient
How to post data to specific URL using WebClient in C#
How do I log into a site with WebClient?
I want to allow two types of authentication on my site :
* Forms authentication: The user login using his/her details in the form. The authentication should be made using cookies.
* Bearer: When calling WebAPI's (for mobile), the authentication should be made only by using bearer tokens.
I've relayed on the SPA template and some questions in SO and did successful made it available.
The only problem I'm facing is the ClaimsIdentity: I wish to use custom identity class. However, I'm being able to do so only in forms authentication, not in bearer WebAPI requests.
My custom identity:
public class MyIdentity : ClaimsIdentity, IMyIdentity
{
#region IMyIdentity
private Account _account = null;
public Account Account
{
get
{
if (_account == null)
{
if (this.IsAuthenticated)
{
Guid claimedAccountId = Guid.Parse(this.FindFirst(ClaimTypes.NameIdentifier).Value);
var accountService = ServiceLocator.SharedInstance.GetInstance<IAccountService>();
_account = accountService.Where(
a => a.Id == claimedAccountId
).FirstOrDefault();
}
_account = _account ?? Membership.Account.GuestAccount;
}
return _account;
}
}
#endregion
}
In Global.asax, I've overridden the Application_OnPostAuthenticateRequest method in order to set the custom identity, and it does working good - but only in forms, not in WebAPI.
In addition, I do set in WebApiConfig.cs
config.SuppressDefaultHostAuthentication();
so it does make sense that MyIdentity being nulled and User.Identity resets back to ClaimsIdentity.
So to sum up my question - is there a way to define which Identity class will be used, so I can set MyIdentity instead of ClaimsIdentity?
For Web API, you could try hooking into the OWIN authentication pipeline, and implement your own Authentication Filter, and use it to change the current principal to your own:
public class MyAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter
{
public Task AuthenticateAsync(HttpAuthenticationContext context, System.Threading.CancellationToken cancellationToken)
{
if (context.Principal != null && context.Principal.Identity.IsAuthenticated)
{
CustomPrincipal myPrincipal = new CustomPrincipal();
// Do work to setup custom principal
context.Principal = myPrincipal;
}
return Task.FromResult(0);
}
And register the filter:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new MyAuthenticationFilter());
...