iframeで外部ページを埋め込む時の問題
ページに組み込んだiframeが表示されない問題についての対処法です。
ブラウザのコンソールをチェックしてみると、以下のエラーが表示されている場合があるかもしれません。
Refused to display xxxxx in a frame because it set 'X-Frame-Options' to SAMEORIGIN'.
これはRailsでデフォルトのセキュリティヘッダーのX-Frame-Options がSAMEORIGIN
になっているからです。
X-Frame-Options
X-Frame-Options は HTTP のレスポンスヘッダーで、ブラウザーがページを frame, iframe, embed, object の中に表示することを許可するかどうかを示すために使用されます。サイトはコンテンツが他のサイトに埋め込まれないよう保証することで、クリックジャッキング攻撃を防ぐために使用することができます。
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
SAMEORIGIN
ではオリジンページに含まれているページのみフレームで表示することが可能という意味です。DENY
にするとフレームにページが読み込まれないようになります。
ALLOW-FROM (uri)
を使うと、指定されたuriのみがフレーム内で表示することが可能になります。
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-Frame-Options'] = 'ALLOW-FROM https://example.com'
しかしALLOW-FROM
はブラウザ側の未サポートやバグがあり、非推奨となっています。chromeやFireFoxなどのメインブラウザで無効なので使わない方が良さそうです。
またALLOWALL
というディレクティブがあるという記事もあります。(参照:Rails4: Allow your site to be iframed by another site. )
response.headers['X-Frame-Options'] = 'ALLOWALL'
しかし試したところ無効でした。MDNで確認したところ有効なディレクティブは現在SAMEORIGIN
とDENY
だけみたいです。
X-Frame-Optionsのまとめ
-
X-Frame-Options
- DENY - ページをフレーム内に表示できなくなる
- SAMEORIGIN - ページのドメインとフレームのドメインが同じ場合にのみ、ページがフレーム内で表示される。
- ALLOW-FROM http://example.com (非推奨) - 指定されたURIのページのみ、フレーム内で表示される。
- ALLOWALL (非推奨/無効) - どのページもフレーム内で表示される。
Content-Security-Policy (CSP)
コンテンツセキュリティポリシー (CSP) は、クロスサイトスクリプティング (XSS) やデータインジェクション攻撃などのような、特定の種類の攻撃を検知し、影響を軽減するために追加できるセキュリティレイヤーです。これらの攻撃はデータの窃取からサイトの改ざん、マルウェアの拡散に至るまで、様々な目的に用いられます。
CSP を有効にするには、ウェブサーバーから Content-Security-Policy HTTP ヘッダーを返すように設定する必要があります (X-Content-Security-Policy ヘッダーに関する記述が時々ありますが、これは古いバージョンのものであり、今日このヘッダーを指定する必要はありません)。
X-Frame-Optionsの代替方法
X-Frame-OptionsでALLOW-FROM uri
や ALLOWALL
を使えないので、その代替としてContent-Security-Policy(CSP)を使うことができそうです。CSPのナビゲーションディレクティブを用いると埋め込み先を管理することができます。
frame-ancestors
というディレクティブでによって埋め込み先のドメインを限定化し、そのドメインでのみの表示を許可することができます。
response.headers['Content-Security-Policy'] = "frame-ancestors https://example.com"
また例えばstaging環境を許容したいという場合は以下のような感じで書くことができます。
url = Rails.env.production? ? "https://example.com" : "https://staging.example.com"
response.headers['Content-Security-Policy'] = "frame-ancestors 'self' #{url}"
これはこのように書き換えることも可能です。
response.headers['Content-Security-Policy'] = "frame-ancestors 'self' https://*.example.com"
よって埋め込み元のcontrollerには以下のように記述すればOKです。
class BlogsController < ApplicationController
after_action :allow_iframe, only: [:iframe]
def iframe
@blog = Blog.new
end
def allow_iframe
response.headers['Content-Security-Policy'] = "frame-ancestors 'self' https://*.example.com"
end
end
CSP frame-ancestorsの書き方まとめ
- 埋め込み先を自身のドメイン (サブドメインを除く) に限定させたい場合。
例えばhttps://example.com/blogs
ページにhttps://example.com/articles
ページを埋め込みたい時、親がhttps://example.com
で同じなのでblogs(埋め込み元)のコントローラーに以下の処理を書けば親が共通のページはフレームで表示することができます。
response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
- 自身のドメインとそのサブドメインへの埋め込みを許可したい場合。
例えばhttps://example.com/blogs
と同じ親を持つhttps://example.com/articles
ページと、サブドメインhttps://staging.example.com
を親に持つhttps://staging.example.com/articles
ページに埋め込みたい時、blogs(埋め込み元)のコントローラーに以下の処理を書けば埋め込み先のフレーム内に表示されます。
response.headers['Content-Security-Policy'] = "frame-ancestors 'self' https://*.example.com"
'self'
はメインドメインを親に持つページ内への埋め込みを許可するという意味であり、https://*.example.com
はサブドメイン(https://staging.example.com
)を親とするページ内への埋め込みを許可するという意味になります。
CSPのまとめ
-
CSP
-
frame-ancestors -
<frame>
,<iframe>
,<object>
,<embed>
,<applet>
などの要素によって埋め込まれるページの親を指定し、埋め込みを有効にする。
-
frame-ancestors -
X-Frame-Optionsのエラーは出るものの変更する必要はなく(というか使えるディレクティブがなく)、その代わりにCSPを指定する必要があったというのが学びでした。