Middleware to Require Authentication for Local IP .Net Core - authentication

In IIS, I have both antonymous and windows authentication enabled. In the code, I want to check if the request IP is a non routable IP, if not, I want the browser to challenge for credentials.
I tried returning a 401 status code, but that doesn't force the browser to challenge. What am I missing? Is there a better way to do this? How could I create a custom attribute, similar to Authorize, to achieve the same behavior (something like LocalOnly) for WebAPI methods?
Here's what I have in the Configure method in Startup.cs:
app.Use(async (context, next) =>
{
var ip = context.Connection.RemoteIpAddress.ToString();
var isInternal = Regex.IsMatch(ip, #"(^127\.)|(^(0)?10\.)|(^172\.(0)?1[6-9]\.)|(^172\.(0)?2[0-9]\.)|(^172\.(0)?3[0-1]\.)|(^169\.254\.)|(^192\.168\.)");
if (!isInternal && !context.User.Identity.IsAuthenticated) {
context.Response.StatusCode = 401;
} else
{
await next();
}
});

Related

self hosted ASP.NET core Web API with Kestrel using HTTPS

I'm attempting to convert an old WCF service to an ASP.NET Core Web API, making use of the CoreWCF package. A key feature of this existing service is that it's being self hosted by an other application and is able to gracefully start & stop by button presses done by the users on a WPF UI.
This service should be accessed via an HTTPS endpoint and this is where I'm struggling. I have been able to succesfully access the API using regular HTTP, but I'm guessing I'm missing something or it's got something to do with my certificates. I've tried both using the default certificate by passing the listeningOption.UseHTTPS() but when I go to the URL, it'll return an error 500. I've also tried a self-signed certificate and passing it in the afformentioned method, but it keeps returning the same error.
The code for configuring everything looks like this:
Configuring the webhost:
private void CreateWebHostBuilder(){
host = WebHost.CreateDefaultBuilder()
.UseKestrel(options =>
{
options.AllowSynchronousIO = true;
options.ListenLocalhost(Startup.PORT_NR,
lOptions => lOptions.UseHttps("{absolute path}", "{password}"));
);
})
.ConfigureLogging(logging => { logging.SetMinimumLevel(LogLevel.Warning); })
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
.UseShutdownTimeout(TimeSpan.FromSeconds(1))
.UseStartup<Startup>()
.Build();
}
Inside the Startup class:
Configuring the IApplicationBuilder:
public void Configure(IApplicationBuilder app){
app.UseServiceModel(builder =>
{
// Add the Echo Service
builder.AddService<EchoService>()
// Add service web endpoint
.AddServiceWebEndpoint<EchoService, IEchoService>(
WEB_API_PATH,behavior => { behavior.HelpEnabled = true;}
);
});
app.UseMiddleware<SwaggerMiddleware>();
app.UseSwaggerUI();
app.UseAuthentication();
app.UseHttpsRedirection();
}
Configuring the services:
public void ConfigureServices(IServiceCollection services){
services.AddServiceModelWebServices()
.AddHostedService<EchoService>()
.AddSingleton(new SwaggerOptions())
.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
services.AddHttpsRedirection(options =>
{
options.HttpsPort = HTTPS_PORT_NR;
});
}
Since this started as a WPF application, I don't have an appsettings.json file to add, and I wouldn't really know what to add in it if this were needed.
So if anyone has any suggestions or tutorial reference, be sure to let me know, any help is welcome.
So I eventually figured out what was missing. I simply needed to add another endpoint, that would listen to https requests like so:
public void Configure(IApplicationBuilder app){
app.UseServiceModel(builder =>
{
// Add the Echo Service
builder.AddService<EchoService>()
// Add service web endpoint
.AddServiceWebEndpoint<EchoService, IEchoService>(
WEB_API_PATH,behavior => { behavior.HelpEnabled = true;}
);
.AddServiceWebEndpoint<EchoService, IEchoService>(new WebHttpBinding
{
Security = new WebHttpSecurity { Mode = WebHttpSecurityMode.Transport },
WEB_API_PATH,behavior => { behavior.HelpEnabled = true;}
);
});
// remainder of code.
}

Custom HTTP response headers on internal IdentityServer4 endpoints

I've been trying to add some custom HTTP response headers to the built-in IdentityServer endpoints like /connect/checksession with global filter approach depicted in the code snippet below (taken from Startup.cs ConfigureServices method):
services.AddMvc(options =>
{
options.EnableEndpointRouting = false;
options.Filters.Add(typeof(SecurityHeadersAttribute));
});
While the headers appear just fine on custom MVC endpoints like /AccountSelect and /Login the internal IdentityServer endpoints seem to ignore those altogether.
I was thinking whether the order of the registration in the startup overrides the global filters. In my case the code below is executed after AddMvc
services.AddIdentityServer(options =>
{
//code omitted for brevity
});
One option is to add your own middleware step in the request pipeline that will execute for every incoming request to your IdentityServer application.
Just add this code in Startup.Configure:
app.Use(async (context, next) =>
{
context.Response.OnStarting(() =>
{
context.Response.Headers.Add("MyMagic", "Header");
return Task.FromResult(0);
});
await next();
});

.NET Core 3 (preview) Web API return UnAuthorized (401) instead of NotFound (404)

Having read through many posts, blogs and this SO thread, this code doesn't do what I expect it to do:
services.AddAuthentication().AddCookie(options =>
{
options.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = context =>
{
context.Response.Clear();
context.Response.StatusCode = 401;
return Task.CompletedTask;
}
};
});
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.Clear();
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
});
An excerpt from an API controller (using the authorize attribute):
[ApiController]
[Route("api/[controller]")]
[Authorize(Roles = "User")]
public class TravelPlanController : BaseController{
...
}
This is part of the startup configuration of a Web API in .NET Core 3.x (preview) and should return a 401 UnAuthorized (which essentially should be UnAuthenticated) but instead returns a 404 NotFound.
The 404 results from the fact that the default .NET Core Authentication Middleware redirects to something like /auth/login and that route is not available (by design; it is an API not a MVC website). So the request is unauthorized, gets redirected by default, and results in a 404 :s
Both the apporaches of OnRedirectToLogin handlers should intercept this default behaviour, which is odd for a RESTfull API, and return a simple 401 UnAuthorized. But they don't, breakpoint isn't hit in debug mode, Postman and an Angular app in Chrome both report a 404.
Did anything change since .NET Core 3.x? Or did the solutions from others never really work.
This did the trick:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityContext>()
.AddDefaultTokenProviders()
.AddRoles<IdentityRole>();
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.Headers["Location"] = context.RedirectUri;
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
Working with ASP.NET Core 3.x (preview) and the default Identity provider the order in which the above declarations are specified makes the difference for the OnRedirectToLogin event to be fired.
I eloborated on this in this SO thread as well.
Try to define the OnRedirectToAccessDenied on the Events as you did for OnRedirectToLogin.

How we can authenticate signalR by using cross domain call?

I am working asp.net apis project where i am using signalR and i want to authenticate with authorize attribute.For this i send jwt token and this token add
into signalR pipeline.First i call to Apis to get token.Then i send this token to get signalR connection,But signalR returns unauthorized response.
The below is my code,
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
// SignalR Auth0 custom configuration.
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerAuthenticationProvider()
{
OnRequestToken = context =>
{
if (context.Request.Path.Value.StartsWith("/signalr"))
{
string bearerToken = context.Request.Query.Get("access_token");
if (bearerToken != null)
{
string[] authorization = new string[] { "bearer " + bearerToken };
context.Request.Headers.Add("Authorization", authorization);
}
}
return null;
}
}
});
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
});
app.UseWebApi(config);
GlobalHost.DependencyResolver.Register(
typeof(SignalRHUB),
() => new SignalRHUB(new UnitOfWork(new DbFactory())));
//GlobalHost.HubPipeline.RequireAuthentication();
//app.MapSignalR();

Auth0 LoopbackJS API access token using 3rd party login

I currently have the loopbackJS api hosted on a domain (e.g. http://backend.com), with third party authentication setup via Auth0. I have a front-end hosted as a SPA on another domain (e.g. http://frontend.com)
loopback-component-passport seems to work fine when the front-end is on the same domain as the API, and it sets the userId and access_token cookies accordingly. However, my front-end in production is on a different domain to the API, for example the API auth link would be something like:
"http://backend.com/auth/auth0?returnTo=" + encodeURIComponent("http://frontend.com")
The backend has used the same auth pattern as in the loopback-passport-example, where a providers.json file specifies the connection details for Auth0 (although I have also tried other social providers such as Facebook).
"auth0-login": {
"provider": "auth0",
"module": "passport-auth0",
"clientID": "AUTH0_CLIENT_ID",
"clientSecret": "AUTH0_CLIENT_SECRET",
"callbackURL": "/auth/auth0/callback",
"authPath": "/auth/auth0",
"callbackPath": "/auth/auth0/callback",
"successRedirect": "/",
"failureRedirect": "/login",
"scope": ["email"],
"failureFlash": true
}
The front-end (http://frontend.com) has a link on the page to redirect to the API authentication:
Login
Clicking on this link redirects to Auth0 properly, and I can login. It then redirects to the specified target (http://backend.com or http://frontend.com, whichever is specified). The returnTo query parameter also seems to work as expected.
Is there a way to capture the access_token just before redirecting back to the front-end, and somehow communicate it (e.g. query parameters, unless that would be too insecure).
After some more investigation, I settled on this method to use for passing the access token and userId from loopbackjs backend, to a separate front-end. This was documented on a github pull-request, using a customCallback of passport-configurator.
Other places that have referenced this are this fork, issue #102, issue #14 and pull request #155.
There are 2 options here, either use a fork of loopback-component-passport (e.g. the one referenced above) as your npm dependency, or provide a customCallback as a passport configuration option as documented.
I wanted a little more control on the format of the URL, so ended up with the customCallback method. In loopback-example-passport, inside /server/server.js there is some basic code for passing providers.json to the passport configurator:
var config = {};
try {
config = require('../providers.json');
} catch (err) {
console.trace(err);
process.exit(1); // fatal
}
passportConfigurator.init();
for (var s in config) {
var c = config[s];
c.session = c.session !== false;
passportConfigurator.configureProvider(s, c);
}
This can be essentially replaced with the documented customCallback code, with the passport variable being assigned by passportConfigurator.init():
var providers = {};
try {
providers = require('../providers.json');
} catch (err) {
console.trace(err);
process.exit(1); // fatal
}
const passport = passportConfigurator.init();
Object.keys(providers).forEach(function(strategy) {
var options = providers[strategy];
options.session = options.session !== false;
var successRedirect = function(req) {
if (!!req && req.session && req.session.returnTo) {
var returnTo = req.session.returnTo;
delete req.session.returnTo;
return returnTo;
}
return options.successRedirect || '';
};
options.customCallback = !options.redirectWithToken
? null
: function (req, res, next) {
var url = require('url');
passport.authenticate(
strategy,
{session: false},
function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.redirect(options.failureRedirect);
}
var redirect = url.parse(successRedirect(req), true);
delete redirect.search;
redirect.query = {
'access_token': info.accessToken.id,
'userId': user.id.toString()
};
redirect = url.format(redirect);
return res.redirect(redirect);
}
)(req, res, next);
};
passportConfigurator.configureProvider(strategy, options);
});
In the above example, I have essentially copied the successRedirect function used in passport-configurator.js, to use the same returnTo query parameter. An option within providers.json can be set e.g. "redirectWithToken": true, which results in redirect only for the auth strategies that need external redirect.
One more final bit of code in case the returnTo redirect is required. If it exists as a query parameter, it should be added at a session level:
app.use(function(req, res, next) {
var returnTo = req.query.returnTo;
if (returnTo) {
req.session = req.session || {};
req.session.returnTo = require('querystring').unescape(returnTo);
}
next();
});
Now, if the backend api is at a URL such as http://api.com, and the front-end is hosted at another domain e.g. http://gui.com, an authentication link can be placed on the front-end:
Login!
This will result in an API auth call, then redirect back to the returnTo link with the access token and userId in the query parameters.
Potentially in the future, one of the issues or other pull requests will be merged that could provide a more ideal method for 3rd party domain redirection, but until then this method work well.