Skip to content

Commit 90f1833

Browse files
authored
Update AdditionalAllowedFrameAncestors.md
1 parent e85180b commit 90f1833

1 file changed

Lines changed: 125 additions & 100 deletions

File tree

specs/AdditionalAllowedFrameAncestors.md

Lines changed: 125 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,148 +2,173 @@ Additional Allowed Frame Ancestors for iframes
22
===
33

44
# Background
5-
Due to potential [Clickjacking](https://en.wikipedia.org/wiki/Clickjacking) attack, a lot of sites only allow them to be hosted in certain trusted ancestor iframes and top page.
6-
However, there are application scenarios that require hosting these sites in the app's UI that is authored as the HTML page.
5+
Due to potential [Clickjacking](https://en.wikipedia.org/wiki/Clickjacking) attack, a lot of sites only allow themselves to be hosted 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 hosting these sites in the app's UI that is authored as an HTML page.
78
`<webview>` HTML element was provided for these hosting scenarios in previous solutions like Electron and JavaScript UWP apps.
8-
For WebView2, we are providing a native API for these hosting scenarios.
9+
10+
For WebView2, we are providing a native API for these hosting scenarios. It let the developers to provide additional allowed frame ancestors as if the site sent these as part of the Content-Security-Policy frame-ancestors directive. An ancestor is allowed if it is allowed by the site's origional http headers or by this addtional allowed frame ancestors.
911

1012
# Conceptual pages (How To)
1113

1214
To host other sites in an trusted page
13-
- Listen to FrameNavigationStarting event of CoreWebView2 or NavigationStarting event of CoreWebView2Frame object.
15+
- Listen to FrameNavigationStarting event of CoreWebView2.
1416
- Set AdditionalAllowedFrameAncestors property of the NavigationStartingEventArgs to a list of trusted origins that is hosting the site.
1517

16-
The list should normally only contain the origin of the current trusted top page.
17-
If you are hosting other sites through nested iframes and the origins of some of the iframes are different from the origin of the top page, the list should also include those origins.
18+
The list should normally only contain the origin of the top page.
19+
If you are hosting other sites through nested iframes and the origins of some of the intermediate iframes are different from the origin of the top page, the list should also include those origins.
1820

1921
You should only add an origin to the list if it is fully trusted. You should limit the usage of the API to the targetted iframes whenever possible.
2022

2123
# Examples
2224
## Win32 C++
2325
```cpp
24-
const std::wstring myTrustedSite = L"example.com/";
25-
const std::wstring siteToHost = L"www.microsoft.com/";
26+
const std::wstring myTrustedSite = L"https://example.com/";
27+
const std::wstring siteToHost = L"https://www.microsoft.com/";
2628

27-
bool IsAppContentUri(const std::wstring& pageUrl)
29+
bool AreSitesSame(PCWSTR url1, PCWSTR url2)
2830
{
29-
// App specific logic to decide whether the page is fully trusted.
30-
wil::com_ptr<IUri> uri;
31-
CHECK_FAILURE(CreateUri(pageUrl.c_str(), Uri_CREATE_CANONICALIZE, 0, &uri));
32-
DWORD scheme = (DWORD)URL_SCHEME_INVALID;
33-
wil::unique_bstr host;
34-
CHECK_FAILURE(uri->GetScheme(&scheme));
35-
CHECK_FAILURE(uri->GetHost(&host));
36-
return (dwScheme == URL_SCHEME_HTTPS) && (host.get() == myTrustedSite);
31+
wil::com_ptr<IUri> uri1;
32+
CHECK_FAILURE(CreateUri(url1.c_str(), Uri_CREATE_CANONICALIZE, 0, &uri1));
33+
DWORD scheme1 = URL_SCHEME_INVALID;
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.c_str(), Uri_CREATE_CANONICALIZE, 0, &uri2));
41+
DWORD scheme2 = URL_SCHEME_INVALID;
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);
3748
}
3849

39-
bool IsTargetSite(const std::wstring& siteUrl)
50+
// App specific logic to decide whether the page is fully trusted.
51+
bool IsAppContentUri(PCWSTR pageUrl)
4052
{
41-
// App specific logic to decide whether the site is the one it wants to host.
42-
wil::com_ptr<IUri> uri;
43-
CHECK_FAILURE(CreateUri(siteUrl.c_str(), Uri_CREATE_CANONICALIZE, 0, &uri));
44-
DWORD scheme = (DWORD)URL_SCHEME_INVALID;
45-
wil::unique_bstr host;
46-
CHECK_FAILURE(uri->GetScheme(&scheme));
47-
CHECK_FAILURE(uri->GetHost(&host));
48-
return (dwScheme == URL_SCHEME_HTTPS) && (host.get() == siteToHost);
53+
return AreSitesSame(pageUrl, myTrustedSite);
54+
}
55+
56+
// App specific logic to decide whether a site is the one it wants to host.
57+
bool IsTargetSite(PCWSTR siteUrl)
58+
{
59+
return AreSitesSame(pageUrl, siteToHost);
4960
}
5061

51-
void HandleHostedSites()
62+
void MyApp::HandleHostedSites()
5263
{
53-
wil::com_ptr<ICoreWebView2_4> webview2_4 = m_webView.try_query<ICoreWebView2_4>();
54-
if (webview2_4)
55-
{
56-
CHECK_FAILURE(webview2_4->add_FrameCreated(
57-
Callback<ICoreWebView2FrameCreatedEventHandler>(
58-
[this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args)
59-
-> HRESULT {
60-
wil::com_ptr<ICoreWebView2Frame> webviewFrame;
61-
CHECK_FAILURE(args->get_Frame(&webviewFrame));
62-
wil::unique_cotaskmem_string pageUrl;
63-
CHECK_FAILURE(m_webView->get_Source(&pageUrl));
64-
// IsAppContentUri verifies that pageUrl is app's content.
65-
if (IsAppContentUri(pageUrl.get()))
64+
CHECK_FAILURE(m_webview->add_FrameCreated(
65+
Callback<ICoreWebView2FrameCreatedEventHandler>(
66+
[this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args)
67+
-> HRESULT
68+
{
69+
wil::com_ptr<ICoreWebView2Frame> webviewFrame;
70+
CHECK_FAILURE(args->get_Frame(&webviewFrame));
71+
wil::unique_cotaskmem_string pageUrl;
72+
CHECK_FAILURE(m_webView->get_Source(&pageUrl));
73+
// IsAppContentUri verifies that pageUrl is app's content.
74+
if (IsAppContentUri(pageUrl.get()))
75+
{
76+
// We are on trusted pages. Now check whether it is the iframe we plan
77+
// to host other sites.
78+
const std::wstring siteHostingFrameName = L"my_site_hosting_frame";
79+
wil::unique_cotaskmem_string frameName;
80+
CHECK_FAILURE(webviewFrame->get_Name(&frameName));
81+
if (siteHostingFrameName == frameName.get())
6682
{
67-
// We are on trusted pages. Now check whether it is the iframe we plan
68-
// to host other sites.
69-
const std::wstring siteHostingFrameName = L"my_site_hosting_frame";
70-
wil::unique_cotaskmem_string frameName;
71-
CHECK_FAILURE(webviewFrame->get_Name(&frameName));
72-
if (siteHostingFrameName == frameName.get())
73-
{
74-
wil::com_ptr<ICoreWebView2ExperimentalFrame> frameExperimental =
75-
webviewFrame.try_query<ICoreWebView2ExperimentalFrame>();
76-
if (frameExperimental)
77-
{
78-
frameExperimental->add_NavigationStarting(
79-
Microsoft::WRL::Callback<
80-
ICoreWebView2ExperimentalFrameNavigationStartingEventHandler>(
81-
[](
82-
ICoreWebView2Frame* sender,
83-
ICoreWebView2NavigationStartingEventArgs* args)
84-
-> HRESULT
85-
{
86-
wil::unique_cotaskmem_string navigationTargetUri;
87-
CHECK_FAILURE(args->get_Uri(&navigationTargetUri));
88-
wil::com_ptr<
89-
ICoreWebView2NavigationStartingEventArgs_2>
90-
nav_start_args;
91-
// If the feature is supported and it is the site we
92-
// want to host, then allow sites to be hosted by app's page.
93-
if (SUCCEEDED(args->QueryInterface(
94-
IID_PPV_ARGS(&nav_start_args))) &&
95-
IsTargetSite(navigationTargetUri.get()))
96-
{
97-
nav_start_args
98-
->put_AdditionalAllowedFrameAncestors(
99-
myTrustedSite);
100-
}
101-
return S_OK;
102-
})
103-
.Get(),
104-
nullptr);
105-
}
106-
}
83+
// We are hosting sites.
84+
m_hosting_site = true;
85+
CHECK_FAILURE(webviewFrame->add_Destroyed(
86+
Microsoft::WRL::Callback<
87+
ICoreWebView2FrameDestroyedEventHandler>(
88+
[this](ICoreWebView2Frame* sender,
89+
IUnknown* args) -> HRESULT {
90+
m_hosting_site = false;
91+
return S_OK;
92+
})
93+
.Get(),
94+
nullptr));
10795
}
108-
return S_OK;
109-
})
110-
.Get(),
111-
nullptr));
112-
}
96+
}
97+
return S_OK;
98+
})
99+
.Get(),
100+
nullptr));
101+
CHECK_FAILURE(m_webview->add_FrameNavigationStarting(
102+
Microsoft::WRL::Callback<ICoreWebView2NavigationStartingEventHandler>(
103+
[this](
104+
ICoreWebView2* sender,
105+
ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
106+
{
107+
if (m_hosting_site)
108+
{
109+
wil::unique_cotaskmem_string navigationTargetUri;
110+
CHECK_FAILURE(args->get_Uri(&navigationTargetUri));
111+
wil::com_ptr<
112+
ICoreWebView2NavigationStartingEventArgs_2>
113+
nav_start_args;
114+
if (SUCCEEDED(args->QueryInterface(
115+
IID_PPV_ARGS(&nav_start_args))) &&
116+
IsTargetSite(navigationTargetUri.get()))
117+
{
118+
nav_start_args
119+
->put_AdditionalAllowedFrameAncestors(
120+
myTrustedSite);
121+
}
122+
}
123+
return S_OK;
124+
})
125+
.Get(),
126+
nullptr));
113127
}
114128
```
115129
## WinRT and .NET
116130
```c#
117-
const string myTrustedSite = "example.com/";
118-
const string siteToHost = "www.microsoft.com";
131+
const string myTrustedSite = "https://example.com/";
132+
const string siteToHost = "https://www.microsoft.com";
133+
private bool AreSitesSame(string url1, string url2)
134+
{
135+
auto uri1 = new Uri(url1);
136+
auto uri2 = new Uri(url2);
137+
return (uri1.SchemeName == uri2.SchemeName) && (uri1.Host == uri2.Host) && (uri1.Port == uri2.Port);
138+
}
119139
private bool IsAppContentUri(string pageUrl)
120140
{
121141
// App specific logic to decide whether the page is fully trusted.
122-
auto uri = new Uri(pageUrl);
123-
return uri.SchemeName == "https" && uri.Host == myTrustedSite;
142+
return AreSitesSame(pageUrl, myTrustedSite);
124143
}
125144
126-
private bool IsTargetSite(string uri)
145+
private bool IsTargetSite(string url)
127146
{
128147
// App specific logic to decide whether the site is the one it wants to host.
129-
auto uri = new Uri(pageUrl);
130-
return uri.SchemeName == "https" && uri.Host == siteToHost;
148+
return AreSitesSame(url, siteToHost);
131149
}
132150
133151
private void CoreWebView2_FrameCreated(CoreWebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2FrameCreatedEventArgs args)
134152
{
135153
// my_site_hosting_frame is the name attribute on the iframe element that we used in the web page to host the site.
136154
const string siteHostingFrameName = "my_site_hosting_frame";
137-
138155
if (IsAppContentUri(sender.Source) && (args.Frame.Name == siteHostingFrameName))
139156
{
140-
args.Frame.NavigationStarting += (CoreWebView2Frame navStartingSender, CoreWebView2NavigationStartingEventArgs navStartingArgs) =>
141-
{
142-
if (IsTargetSite(navStartingArgs.Uri))
143-
{
144-
args.AdditionalAllowedFrameAncestors = myTrustedSite;
145-
}
146-
};
157+
m_hosting_site = true;
158+
args.Frame.Destroyed += CoreWebView2_SiteHostingFrameDestroyed;
159+
}
160+
}
161+
162+
private void CoreWebView2_SiteHostingFrameDestroyed(CoreWebView2Frame sender, Object args)
163+
{
164+
m_hosting_site = false;
165+
}
166+
167+
private void CoreWebView2_FrameNavigationStarting(CoreWebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args)
168+
{
169+
if (IsTargetSite(args.Uri))
170+
{
171+
args.AdditionalAllowedFrameAncestors = myTrustedSite;
147172
}
148173
}
149174
```

0 commit comments

Comments
 (0)