Skip to content

Commit 4268153

Browse files
authored
Merge pull request #1842 from MicrosoftEdge/api-additional-allowed-frame-ancestors
Api additional allowed frame ancestors
2 parents 0cd248f + 9d8e170 commit 4268153

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
Additional Allowed Frame Ancestors for iframes
2+
===
3+
4+
# Background
5+
Due to potential [Clickjacking](https://en.wikipedia.org/wiki/Clickjacking) attack, a lot of sites only allow themselves to be embedded in certain trusted ancestor iframes and pages. The main way to specify this ancestor requirement for sites are http header [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) and [Content-Security-Policy frame-ancestors directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors).
6+
7+
However, there are application scenarios that require embedding these sites in the app's UI that is authored as an HTML page.
8+
`<webview>` HTML element was provided for these embedding scenarios in previous solutions like Electron and JavaScript UWP apps.
9+
10+
For WebView2, we are providing a native API for these embedding scenarios. Developers can use it to provide additional allowed frame ancestors as if the site sent these as part of the Content-Security-Policy frame-ancestors directive. The result is that an ancestor is allowed if it is allowed by the site's original policies or by this additional allowed frame ancestors.
11+
12+
# Conceptual pages (How To)
13+
14+
To embed other sites in an trusted page with modified allowed frame ancestors
15+
- Listen to FrameNavigationStarting event of CoreWebView2.
16+
- Set AdditionalAllowedFrameAncestors property of the NavigationStartingEventArgs to a list additional allowed frame ancestors using the same syntax for the source list of [Content-Security-Policy frame-ancestors directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors). Basically, it is a space delimited list. All source syntax of Content-Security-Policy frame-ancestors directive are supported.
17+
18+
The list should normally only contain the origin of the top page.
19+
If you are embedding other sites through nested iframes and the origins of some of the intermediate iframes are different from the origin of the top page and those origins might not be allowed by the site's original policies, the list should also include those origins. As an example, if you owns the content on `https://example.com` and `https://www.example.com` and uses them on top page and some intermediate iframes, you should set the list as `https://example.com https://www.example.com`.
20+
21+
You should only add an origin to the list if it is fully trusted. When possible, you should try to limit the usage of the API to the targetted app scenarios. For example, you can use an iframe with a specific name attribute to embed sites (something like `<iframe name="my_site_embedding_frame">`) and then detect the embedding scenario is active when the trusted page is navigated to and the embedding iframe is created.
22+
23+
# Examples
24+
## Win32 C++
25+
```cpp
26+
const std::wstring myTrustedSite = L"https://example.com/";
27+
const std::wstring siteToEmbed = L"https://www.microsoft.com/";
28+
29+
bool AreSitesSame(PCWSTR url1, PCWSTR url2)
30+
{
31+
wil::com_ptr<IUri> uri1;
32+
CHECK_FAILURE(CreateUri(url1, Uri_CREATE_CANONICALIZE, 0, &uri1));
33+
DWORD scheme1 = -1;
34+
DWORD port1 = 0;
35+
wil::unique_bstr host1;
36+
CHECK_FAILURE(uri1->GetScheme(&scheme1));
37+
CHECK_FAILURE(uri1->GetHost(&host1));
38+
CHECK_FAILURE(uri1->GetPort(&port1));
39+
wil::com_ptr<IUri> uri2;
40+
CHECK_FAILURE(CreateUri(url2, Uri_CREATE_CANONICALIZE, 0, &uri2));
41+
DWORD scheme2 = -1;
42+
DWORD port2 = 0;
43+
wil::unique_bstr host2;
44+
CHECK_FAILURE(uri2->GetScheme(&scheme2));
45+
CHECK_FAILURE(uri2->GetHost(&host2));
46+
CHECK_FAILURE(uri2->GetPort(&port2));
47+
return (scheme1 == scheme2) && (port1 == port2) && (wcscmp(host1.get(), host2.get()) == 0);
48+
}
49+
50+
// App specific logic to decide whether the page is fully trusted.
51+
bool IsAppContentUri(PCWSTR pageUrl)
52+
{
53+
return AreSitesSame(pageUrl, myTrustedSite.c_str());
54+
}
55+
56+
// App specific logic to decide whether a site is the one it wants to embed.
57+
bool IsTargetSite(PCWSTR siteUrl)
58+
{
59+
return AreSitesSame(siteUrl, siteToEmbed.c_str());
60+
}
61+
62+
void MyApp::HandleEmbeddedSites()
63+
{
64+
// Set up the event listeners. The code will take effect when the site embedding page is navigated to
65+
// and the embedding iframe navigates to the site that we want to embed.
66+
67+
// This part is trying to scope the API usage to the specific scenario where we are embedding a site.
68+
// The result is recorded in m_embeddingSite.
69+
CHECK_FAILURE(m_webview->add_FrameCreated(
70+
Callback<ICoreWebView2FrameCreatedEventHandler>(
71+
[this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args)
72+
-> HRESULT
73+
{
74+
wil::unique_cotaskmem_string pageUrl;
75+
CHECK_FAILURE(m_webView->get_Source(&pageUrl));
76+
// IsAppContentUri verifies that pageUrl is app's content.
77+
if (IsAppContentUri(pageUrl.get()))
78+
{
79+
// We are on trusted pages. Now check whether it is the iframe we plan
80+
// to embed other sites.
81+
// We know that our trusted page is using <iframe name="my_site_embedding_frame">
82+
// element to embed other sites.
83+
const std::wstring siteEmbeddingFrameName = L"my_site_embedding_frame";
84+
wil::com_ptr<ICoreWebView2Frame> webviewFrame;
85+
CHECK_FAILURE(args->get_Frame(&webviewFrame));
86+
wil::unique_cotaskmem_string frameName;
87+
CHECK_FAILURE(webviewFrame->get_Name(&frameName));
88+
if (siteEmbeddingFrameName == frameName.get())
89+
{
90+
// We are embedding sites.
91+
m_embeddingSite = true;
92+
CHECK_FAILURE(webviewFrame->add_Destroyed(
93+
Microsoft::WRL::Callback<
94+
ICoreWebView2FrameDestroyedEventHandler>(
95+
[this](ICoreWebView2Frame* sender,
96+
IUnknown* args) -> HRESULT {
97+
m_embeddingSite = false;
98+
return S_OK;
99+
})
100+
.Get(),
101+
nullptr));
102+
}
103+
}
104+
return S_OK;
105+
})
106+
.Get(),
107+
nullptr));
108+
109+
// Using FrameNavigationStarting event instead of NavigationStarting event of CoreWebViewFrame
110+
// to cover all possible nested iframes inside the embedded site as CoreWebViewFrame
111+
// object currently only support first level iframes in the top page.
112+
CHECK_FAILURE(m_webview->add_FrameNavigationStarting(
113+
Microsoft::WRL::Callback<ICoreWebView2NavigationStartingEventHandler>(
114+
[this](
115+
ICoreWebView2* sender,
116+
ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
117+
{
118+
if (m_embeddingSite)
119+
{
120+
wil::unique_cotaskmem_string navigationTargetUri;
121+
CHECK_FAILURE(args->get_Uri(&navigationTargetUri));
122+
if (IsTargetSite(navigationTargetUri.get()))
123+
{
124+
wil::com_ptr<
125+
ICoreWebView2NavigationStartingEventArgs2>
126+
navigationStartArgs;
127+
if (SUCCEEDED(args->QueryInterface(
128+
IID_PPV_ARGS(&navigationStartArgs))))
129+
{
130+
navigationStartArgs
131+
->put_AdditionalAllowedFrameAncestors(
132+
myTrustedSite.c_str());
133+
}
134+
}
135+
}
136+
return S_OK;
137+
})
138+
.Get(),
139+
nullptr));
140+
}
141+
```
142+
## WinRT and .NET
143+
```c#
144+
const string myTrustedSite = "https://example.com/";
145+
const string siteToEmbed = "https://www.microsoft.com";
146+
private bool AreSitesSame(string url1, string url2)
147+
{
148+
auto uri1 = new Uri(url1);
149+
auto uri2 = new Uri(url2);
150+
return (uri1.SchemeName == uri2.SchemeName) && (uri1.Host == uri2.Host) && (uri1.Port == uri2.Port);
151+
}
152+
private bool IsAppContentUri(string pageUrl)
153+
{
154+
// App specific logic to decide whether the page is fully trusted.
155+
return AreSitesSame(pageUrl, myTrustedSite);
156+
}
157+
158+
private bool IsTargetSite(string siteUrl)
159+
{
160+
// App specific logic to decide whether the site is the one it wants to embed.
161+
return AreSitesSame(siteUrl, siteToEmbed);
162+
}
163+
164+
// This part is trying to scope the API usage to the specific scenario where we are embedding a site.
165+
// The result is recorded in m_embeddingSite.
166+
private void CoreWebView2_FrameCreated(CoreWebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2FrameCreatedEventArgs args)
167+
{
168+
// We know that our trusted page is using <iframe name="my_site_embedding_frame"> element to embed other sites.
169+
// We are embedding sites when we are on trusted pages and the embedding iframe is created.
170+
const string siteEmbeddingFrameName = "my_site_embedding_frame";
171+
if (IsAppContentUri(sender.Source) && (args.Frame.Name == siteEmbeddingFrameName))
172+
{
173+
m_embeddingSite = true;
174+
args.Frame.Destroyed += CoreWebView2_SiteEmbeddingFrameDestroyed;
175+
}
176+
}
177+
private void CoreWebView2_SiteEmbeddingFrameDestroyed(CoreWebView2Frame sender, Object args)
178+
{
179+
m_embeddingSite = false;
180+
}
181+
182+
// Using FrameNavigationStarting event instead of NavigationStarting event of CoreWebViewFrame
183+
// to cover all possible nested iframes inside the embedded site as CoreWebViewFrame
184+
// object currently only support first level iframes in the top page.
185+
private void CoreWebView2_FrameNavigationStarting(CoreWebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args)
186+
{
187+
if (m_embeddingSite && IsTargetSite(args.Uri))
188+
{
189+
args.AdditionalAllowedFrameAncestors = myTrustedSite;
190+
}
191+
}
192+
private void HandleEmbeddedSites()
193+
{
194+
// Set up the event listeners. The code will take effect when the site embedding page is navigated to
195+
// and the embedding iframe navigates to the site that we want to embed.
196+
webView.FrameCreated += CoreWebView2_FrameCreated;
197+
webView.FrameNavigationStarting += CoreWebView2_FrameNavigationStarting;
198+
}
199+
```
200+
201+
# API Details
202+
## Win32 C++
203+
```
204+
interface ICoreWebView2NavigationStartingEventArgs_2 : ICoreWebView2NavigationStartingEventArgs
205+
{
206+
207+
/// Get additional allowed frame ancestors set by the app.
208+
[propget] HRESULT AdditionalAllowedFrameAncestors([out, retval] LPWSTR* value);
209+
210+
/// The app may set this property to allow a frame to be embedded by additional ancestors besides what is allowed by
211+
/// http header [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)
212+
/// and [Content-Security-Policy frame-ancestors directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors).
213+
/// If set, a frame ancestor is allowed if it is allowed by the additional allowed frame
214+
/// ancestoers or original http header from the site.
215+
/// Whether an ancestor is allowed by the additional allowed frame ancestoers is done the same way as if the site provided
216+
/// it as the source list of the Content-Security-Policy frame-ancestors directive.
217+
/// For example, if `https://example.com` and `https://www.example.com` are the origins of the top
218+
/// page and intemediate iframes that embed a nested site-embedding iframe, and you fully trust
219+
/// those origins, you should set this property to `https://example.com https://www.example.com`.
220+
/// This property gives the app the ability to use iframe to embed sites that otherwise
221+
/// could not be embedded in an iframe in trusted app pages.
222+
/// This could potentially subject the embedded sites to [Clickjacking](https://en.wikipedia.org/wiki/Clickjacking)
223+
/// attack from the code running in the embedding web page. Therefore, you should only
224+
/// set this property with origins of fully trusted embedding page and any intermediate iframes.
225+
/// Whenever possible, you should use the list of specific origins of the top and intermediate
226+
/// frames instead of wildcard characters for this property.
227+
/// This API is to provide limited support for app scenarios that used to be supported by
228+
/// `<webview>` element in other solutions like JavaScript UWP apps and Electron.
229+
/// You should limit the usage of this property to trusted pages, and specific navigation
230+
/// target url, by checking the `Source` of the WebView2, and `Uri` of the event args.
231+
///
232+
/// This property is ignored for top level document navigation.
233+
///
234+
[propput] HRESULT AdditionalAllowedFrameAncestors([in] LPCWSTR value);
235+
236+
}
237+
```
238+
## WinRT and .NET
239+
```c#
240+
namespace Microsoft.Web.WebView2.Core
241+
{
242+
runtimeclass CoreWebView2NavigationStartingEventArgs
243+
{
244+
// ...
245+
246+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2NavigationStartingEventArgs2")]
247+
{
248+
String AdditionalAllowedFrameAncestors { get; set; };
249+
}
250+
}
251+
}
252+
```

0 commit comments

Comments
 (0)