【Rails】iframeタグを使って外部コンテンツが埋め込めない

## iframeで外部ページを埋め込む時の問題 
ページに組み込んだiframeが表示されない問題についての対処法です。
ブラウザのコンソールをチェックしてみると、以下のエラーが表示されている場合があるかもしれません。

```html:error
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 の中に表示することを許可するかどうかを示すために使用されます。サイトはコンテンツが他のサイトに埋め込まれないよう保証することで、[クリックジャッキング](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E3%82%B8%E3%83%A3%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0)攻撃を防ぐために使用することができます。

参照：[X-Frame-Options](https://developer.mozilla.org/ja/docs/Web/HTTP/X-Frame-Options)





```ruby
 response.headers['X-Frame-Options'] = 'SAMEORIGIN'
```

`SAMEORIGIN`ではオリジンページに含まれているページのみフレームで表示することが可能という意味です。`DENY`にするとフレームにページが読み込まれないようになります。
`ALLOW-FROM  (uri)`を使うと、指定された*uri*のみがフレーム内で表示することが可能になります。

```ruby
 response.headers['X-Frame-Options'] = 'DENY'
 response.headers['X-Frame-Options'] = 'ALLOW-FROM https://example.com'
```
しかし`ALLOW-FROM `はブラウザ側の未サポートやバグがあり、[非推奨](https://developer.mozilla.org/ja/docs/Web/HTTP/X-Frame-Options)となっています。chromeやFireFoxなどのメインブラウザで無効なので使わない方が良さそうです。
また`ALLOWALL`というディレクティブがあるという記事もあります。（参照：[Rails4: Allow your site to be iframed by another site.](https://coderwall.com/p/2d0phq/rails-4-allow-your-site-to-be-iframed-by-another-site) ）

```ruby
 response.headers['X-Frame-Options'] = 'ALLOWALL'
```

しかし試したところ無効でした。[MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/X-Frame-Options)で確認したところ有効なディレクティブは現在`SAMEORIGIN`と`DENY`だけみたいです。

### X-Frame-Optionsのまとめ
-  **X-Frame-Options** 
   -  **DENY**  - ページをフレーム内に表示できなくなる
    -  **SAMEORIGIN**  -  ページのドメインとフレームのドメインが同じ場合にのみ、ページがフレーム内で表示される。
    -  **ALLOW-FROM http://example.com**  (非推奨) - 指定されたURIのページのみ、フレーム内で表示される。
    -  **ALLOWALL**  (非推奨/無効) - どのページもフレーム内で表示される。
  
#  **Content-Security-Policy (CSP)** 
> コンテンツセキュリティポリシー (CSP) は、[クロスサイトスクリプティング (XSS)](https://developer.mozilla.org/ja/docs/Glossary/Cross-site_scripting) やデータインジェクション攻撃などのような、特定の種類の攻撃を検知し、影響を軽減するために追加できるセキュリティレイヤーです。これらの攻撃はデータの窃取からサイトの改ざん、マルウェアの拡散に至るまで、様々な目的に用いられます。

> 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`というディレクティブでによって埋め込み先のドメインを限定化し、そのドメインでのみの表示を許可することができます。


```ruby
response.headers['Content-Security-Policy'] = "frame-ancestors https://example.com"
```
また例えばstaging環境を許容したいという場合は以下のような感じで書くことができます。

```ruby
url = Rails.env.production? ? "https://example.com" : "https://staging.example.com"
response.headers['Content-Security-Policy'] = "frame-ancestors 'self' #{url}"
```
これはこのように書き換えることも可能です。

```ruby
response.headers['Content-Security-Policy'] = "frame-ancestors 'self' https://*.example.com"
```


よって埋め込み元のcontrollerには以下のように記述すればOKです。

```ruby:blogs_controller.rb
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(埋め込み元)のコントローラーに以下の処理を書けば親が共通のページはフレームで表示することができます。

```ruby:blogs_controller.rb
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(埋め込み元)のコントローラーに以下の処理を書けば埋め込み先のフレーム内に表示されます。

```ruby:blogs_controller.rb
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>`などの要素によって埋め込まれるページの親を指定し、埋め込みを有効にする。

X-Frame-Optionsのエラーは出るものの変更する必要はなく（というか使えるディレクティブがなく）、その代わりにCSPを指定する必要があったというのが学びでした。