SharePoint and iFrames (This content cannot be displayed in a frame)
Overview
From time to time someone may want to embed a webpage within another webpage. In HTML this is referred to as 'framing' where one web page frames another web page. One of the more common applications of this in SharePoint is framing Outlook Web Access in a SharePoint page. In previous versions of SharePoint (2007 and 2010) there was an Outlook Web Part that used this functionality. This web part itself was removed in 2013, however that doesn't stop someone from using the Page Viewer Web Part to achieve a similar experience or to frame any other content for that matter. The issue described here is the This content cannot be displayed in a frame message that can be seen.
The image below is a web part page hosted in SharePoint 2016 that contains a page viewer web part configured to display https://frame.joeric.lab/SitePages/Home.aspx which is a SharePoint 2013 page (note that the premise of this article is independent of the platform the pages are hosted on, this is all html and browser security in play here, nothing that is specific to SharePoint).
If we look at what the structure of this web part looks like at the HTML level this is what we see:
This shows us that this is just an html iframe tag.
Important Note: It is the content displayed in the frame that makes the declaration as to whether or not it can be displayed in a frame NOT the webpage that is hosting the frame. To further illustrate this the image below shows a simple html page created locally on the machine that contains a similar iframe tag that renders the same result.
This shows us that it isn't the fact that SharePoint is hosting the page that contains the frame, but rather the page that is being framed. Whether or not the content being framed is being rendered by SharePoint or some other hosting software this issue can be seen. Below is discussed how SharePoint influences this with the pages that it sets, however if it is a non-SharePoint hosted resource that is setting this header it is up to that application as to whether or not it can be displayed within a frame. Again it is up to the content that is being hosted as to whether or not it will be framed, not the application that is calling the framed content.
A web page can specify whether or not it is allowed to be displayed in a frame. Meaning that it is up to the publisher of the content that is being framed whether or not it can/will be displayed within another site or not. This is all controlled via the X-Frame-Options http response header.
By taking a Fiddler (or other similar http trace) while loading the page that contains the frame we will see both the request to the hosting page as well as the subsequent request to the content of the framed page. Below this is in frame 2 (request for the hosting page) and frame 21 (request for the page in the frame). Here the important part is frame 21. As mentioned before it is the content that is being framed that specifies whether or not the content can be framed. The hosting page is irrelevant in this scenario. In Fiddler you will want to select this request and then Inspectors > HTTP Headers in the response tab. Here you will see the X-FRAME-OPTIONS header as seen below:
The X-FRAME-OPTIONS header has a few different possible values:
- DENY - this indicates that the specified cannot be framed anywhere
- SAMEORIGIN - indicates that the content can be framed if it is being framed within the same hostname as the host page. I.e. if https://collab.contoso.com/Pages/Home.aspx is framing https://collab.contoso.com/Pages/FramedContent.aspx it will work. The important part here is that the hostname has to match (https://collab.contoso.com == https://collab.contoso.com).
- ALLOW-FROM uri (E.g. ALLOW-FROM https://collab.contoso.com) - indicates that the content can be framed only if the hosting page is under the hostname provided in the header. This header allows for only one hostname to be provided and does not support wildcards, therefore this header works with one and only one hostname.
- If the header is not present or is not valid then it will allow framing anywhere.
This is not an uncommon thing. Think about app-parts from SharePoint hosted apps. The app will be rendered within the app domain, but the customer may want to display this within a web part on one of the pages in the site. In this scenario it is not uncommon for a developer to create an aspx page that they will then render within an iFrame on the site hosting the app. Since SharePoint adds X-FRAME-OPTIONS: SAME ORIGIN to ALL aspx pages that it renders by default this would fail with the 'This content cannot be displayed within a frame'. Thankfully there is a control that can be added to the aspx page.
On the aspx page you wish to frame within a different hostname add the below control to one of the asp:Content elements on the page, in my examples I added this under PlaceHolderMain.
<WebPartPages:AllowFraming runat="server" />
Once this has been added to the page we see that the X-FRAME-OPTIONS header is no longer present. This can also be added to the master page of a site if there are many pages that you want to be framed. Do note that framing is prevented intentionally as a mechanism to prevent click jacking attacks and taking steps to circumvent these protections could open an environment up to these sorts of attacks.
More than one Authentication Providers causing 'Page cannot be displayed in a frame message' when /_login/Default.aspx is rendered?
If you have more than one authentication provider (say ADFS and Windows auth) in the same zone of a web application you will be directed to /_login/Default.aspx to select the authentication mechanism to use. As is the same story with any other aspx page SharePoint will add the X-FRAME-OPTIONS: SAMEORIGIN header to the response. Since this page requires a user to select an authentication method there are two options:
- Remove the additional authentication provider from the web application hosting the apps
- Create a custom login page and register the Microsoft.SharePoint.WebPartPages namespace and add the AllowFraming user control to the default or custom login page used.
- Create a custom login page as described in this article: https://msdn.microsoft.com/en-us/library/office/hh237665(v=office.14).aspx
- Lines to add:
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<WebPartPages:AllowFraming runat="server" />
When ADFS (or other SAML based authentication) is used SharePoint will set a FedAuth cookie to be used for the user to authenticate to the site. When a user is accessing an app in a frame this cookie will need to be set for the app-domain in the context of the iFrame. By default this works fine in IE, however there is a configuration that prevents IE from storing cookies when they are set within an iFrame. An easy way to test/identify this behavior is to hit the app directly (outside of the iFrame), then go back to the page where it is framed and the app should be displayed. This is because IE will utilize already existing cookies to send with the request, however cookies set within the iFrame will not be persisted.
In Fiddler you will see a loop back and forth between SharePoint and ADFS where the user authenticates to ADFS, comes back to SP and is issued a FedAuth cookie, then on the subsequent request to SP does not present the FedAuth cookie.
In the above Fiddler we see:
Frame |
Summary |
145 |
User requests the app and is directed to default login page and is eventually redirected to ADFS in frame 149 |
152 |
User is directed back to /_trust/Default.aspx and is issued a FedAuth cookie: |
153 |
User is redirected to the app, but if we look at the cookies set we do NOT see the FedAuth cookie that was just issued |
155 |
The user is ultimately directed back to authenticate since they didn't provide a valid cookie in the previous request: |
This will then loop until something puts an end to it. Typically ADFS will end the loop after a specified number of attempts to authenticate as a security measure.
To remedy this behavior we must tell IE to accept third party cookies:
Open Internet Explorer and click on Internet Options from the actions menu
Click on the Privacy tab and then click on Advanced under the Settings heading
Ensure that Third-party Cookies is set to Accept and press OK
In SharePoint 2007 there was a web part that displayed OWA (Outlook Web Access) that framed the OWA page within the SharePoint page. In Exchange 2013 there were changes implemented to improve security, one of these was setting the X-Frame-Options header to the responses from OWA which effectively breaks this functionality. Similarly the web part was removed from SharePoint starting in 2010. There is a configuration within Exchange that should be able to accommodate this, however there have been mixed results with the success/failure of it. Specifically this is the WebPartsFrameOptionsType parameter configurable through Set-OwaMailboxPolicy. In the past we have seen custom http modules used to remove the header after it is being sent, development/implementation/maintenance of such a customization would be on the customer.
The important take away here is that the hosting web site has no bearing over this. This is purely the content that is being displayed and modern browser security honoring the value specified from the content that is being framed.
Different Browsers Behave Differently
As I discussed the 'third party cookies' in IE there is one other key difference when it comes to framing content. That is that Google Chrome does not honor the Allow-From operator within the X-Frame-Options header. If that is specified you will see that the behavior in Chrome will be to allow framing anywhere. This isn't anything that we have control over, but rather Chrome's implementation of the header.