How to enable content security policy for identity server? - asp.net

I have a micro front-end which is connected to my identity server. In order for a user to access the micro front-end, the user needs to be authenticated via this ID server. The micro front-end needs to be embedded in an iframe in my main application but since this micro front-end requires the ID server, I am getting an error on the console. Everything works fine before embedding this micro front-end but I get this problem as soon it gets into the iframe.
Refused to frame 'https://localhost:44300/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'none'".
I tried including this in my identity server's web.config file which resulted to the above error on the console.
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Content-Security-Policy" value="frame-ancestors ''" />
</customHeaders>
</httpProtocol>
</system.webServer>
It seems ID server does not allow its content to be embedded in an iframe. This perhaps might not be a good practice but per my requirements, i will like to know how to enable this.
UPDATE
Here is where the csp is defined
public override void OnResultExecuting(ResultExecutingContext context)
{
var result = context.Result;
if (result is ViewResult)
{
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options"))
{
context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff");
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options"))
{
context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';";
// also consider adding upgrade-insecure-requests once you have HTTPS in place for production
//csp += "upgrade-insecure-requests;";
// also an example if you need client images to be displayed from twitter
// csp += "img-src 'self' https://pbs.twimg.com;";
// once for standards compliant browsers
if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy"))
{
context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp);
}
// and once again for IE
if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy"))
{
context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp);
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
var referrer_policy = "no-referrer";
if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy"))
{
context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy);
}
}
}

It is really a bad idea to try to iframe IdentityServer, because then its hard for the user to know where he is actually logging in to.
If you still want to change it you need to look at the SecurityHeadersAttribute.cs file in the QuickStart folder, that one defines the CSP.
In this file you can tweak the security headers that are sent to the browser. You need to look at the CSP and the X-Frame-Options header.

Related

Why isn't stripping x-frame options allowing me to load a cross site iframe in this extension?

Context
I'm building a chrome extension that allows users to run automated scripts on 3rd party sites from anywhere on the web. The extension needs the ability to dynamically insert an iframe on any page that the user in on where that iframe is loading a 3rd party site.
The Problem
When I try to load linkedin.com in an iframe from google.com I get the linkedin.com refused to connect. If I look I can see that the x-frame options are still present in the headers while I have confirmed that I've stripped them out in them out.
I've added the following to my extension background script to allow iframes to load in any site
chrome.webRequest.onHeadersReceived.addListener(function (details) {
const headers = details.responseHeaders.filter(header => {
let headerName = header.name.toLowerCase();
return !(headerName === 'content-security-policy' || headerName === 'x-frame-options');
})
if (details.url.includes('linkedin.com')) {
// this console log shows that I've stripped out the necessary headers correctly
console.debug('REMOVED HEADERS: ', headers);
}
return {
responseHeaders: headers
};
}, {
urls: ['<all_urls>']
}, ['blocking', 'responseHeaders']);
I'm using the following code in the console on google.com to insert an iframe loading linkedin.com
(function () {
var iframe = document.createElement('iframe');
iframe.style.position = 'absolute';
iframe.style.zIndex = 100000;
iframe.style.top = 0;
iframe.style.left = 0;
iframe.height = 600;
iframe.width = 900;
iframe.referrerPolicy = 'no-referrer-when-downgrade';
iframe.src = 'https://www.linkedin.com';
document.body.appendChild(iframe);
})();
Here you can see the console log showing the modified headers with x-frame and CSP removed from the iframe request headers
but then the iframe doesn't load. it returns 200 but nothing happens
This actually was working and the Brave shield was preventing the iframe from loading in the page.

How to embed youtube channel page using iframe in my extension?

I'm creating extension to organize youtube channels using tags. It has angular frontend with url like this
moz-extension://f78b3bd9-a210-41c5-9d8d-9b7ab3717f6e/index.html#/channel/UCtinbF-Q-fVthA0qrFQTgXQ
And I want to embed channel's page using iframe, but security policies doesn't allow me to do that.
Load denied by X-Frame-Options: https://www.youtube.com/ does not permit cross-origin framing.
So I tried to modify X-Frame-Options, but it doesn't change anything(headers aren't added).
What I did:
1 Added permissions to manifest.json:
"webRequest",
"://.youtube.com/",
"://www.youtube.com/*"
2 Wrote some code in background.js
function addFramePermissions(e) {
console.log("Loading url: " + e.url);
var allowedHeaders = [];
for (var header of e.responseHeaders) {
if (header.name.toLowerCase() !== "x-frame-options") {
allowedHeaders.push(header);
} else {
console.log('x-frame-options found!!!');
}
}
e.responseHeaders = allowedHeaders;
return { responseHeaders: e.responseHeaders };
}
browser.webRequest.onHeadersReceived.addListener(
addFramePermissions,
{
urls: [
"*://*.youtube.com/*",
"*://youtube.com/*"
]
},
["blocking", "responseHeaders"]
);
Code reaches function and I can see "x-frame-options found!!!" in console, but firefox's Network Monitor shows that x-frame-options exists with value SAMEORIGIN
I ran my extension in Chrome and Chrome said that I forgot to add "webRequestBlocking" in permissions. Thanks, Chrome!

Json get not working through azure but working locally

I have created a asp.net webapi and hosted it through azure.
This works fine when I run host/api/carparks. It also works when I run an ODATA query string
host/api/carparks?$Filter%20eq%20%27Liverpool%27
Google chrome returns the results as JSON as I want them.
The problem I am having is, I need to create a "Client" application to visualize my data. I have created a really simple for loop to return my data for testing purposes, once I have data returned I can start creating my application.
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
<script type="text/javascript">
function getStations() {
var town = document.getElementById("town").value;
var stationList = "<p>";
var uri = "http://localhost:38852/api/carparks?$filter=Town%20eq%20%27" + town + "%27";
$.getJSON(uri,
function (data) {
$('#here_data').empty(); // Clear existing text.
// Loop through the list of products.
$.each(data, function (key, val) {
stationList += val.Name + '<br />';
});
stationList += "</p>";
document.getElementById("here_data").innerHTML = stationList;
});
}
$(document).ready(getStations);
</script>
</head>
<body onload="getStations()">
<h1>Stations API</h1>
<p>Enter town</p>
<input type="text" id="town" value="Derby" />
<input type="button" value="Find Stations" onclick="getStations()" />
<div id="here_data">
<p>Car parks go here</p>
</div>
</body>
</html>
My client app works perfectly when I run my web api locally but when I change the getJSON request URI to my azure one (Which works in the browser!) nothing happens.
I have tried uploading my client app to azure and testing it that way but nothing :(
Is there any Azure settings that need to be changed?
Looks very much like a cross-origin issue.
The issue does not occur when you call the Service directly in your browser but only when you issue an Ajax call from a different domain (localhost vs. *.azurewebsites.net).
If you want to access your Web Api service with an Ajax call from a different domain you need to enable Cross Origin Resource Sharing (CORS).
A detailed article is found here:
http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api
Quoted from the link:
Install-Package Microsoft.AspNet.WebApi.Cors
Open the file App_Start/WebApiConfig.cs. Add the following code to the
WebApiConfig.Register method.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// New code
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Next, add the [EnableCors] attribute to the TestController class:
using System.Net.Http; using System.Web.Http; using
System.Web.Http.Cors;
namespace WebService.Controllers
{
[EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
public class TestController : ApiController
{
// Controller methods not shown...
}
}
For the origins parameter, use the URI where you deployed the
WebClient application. This allows cross-origin requests from
WebClient, while still disallowing all other cross-domain requests.
Later, I’ll describe the parameters for [EnableCors] in more detail.
Do not include a forward slash at the end of the origins URL.
Thanks to #viperguynaz and #florian I have fixed my issue. I changed the CORS option in Azure portal. (When I first did it I didn't remove the forward slash at the end of the URL). I removed the slash and it works.
I have also used the info given by #florian to help me understand CORS more.
Thanks again
1 happy joe :)

Web API + CORS + Basic NTLM: 401 with Firefox + Chrome

So I've been trolling every post I could find on this topic and still haven't figured it out. Hopefully you can help:
Web API 2 with CORS enabled:
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
web.config:
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
<authentication mode="Windows" />
</system.web>
jQuery
<script>
$(document).ready(function()
{
$("button").click(function()
{
var endpoint = "http://localhost:82/api/test";
var base64Credentials = window.btoa("domain\credentials:password");
$.ajax({
url: endpoint,
beforeSend: function(xhr) {
xhr.withCredentials = true;
xhr.setRequestHeader("Authorization", "Basic " + base64Credentials);
},
success: function(result) {
alert(result);
}
});
});
});
</script>
IIS has Basic Authentication enabled.
Basic Auth works in IE. Firefox and Chrome is subject to the preflight OPTIONS call.
Using Fiddler, I can see that if I set IIS to allow both Anonymous and Basic Authentication then the OPTIONS call will result in a 200 and then the subsequent GET with Basic Auth kicks in and all is right in the world. If I turn off Anonymous, then I get 401 on the OPTIONS call.
Question:
Is it a requirement to allow both Anonymous and Basic authentication to support this use case?
I've partially solved this on one of my projects. It always works in IE, but in Chrome I still get random 401 replies on requests but when I examine the response web api is sending the JSON data back so it gets past the OPTIONS.
The IIS site config for Web API is:
Windows Auth, anon disabled;
And under Windows Auth providers (option on the right), move NTLM to the top
In your web.config:
<httpProtocol>
<customHeaders>
<!-- Allows auth in CORS AJAX requests -->
<add name="Access-Control-Allow-Credentials" value="true" />
<!-- May only ever be one value - a domain or "*". "*" is disallowed when Access-Control-Allow-Credentials is true -->
<add name="Access-Control-Allow-Origin" value="http://localhost:63415" />
<add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Headers, Access-Control-Request-Method" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, HEAD, OPTIONS" />
</customHeaders>
</httpProtocol>
and in Global.asax.cs
protected void Application_BeginRequest()
{
// When Chrome sends the OPTIONS it breaks on windows auth, so this fixes it.
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
{
Response.StatusCode = (int)HttpStatusCode.OK;
Response.Flush();
}
}
All of that is without using the CORS NuGet package. I tried that but I still had the OPTIONS problems.
EDIT: An update on this, it all seems to be working perfectly well for me in Chrome now.

Access ASP.NET authentication ticket on Client (via javascript)

I have an ASP.NET website that uses Forms authentication
<authentication mode="Forms">
<forms name="NewsCoreAuthentication" loginUrl="~/Default.aspx" defaultUrl="~/Default.aspx" protection="Validation" timeout="300" domain="someRootDomain.com" />
</authentication>
I need to identify if user is authenticated on web page after it was rendered to client.
To accomplish this I thought that I can read document.cookie and check if ".ASPXAUTH" is there.
But the problem is that even if I am signed in this value is empty.
How can I check that user is authenticated?
Why document.cookie is empty?
Thank you for answers. blowdart helped me to understand why authentication ticket is not accessible from client script.
The reason it's blank is because the cookie is protected by being marked as HttpOnly. This means it cannot be accessed via script. Turning this off is a very very bad idea, as XSS vulnerabilities in your site could expose it to cookie theft, so I'm not going to tell you how you can do it.
As others have said, the auth ticket is and SHOULD be httponly.
The best way to do this is to use ApplicationServices. The JSON authentication endpoint exposes IsLoggedIn and I have noticed your concern regarding server load. The overhead of a call to a static endpoint that simply checks the cookie for you is negligible. Really.
So, If you are using MsAjax, just enable application services and call Sys.Services.AuthenticationService.IsLoggedIn.
If you want to do this from raw javascript here is the codez ;-)
Add this segment to you config file
<system.web>
------------
</system.web>
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled ="true" requireSSL="false"/>
</webServices>
</scripting>
</system.web.extensions>
The page....
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
function createXHR() {
// a memoizing XMLHttpRequest factory.
var xhr;
var factories = [
function() { return new XMLHttpRequest(); },
function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
function() { return new ActiveXObject("Msxml3.XMLHTTP"); },
function() { return new ActiveXObject("Microsoft.XMLHTTP"); } ];
for (var i = 0; i < factories.length; i++) {
try {
xhr = factories[i]();
// memoize the factory so we don't have to look for it again.
createXHR = factories[i];
return xhr;
} catch (e) { }
}
}
function isLoggedIn() {
var xhr = createXHR();
xhr.open("POST", "/Authentication_JSON_AppService.axd/IsLoggedIn", true);
xhr.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status != 200) {
alert(xhr.statusText);
} else {
alert("IsLoggedIn = " + xhr.responseText);
}
xhr = null;
}
};
xhr.setRequestHeader("content-type", "application/json");
xhr.send(null);
}
</script>
</head>
<body>
<input type="button" value="IsLoggedIn?" onclick="isLoggedIn()" />
</body>
</html>
Number one... this is a bad idea. There is absolutely no security in checking if a user is authorized on the client side. None.
But if you really want to do this... do the check in code behind, and push a value to the client that can be read via Javascript. Something akin to:
RegisterClientScript("isvalidated", "var isUserAuthenticated = " + UserAuthenticated);
You see the problem now? You could do the same thing in AJAX... but it has the same problem.
OK, I can see doing this as a simple convenience for the user... showing certain links if they are authorized for instance. But it is not secure in any way shape or form. Just do yourself a favor and handle this in code-behind.

Resources