Content Security Policy error when using express.static to show file after authentication - express

I'm trying to use nginx as a proxy server and use express+passport to authenticate user before showing private/static file(s).
I'm only working with http now (during the development stage). I found Express + Nginx. Can't serve static files and learned quite some from it but my code is not working.
My nginx settings:
http {
server {
listen 80;
root /var/www/html;
location /private {
proxy_pass http://myIP4:3000/private; #3000 is the port for express server
proxy_method GET;
}
}
}
My express(passport) code is like:
...
...
#simplified login, real code is longer
app.use('/login', passport.authenticated('local'),function(req, res){
res.redirect('/private/index.html'); #if authentication is OK
});
app.use(function(req,res,next){
if ((req.url !== '/login') && (!req.isAuthenticated()) ){ #not the login page and not authentication failed
res.redirect(301,'http://myIP4/login.html');
}
else {#if authenticated
console.log('authentication OK');
express.static("/var/www/html/private/");
}
});
My login API works fine, after I submit the username/password, I could see login successfully. But the redirect has some issues: the browser could not show the destination file: /private/index.html (after login) and /private/test.html (if I type the full url directly in my browser after login successfully).
Browser shows:
Cannot GET /private/index.html
Debug of Browser shows:
Content Security Policy: The page’s settings blocked the loading of a resource at http://myIP4/favicon.ico (“default-src”).
I found some posts about setting Content_Secrity_policy but I could not make it working after some try out.
Thanks for your time help!

I solved this by changing in the express:
From:
express.static("/var/www/html/private/");
To:
app.use ('/private',express.static('/var/www/html/private'));
And it works now.

Related

Google Chrome Browser Block ".AspNetCore.Identity.Application" Cookie In Deployment Only

After deploy asp.net core mvc 6 to iis server the user in case submit a true login not redirected to the controller and not authenticated, when I tracked the cookie in developer tools there is a ! mark on .AspNetCore.Identity.Application and when hover on it there is a message show (This cookie was blocked because it had the "Secure" attribute and the connection was not secure).
What I tried Before
Changing the cookie configuration
Exchange return LocalRedirect(returnUrl) To return RedirectionToAction("Index","Home")
Add [AllowAnonymous] attribute on LoginModel in Areas.Identity.Pages.Account
Remove Use.HttpsRedirection(); from program.cs
Because there are many apps host in the server so the browser blocked the cookie cause the other apps have the same cookie name which .AspNetCore.Identity.Application. So simply the issue can be solved by changing the name of the cookie:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
});
builder.Services.ConfigureApplicationCookie(options => options.Cookie.Name = "AppNameForExample");
Your cookies are configured to require an HTTPS connection. When you try to set them on a non-secure connection, they will be rejected. You can try these steps:
Obtain a valid SSL certificate: You'll need a certificate that is trusted by the browsers you're targeting. Check your web.config file settings for:
<httpCookies requireSSL="true" />
Configure IIS to use HTTPS: This involves binding the SSL certificate to the IIS website and enabling HTTPS.
Update your ASP.NET Core application to use HTTPS: In the Startup.cs file, you can use the following code to redirect all HTTP traffic to HTTPS:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Rewrite;
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var options = new RewriteOptions().AddRedirectToHttps();
app.UseRewriter(options);
// rest of the code...
}

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\"}");
}

URL Rewrite exceptions for Blazor WebAssembly Hosted deployment

During development, i have used Swagger on the server side of my Blazor WebAssembly App. Always launching (debug) using kestrel instead of IIS Express.
Routing worked as expected, all my component routed properly and if i manually typed /swagger, i got to the swagger page. All good.
We have deployed under IIS on our pre-prod servers, the Server side and Blazor WebAssembly App (client) work as expected and are usable, however, my /swagger url gets rewritten (I assume) to go somewhere in my App instead of letting it go to Swagger, obviously there isn't any component that answers to /swagger.
My only guess is that, when hosted on IIS, the aspnet core app takes care of telling IIS what to rewrite and how (similar to the configs that could be provided thru a web.config for a "Standalone" deployment.)
I can't find how to specify exceptions, I've been following the doc at
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/blazor/webassembly?view=aspnetcore-3.1#iis
Any idea how i could add an exception for /swagger ?
EDIT:
Turns out it works without issues in Chrome, only Firefox has the unwanted behavior. If i clear my cache, or use Incognito mode, the issue does not happen in Firefox. So, it seems that Firefox caches some stuff and tries to send my URL input to the Blazor Wasm instead of going thru to the server. I will debug some more with the dev tools and fiddler open to try and figure it out, will report back.
Turns out there this is part of the service-worker.js file that is published. It is different in dev than what gets published (which makes sense).
During my debugging i was able to reproduce the issue on all browsers (Edge, Chrome and Firefox), regardless of being in Incognito/Private mode or not.
Once the service-worker is running, it handles serving requests from cache/index.html of the Blazor WebAssembly app.
If you go into your Blazor WebAssembly Client "wwwroot" folder, you'll find a service-worker.js and a service-worker.published.js. In the service-worker.published.js, you will find a function that looks like this :
async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
const shouldServeIndexHtml = event.request.mode === 'navigate'
&& !event.request.url.includes('/connect/')
&& !event.request.url.includes('/Identity/');
const request = shouldServeIndexHtml ? 'index.html' : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}
return cachedResponse || fetch(event.request);
}
Simply following the instructions found in the code comments is gonna fix the issue. So we ended up adding an exclusion for "/swagger" like so :
&& !event.request.url.includes('/swagger')
Hopefully this post is useful for people who are gonna want to serve things outside of the service worker, not only Swagger.
Do you have UseSwagger first in your Startup.Configure method?
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json", "YourAppName V1")
);
In Startup.ConfigureServices I have the Swagger code last.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSwaggerGen(c =>
c.SwaggerDoc(
name: "v1",
info: new OpenApiInfo
{
Title = "YourAppName",
Version = "V1",
}));
}
This is working just fine for us.
Note: You must navigate to https://yourdomain/swagger/index.html

How to handle redirects to auth provider from the backend in Fable Elmish SPA

I have an AspNetCore backend api (in F# with Giraffe) that uses AzureAD authentication with Microsoft.AspNetCore.Authentication.AzureAD.UI, with stateful session store, and https only cookies.
The frontend is an Elmish SPA compiled to js with Fable.
If I just type into the url bar a protected endpoint of my backend, everything works correctly, if not already signed in, I get redirected to the login.microsoft endpoint, with the clientID and so on, where upon successful signin, the original request completes and I get the response of my protected endpoint.
But if I try to access the same endpoint from the SPA code, eg.: with fetch, or with Fable.Remoting, if not logged in, the backend still redirects but the redirected request to login.microsoft no longer works.
With Fable.Remoting there is a CORS header, that the login endpoint refuses. If I send fetch with nocors, there is a 200 OK response from the login endpoint BUT no response body (eg no html code for the login page) and seemingly nothing happens.
I just have no idea how this should be handled on the SPA side, and could not really find anything about it. Why does the backend include a CORS header in the redirect if initiated from Fable.Remoting vs if initiated from the browser url bar? What is wrong with the fetch-ed response that there is no response body?
I can write just js code into my client, but could not even figure out how would this be handled in a pure js SPA.
Also tried the whole thing in production, to remove the webpack devServer proxy from the equation, but everything stays the same.
First, create "signin" and "signout" routes in Giraffe:
/// Signs a user in via Azure
GET >=> routeCi "/signin"
>=> (fun (next: HttpFunc) (ctx: HttpContext) ->
if ctx.User.Identity.IsAuthenticated
then redirectTo false "/" next ctx
else challenge AzureADDefaults.AuthenticationScheme next ctx
)
/// Signs a user out of Azure
GET >=> routeCi "/signout"
>=> signOut AzureADDefaults.AuthenticationScheme
>=> text "You are signed out."
Next, you need to configure the webpack "devServerProxy". This is how my current Fable app is configured:
// When using webpack-dev-server, you may need to redirect some calls
// to a external API server. See https://webpack.js.org/configuration/dev-server/#devserver-proxy
devServerProxy: {
// delegate the requests prefixed with /api/
'/api/*': {
target: "http://localhost:59641",
changeOrigin: true
},
// delegate the requests prefixed with /signin/
'/signin/*': {
target: "http://localhost:59641",
changeOrigin: true
},
// delegate the requests prefixed with /signout/
'/signout/*': {
target: "http://localhost:59641",
changeOrigin: true
}
},
This will allow you to provide a sign-in link from your SPA:
a [Href "/signin"] [str "Sign in"]
Now when the user loads your app, you can immediately try to pull back some user info (this route should require authentication). If the request (or any other) fails with a 401, you can prompt the user to "Sign in" with your sign-in link.
Lastly, your Azure app registration for your dev environment should point back to the port of your Web Api (which it sounds like yours already does).

Redirect HTTP to HTTPS in MVC4 Mobile Application

In My MVC4 Mobile application i have registration, login page and remaining pages. i would like to redirect user to HTTPS connection for all sensitive information pages like registration and login pages and HTTP to remailing pages.
I prefer you to use conditional functionality putting the class
public class RequireHttpsConditional : RequireHttpsAttribute
{
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
var useSslConfig = ConfigurationManager.AppSettings["UseSSL"];
if (useSslConfig != null)
{
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The requested resource can only be accessed via SSL.");
}
var request = filterContext.HttpContext.Request;
string url = null;
int sslPort;
if (Int32.TryParse(useSslConfig, out sslPort) && sslPort > 0)
{
url = "https://" + request.Url.Host + request.RawUrl;
if (sslPort != 443)
{
var builder = new UriBuilder(url) { Port = sslPort };
url = builder.Uri.ToString();
}
}
if (sslPort != request.Url.Port)
{
filterContext.Result = new RedirectResult(url);
}
}
}
}
and using this [RequireHttpsConditional] above the action result.
i have got this code somewhere in internet and is working fine for me.
in web.config appsettings use <add key="UseSSL" value="443" />
and in the controller above the action result you need put
[RequireHttpsConditional]
public ActionResult SignIn()
{
}
In IIS where you have your project right click and click "Edit Bindings" then you add a custom type https and port no 443 (you can change it)
Note this will work only in production environment. when executed locally it wont be working.
When you execute it locally you have request.Url.Host which will return you only localhost and missing your port number. so if you use it in MVC you will find error loading page for your pages where you put this code.
So this will work when you have the host assigned instead of using the localhost with a specific port number.
Within the controller actions that you wish to be HTTPS add the following code to the top of the method (of course you can simply add this to its own method and then call it):
if (!HttpContext.Request.IsSecureConnection)
{
var url = new UriBuilder(HttpContext.Request.Url);
url.Scheme = "https";
Response.Redirect(url.Uri.AbsoluteUri);
}
It is recommended though that you keep HTTPS on throughout your site to protect against a MITM attack against the auth cookie.