Test For Localhost On ASP.NET Web API ActionFilterAttribute - ssl

How can I test for localhost on an ActionFilterAttribute with ASP.NET Web API? I want to skip the SSL check.
public class RequireHttpsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var request = actionContext.Request;
if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
throw new ValidationException(new SecureConnection());
}
base.OnActionExecuting(actionContext);
}
}

If you just want to test whether the request URI is localhost, then IsLoopback on the URI should work fine.
But here's the thing... request URIs can easily be spoofed by computers that aren't the local computer. So any remote computer can actually send you a request with a localhost Host header.
A better way is to use Filip's suggestion in his blog post:
http://www.strathweb.com/2013/01/adding-request-islocal-to-asp-net-web-api/
That should work for both selfhost and webhost and whether the IP address of the client is a loopback.

Looks like this works. Let me know if I am incorrect.
request.RequestUri.IsLoopback

Related

Nginx reverse proxy - 405 POST

I have Asp.Net Core Web Api application, which uses "x-api-key" http header to authorize a person sending a request. I've setup action filter as
public void OnActionExecuting(ActionExecutingContext context)
{
// Retrieve record with specified api key
var api = dbContext.Apis
.Include(a => a.User)
.FirstOrDefault(a => a.Key.Equals(context.HttpContext.Request.Headers["x-api-key"]));
// Check if record exists
if (api is null)
{
context.Result = new UnauthorizedResult(); // short circuit and return 401
}
}
It is working as expected on both GET and POST requests without nginx proxy, however as soon as I add nginx, I receive 405 Not Allowed on POST request if api key is invalid but 401 on GET (if api key is valid filter works as expected and passes execution to controller). Here is my proxy configuration
server {
listen 80;
location / {
proxy_pass http://ctoxweb:5000;
}
}
(Both nginx and web api are setup using docker). What's the problem and how to fix that?
I managed to fix this problem, however I don't know exactly why this happens, I suppose it's somehow related to nginx not allowing any method (except for GET) on static content. My best guess is that nginx assumes that empty response body (which comes from new UnauthorizedResult()) is static, though it's clearly supplied by backend. The way to fix it is as easy as supply some object to response body, for example
if (api is null)
{
context.HttpContext.Response.ContentType = "application/json";
context.Result = new UnautorizedObjectResult("{\"info\":\"no api key header present\"}");
}

Block server-to-server or Postman calls to ASP.NET Core 3.1 API

I have a ASP.NET Core 3.1 API where I have not used CORS. As I understand, CORS is a browser thing. And as my ajax calls from another site on another origin is blocked to the API endpoints (which is great), I can still reach the same endpoints by using Postman or a HttpClient and GetAsync() calls.
My question is of it's possible to also block server-to-server calls (or Postman calls) to my API? Or like CORS, only allow certain origins?
Most of my endpoints are protected by a bearer JWT token, but I have an anonymous endpoint that I would like to let only origins I control (or can configure) to have access to that anonymous API.
I solved it after i bumped in to this post on stackoverflow:
How do you create a custom AuthorizeAttribute in ASP.NET Core?
I simply made a custom Authorize attribute [ApiAuthorize()], that I call this way:
[ApiController]
[ApiAuthorize(new string[] { "https://localhost:44351", "https://mysite.onthe.net" })]
public class MyInternalApiController : ControllerBase
{
...
}
It may also be implemented on the Action instead of the Controller. The implementation was done like this:
public class ApiAuthorizeAttribute : TypeFilterAttribute
{
public ApiAuthorizeAttribute(string[] origins) : base(typeof(ApiAuthorizeFilter))
{
Arguments = new object[] { origins };
}
}
public class ApiAuthorizeFilter : IAuthorizationFilter
{
readonly string[] _origins;
public ApiAuthorizeFilter(string[] origins)
{
_origins = origins;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (_origins == null)
return;
string referer = context.HttpContext.Request.Headers["Referer"].ToString();
if (string.IsNullOrWhiteSpace(referer) || !_origins.Any(origin => referer.StartsWith(origin, StringComparison.OrdinalIgnoreCase)))
context.Result = new ForbidResult();
}
}
Things to consider:
The implementation and check of the referer could be exact match instead of StartsWith
The handling could use RegEx or any good alternative to handle subdomains, wildcards etc
The referer could be translated to a Uri objects to get better results and variations
A jQuery ajax call gets a "403 - Forbidden" as expected, but Postman gets a "404 - Not Found". To me that does not matter, but that's something to look into if it matters.
But it covers what I need, so I'm happy with this.

I am receiving status 401 Unauthorized when I attempt to call my secured API by keycloak

I have an web application providing an REST API endpoints, secured with spring security and SSO Keycloak.
My web application works fine with protection on and I can access REST API endpoints using web browser, after authentication on keycloak and redirect back to endpoint. I am getting expected JSON response, which mean user and role should be configured correctly.
But when i want to call a provided REST API endpoint from another web application or using Postman, i have every time an error 401 error: unauthorized.
In the header of the request I am putting:
"Authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgO...." token.
I am able to fetch this token from keycloak in consumer web application and also when I am making a request using postman.
My Keycloak configuration in my spring security config:
#KeycloakConfiguration
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/api/v1/**").hasRole("USER")
.antMatchers("/admin/**", "/app/**").hasRole("ADMIN")
.anyRequest().permitAll();
}
}
My keycloak.json file:
{
"realm": "realm-name",
"auth-server-url": "https://auth.server.com/auth/",
"ssl-required": "external",
"resource": "resource-name",
"verify-token-audience": true,
"credentials": {
"secret": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"use-resource-role-mappings": true,
"confidential-port": 0
}
My method in the controller of the api:
#RestController
#RequestMapping("/api/v1")
public class WakeMeUpController {
#RequestMapping(value = "/test", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE)
public String testMeUp() {
return "I am tested!";
}
}
This is a part of my client configuration in keycloak:
keycloak Client screen
Rest keycloak config is how it is coming by default.
Thx for your help!
Thank you. The problem was into audience configuration in keycloak.
For a client, you need to configure new Mapping under Client -> <client_name> -> Mappers and add another Mapping with mapper type 'Audience' and appropriate 'Included Client audience' (i choose client id i have used to make request)
Also another problem that i have faced after i solve this one, was that user was authenticated but was forbidden the access to endpoint.
Explicitly adding role into client configuration itself and assigning this role to the user solve it and it is working.
From huge help was setting spring logging to DEBUG
logging.level.org.springframework.security=DEBUG
And also XXX-Authentication header in server response that gives better description what the issue is.

How to get base url without accessing a request

How to get the base URL in AspNet core application without having a request?
I know from the Request you can get the scheme and host (ie $"{Request.Scheme}://{Request.Host}" would give something like https://localhost:5000), but is it possible to get this information from anywhere else?
In other words, if I have a service class that needs to build absolute URLs, how can I get the current URL when there is not an http request available?
UPDATE: Maybe that scenario does not even make sense since the hosting URL is totally external to the application and that's why it only makes sense to extract it from the Request host..
i needed for some reason to get the base URL in Start.cs Configure, so i come up with this
var URLS = app.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
You are right, hosting URL is an external information, and you can simply pass it as configuration parameter to your application.
Maybe this will help you somehow: without request, you can get a configured listening address (like http://+:5000) using the IWebHostBuilder interface. It provides access to host settings via the GetSetting method:
/// <summary>
/// Get the setting value from the configuration.
/// </summary>
/// <param name="key">The key of the setting to look up.</param>
/// <returns>The value the setting currently contains.</returns>
string GetSetting(string key);
There is a WebHostDefaults.ServerUrlsKey setting name, that allows to configure listening address. We override it when add .UseUrls extension method:
public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls);
or define urls configuration parameter as described in the documentation (you know, by default listening is configured to localhost:5000).
So, having instance of IWebHostBuilder, you can call .GetSetting(WebHostDefaults.ServerUrlsKey) and get the current value.
,The ASP.NET Core Module generates a dynamic port to assign to the backend process. CreateDefaultBuilder calls the UseIISIntegration method. UseIISIntegration configures Kestrel to listen on the dynamic port at the localhost IP address (127.0.0.1). If the dynamic port is 1234, Kestrel listens at 127.0.0.1:1234. This configuration replaces other URL configurations provided by.
For IIS Integration, it works if you get the address after the WebHostBuilder.Build() have run.
var builder = CreateWebHostBuilder(args);
var webHost = builder.Build();
var addresses = webHost.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
var address = addresses.FirstOrDefault();
AppDomain.CurrentDomain.SetData("BaseUrl", address ?? "");
webHost.Run();
and got the local Kestrel address in the HostedService like this:
string baseUrl = AppDomain.CurrentDomain.GetData("BaseUrl").ToString();
But there's a catch - this address is useless, because you can not make a request directly on this address. The IIS Integration middleware checks that only the IIS handler can make a request on this address. It produces a similar error:
<category>Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware</category>
<state>'MS-ASPNETCORE-TOKEN' does not match the expected pairing token 'ed5bc610-b7b9-4c1c-9941-954d0579edfc', request rejected.</state>
And in general case (no IIS Integration) this method of getting the address does not work if you use Kestrel configured to run with a custom port (not 5000), or a dynamic port 0. In this case the address needs to be obtained in a delayed manner, only after the application started.
For this case i tried this way: In Configure method in the StartUp class, i saved in ServerAddressFeature in the private member.
private IServerAddressesFeature _serverAddressesFeature;
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
_serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
... not related code here ...
And in the ConfigureServices method i added a dependency
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServerAddressesFeature>((sp) => _serverAddressesFeature);
... not related code here ...
Then in a hosted service i obtain this saved feature using dependency injection, and use it to get the address.
It works, only get the address in the StartAsync method, not in the service constructor!
public class WarmUpService : IHostedService
{
private readonly ILogger _logger;
private readonly IServerAddressesFeature _saf;
public WarmUpService(ILogger<WarmUpService> logger, IServerAddressesFeature serverAddressesFeature)
{
_logger = logger;
_saf = serverAddressesFeature;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
// the URL can be Got here
string baseUrl = _saf?.Addresses?.FirstOrDefault();
// await _WarmUp(baseUrl);
}
catch(Exception ex)
{
_logger.LogCritical(ex, "WarmUp Failed");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

Getting IP Address of the remote peer in Websocket API for Java EE 7

How can I get the IP address of the remote peer in Websocket API for Java Glassfish ?
Another method, based on this answer to another question, is to get the headers of the HandshakeRequest. The headers you are looking for are either of the following.
origin: [IP Address]
x-forwarded-for: [Possibly a separate IP]
Just for clarity, here's my setup, and how I discovered this:
Wamp 2.5 on MyMachine:6060. This hosts a client HTML page.
Wamp 2.5 on LabMachine:6060 (normal connections) and LabMachine:6443 (secure connections). This acts as a proxy.
GlassFish 4.0 on MyMachine:8080 (normal) and MyMachine:8181 (SSL). This is the endpoint.
I connected to the client page via my own machine, the lab machine, and a colleague's machine. In every case, the origin header of the WebSocket request was
http://MyMachine:6060
However, in each case the x-forwarded-host header was different, matching the IP addresses of the actual client.
See getRemoteAddr()
You will need to cast your socket Session instance to a TyrusSession, which implements the standard JSR-356 Session interface.
You might need to upgrade Tyrus, as I am not sure if the version you have supports this method. I am using Tyrus 1.7 and it works fine for me. Here is how to upgrade.
WebSockets are based on HTTP requests. Therefore you are probably extending an HttpServlet or a WebSocketServlet somewhere, so the usual way of getting the IP from the HttpServletRequest should work:
Example:
public class WebSocketsServlet extends HttpServlet {
private final WebSocketApplication app = new WebSocketApplication();
#Override
public void init(ServletConfig config) throws ServletException {
WebSocketEngine.getEngine().register(
config.getServletContext().getContextPath() + "/context", app);
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("IP: " + req.getRemoteAddr());
super.doGet(req, resp);
}
}