今回達成すること
今回は、Safariのブラウザで正常にログインできるように設定します。
RailsとNuxt.js、それぞれのアプリをHerokuにデプロイしている前提で話を進めます。
また、この記事には
- Herokuに独自ドメインを設定する方法
- 独自ドメインをSSL化する方法
- httpとHerokuドメインを独自ドメインにリダイレクトする方法
が書かれています。
現状の問題
現在は、Safariのブラウザでログインを行うと、401認証エラーが返ってきます。
原因
原因は、SafariがデフォルトでクロスサイトのCookieを保存できない仕様になっているためです。
クロスサイトリソースのCookieは、デフォルトで全面的にブロックされるようになりました。これは、例外の感覚を取り除くか、「少しのクロスサイト追跡が許可される」ため、プライバシーの大幅な改善です。
CookieにアクセストークンがないままRailsにリクエストを行なっているため、ログイン直後のプロジェクト取得リクエストが拒否されるために起こるエラーです。
解決
解決方法は、フロントとサーバーサイド共に同じDNSサーバーで運用することです。
これにより、Safariはサーバーサイドをサードパーティと見なさず、Cookieが保存できないエラーを回避できます。
具体的には、
- Nuxt.jsアプリのサブドメインと
- Railsアプリのサブドメイン
両方を同じDNSサーバー内のCNAMEに追加します。
注意点
この解決方法は、Herokuの有料プランを使用するため、月$7 × 2 = $14 (2020年11月現在)の料金が発生します。
実験的に実装する場合は、1ヶ月以内に無料プランへ戻してください。
また、ドメイン取得前提で話を進めますので、まだの方は先にドメインを取得しここへ戻ってきてください。
実装手順
長くなるので手順を整理します。
フロント(Nuxt.js)
- Herokuに独自ドメインを追加する
- ドメインのDNS設定を行う
- Heroku SSLを使って独自ドメインをSSL化(
https
)する - Railsの
API_DOMAIN
の値を変更する - httpとherokuapp.comのリダイレクト処理を行う
サーバー(Rails)
- Herokuに独自ドメインを追加する
- ドメインのDNS設定を行う
- Heroku SSLを使って独自ドメインをSSL化(
https
)する - Nuxt.jsの
API_URL
の値を変更する - httpとherokuapp.comのリダイレクト処理を行う
【Nuxt.js】Herokuに独自ドメインを追加する
ドメインを取得している前提で操作を進めます。
「front」ディレクトリに移動してHerokuにログインします。
root $ cd front
front $ heroku login
heroku: Press any key to open up the browser to login or q to exit: <Enterキー>
# ブラウザよりログインする
今回はサブドメインを設定する方法です。
domains:add
コマンドでドメインを追加します。
ドメイン名はご自身のドメインに置き換えて実行してください。
front $ heroku domains:add udemy-v1.cloud-acct.com
DNSターゲット名が表示されます。
Configure your app's DNS provider to point to the DNS Target <DNSターゲット>.herokudns.com.
domains
コマンドでも、ドメインに関連付いたDNSターゲット名の確認ができます。
front $ heroku domains
Domain Name DNS Record Type DNS Target
udemy-v1.cloud-acct.com CNAME <DNSターゲット>.herokudns.com
【Nuxt.js】ドメインのDNS設定を行う
ここからはドメイン設定画面に移動します。
筆者は、G Suite経由のGoDaddy(ゴーダディ)でドメインを取得したので、その管理画面を前提に説明します。
操作画面は違いますが、DNS設定は自体は同じなので、ご自身の操作画面と照らし合わせて進めてください。
ドメイン管理画面より、DNS設定へ移動します。
CNAMEを追加する
DNS管理画面の右下の「追加」ボタンよりタイプCNAMEを選択します。
- ホストは、追加したサブドメインのドット以前部分(
udemy-v1
) - ポイント先は、HerokuのDNSターゲット名をコピーしたもの
を追加して保存します。
以上で、Herokuにサブドメインが追加できました。
この設定はHerokuの「Settings」タブからも確認ができます。
DNS設定の反映は、24時間以上かかる場合もあります。
確認が取れれば独自サブドメイン(http://udemy-v1.cloud-acct.com
)でNuxtアプリが起動できます。
【Nuxt.js】HerokuでSSL設定を行う
今のままではドメインがhttp
となっており、Cookieが保存できません。
これは、Rails上で「本番環境でのみCookieのsecure
を有効にする」実装を行なっているためです。
secure
を有効にした場合、https
経由のSSL通信でないとCookieが保存されません。
そこで、Herokuに設定したサブドメインをSSL通信に変更します。
Herokuを有料プランに変更する
まずは有料プランへの変更を行います。
これから行うSSL設定は、Herokuが取得した証明書を使用するため、月$7(2020年11月現在)の料金が必要です。
Heroku管理画面の「Resources(リソーシズ)」タブから「Change Dyno Type(チェンジ ダイノ タイプ)」をクリックします。
「Hobby(ホビー)」を選択し、保存します。
SSL設定の確認を行う
有料プランに変更すると、Herokuが自動でSSL設定を行なってくれます。
「Settings」タブの、「SSL Certificates(エスエスエル サーティフィケート )」メニューを確認し、緑のチェクマークが入っていればSSL設定ができています。
ドメインもhttps
に変わっていますね。
手動でSSL設定を行う
手動で設定する場合は、「Configure SSL(コンフィギュア エスエスエル)」をクリックします。
開いたウィンドウの「Automatic Certificate Management (ACM)(オートマチック サーティフィケート マネジメント)」を選択します。
これがSSL証明書を取得、自動更新するプランです。
これでSSL設定は完了です。
ブラウザで、https
のサブドメインでNuxt.jsアプリが稼働していることを確認してください。
【Rails】環境変数のAPI_DOMAINを変更する
Railsの本番環境で使用するAPIドメインを変更します。
環境変数のAPI_DOMAIN
を、今回取得したサブドメインに変更します。
front $ cd ../api
api $ heroku config:set API_DOMAIN=udemy-v1.cloud-acct.com
api $ heroku config
API_DOMAIN: udemy-v1.cloud-acct.com
...
【Nuxt.js】httpとherokuappのリダイレクト処理
今のNuxt.jsは
- http://udemy-v1.cloud-acct.com
- https://udemy-v1.cloud-acct.com (本番用)
- https://udemy-demoapp-v1-front.herokuapp.com
の3つのドメインに紐づいています。
全てのドメインを、本番用のhttps://udemy-v1.cloud-acct.com
にリダイレクトする処理を行います。
Nuxt.jsアプリ直下に「server」ディレクトリと、
api $ cd..
root $ mkdir front/server && touch $_/redirectSsl.js
serverMiddleware
から、作成ファイルを読み込むよう設定します。
ここで指定したファイルは、 Nuxt.jsが読み込まれた直後のサーバサイドで実行されます。
front/nuxt.config.js
export default {
...
modules: [
...
],
// 追加
// Doc: https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-servermiddleware
serverMiddleware: [
// 301 redirect
'~/server/redirectSsl.js'
],
...
}
URLがhttp
とherokuapp.com
を含む場合に、正規のURLへリダイレクトします。
front/server/redirectSsl.js
// req、res => https://nuxtjs.org/docs/2.x/internals-glossary/nuxt-render
export default (req, res, next) => {
const { NODE_ENV, BASE_URL } = process.env
const isProduction = NODE_ENV === 'production' && !!BASE_URL
const isRedirectDomain = /herokuapp.com/.test(req.headers.host)
const isRedirectProtocol = req.headers['x-forwarded-proto'] === 'http'
// httpとHerokuの場合にリダイレクト処理
if (isProduction && isRedirectProtocol || isProduction && isRedirectDomain) {
const redirectUrl = BASE_URL + req.url
// リクエストにレスポンスヘッダーを送信する
// https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers
res.writeHead(301, { Location: redirectUrl })
// すべての応答ヘッダーと本文が送信されたことをサーバーに通知する
// https://nodejs.org/api/http.html#http_response_end_data_encoding_callback
return res.end(redirectUrl)
}
return next()
}
Herokuに環境変数をセットする
「front」ディレクトリに移動し、本番用URLを代入した環境変数BASE_URL
を追加します。
root $ cd front
front $ heroku config:set BASE_URL=https://udemy-v1.cloud-acct.com
確認しよう
変更をHerokuにPushして、アプリを開きます。
front $ git add -A && git commit -m "add_serverMiddleware_redirectSsl.js"
front $ git push && git push heroku && heroku open
Herokuのドメインがhttps
にリダイレクトされています。
同じくhttp
にもアクセスできないことを確認してください。
【Rails】Herokuに独自ドメインを追加する
Railsにも独自ドメインを設定します。
front $ cd ../api
api $ heroku login
# 好きなドメインを指定
api $ heroku domains:add udemy-v1-api.cloud-acct.com
【Rails】ドメインのDNS設定を行う
お使いのドメインDNS設定で、表示されたDNSターゲットとサブドメインを追加します。
タイプ | ホスト | ポイント先 |
---|---|---|
CNAME | udemy-v1-api | <DNSターゲット>.herokudns.com |
【Rails】HerokuでSSL設定を行う
Herokuにログインし、「Resources」から有料プランに変更します。
Heroku SSLの証明書が設定できているか確認します。
api $ heroku certs:auto
...
Issuer: /C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
Let's Encryptの証明書が発行できています。
【Nuxt.js】環境変数API_URLを変更する
「front」ディレクトリのAPI_URL
を書き換えます。
front/heroku.yml
build:
docker:
web: Dockerfile
config:
WORKDIR: app
# 変更
API_URL: "https://udemy-v1-api.cloud-acct.com"
# 削除
# API_URL: "https://udemy-demoapp-v1-api.herokuapp.com"
run:
web: yarn run start
「front」ディレクトリ上でHerokuにpushします。
front $ git commit -am "edit_heroku.yml"
front $ git push && git push heroku
証明書の適用はタイムラグがあります。(筆者は30分ほどアクセスできなかった)
確認するには、Rails側のhttps
のURLにアクセスしてください。
こんな画面が出てきた場合、まだSSLが有効ではありません。時間の経過で有効になります。
24時間経過しても有効にならない場合は、もう一度設定をやり直してください。
間違っている可能性があります。
SSL設定が無効の状態
確認しよう
Rails側のSSL設定が完了したら、Safariでログインしてみましょう。
今度はサードパーティCookieとは見なされず、無事ログインすることができました。
【Rails】httpとherokuappのリダイレクト処理
サーバーサイドはユーザーから何も見えませんが、リクエストを返すドメインが複数存在するのはセキュリティ上好ましくないので、リダイレクト処理を行います。
httpからhttpsへリダイレクトさせる
この設定は、SSL通信の使用を強要するもので、自動でhttps
へリダイレクトするようになります。
api/config/environments/production.rb
...
# 40行目付近
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
参考: Can Heroku force an application to use SSL/TLS? - Heroku Help
herokuapp.comから独自ドメインへリダイレクト
Herokuのアプリドメインにアクセスした場合に、独自ドメインへリダイレクトを行う処理を実装します。
今回実装するリダイレクト処理は、無効なURLの場合(404エラー)のリダイレクトを行いません。
全てのURLをリダイレクトする場合は Gem rack-rewrite(ラック リライト)で実装できると思われます。(未確認)
before_action
を追加します。
api/app/controllers/application_controller.rb
class ApplicationController < ActionController::API
# 追加
# 301リダイレクト(本番環境のみ有効)
before_action :moved_permanently, if: :is_redirect
include ActionController::Cookies
include UserAuth::Authenticator
# 以下、追加
private
# リダイレクト条件に一致した場合trueを返す
def is_redirect
target = "herokuapp.com"
# include? => 文字列に引数の文字列が含まれている場合trueを返す
Rails.env.production? && ENV["BASE_URL"] && request.host.include?(target)
end
# 301リダイレクトを行う
def moved_permanently
redirect_to "#{ENV["BASE_URL"]}#{request.path}", status: 301
end
end
Herokuの環境変数にBASE_URL
を追加し、新しく追加したhttps
のURLを代入します。
front $ cd ../api
api $ heroku config:set BASE_URL=https://udemy-v1-api.cloud-acct.com
これでRails側のリダイレクト処理は完了です。
確認しよう
ここまでの変更をHerokuにpushします。
api $ git commit -am "add_ssl_and_301_redirect_application.rb"
api $ git push && git push heroku
デプロイが完了したら、ブラウザから
-
Herokuのドメイン と
https://<Herokuアプリ名>.herokuapp.com/api/v1/users/current_user
-
独自ドメインの
http
にhttp://<独自ドメイン>/api/v1/users/current_user
アクセスしてください。
双方ともhttps
+ 独自ドメイン
にリダイレクトされていれば成功です。
rootディレクトリもpushしとく
以上でSafariのクロスサイトでのCookie保存エラーに対応できました。
最後に「root」ディレクトリをpushします。
api $ cd ..
root $ git commit -am "finished_Safari_cross_domain_Cookies_and_ssl_redirects"
root $ git push
まとめ
今回は、Herokuに独自ドメインの設定と、リダイレクト処理を行いました。
同じDNSサーバー内でフロントとサーバーサイドを運用することで、SafariのCookieの保存拒否に対応することができます。
Chromeも2022年までに、Safariと同じようにサードパーティのCookieを拒否する仕様にするよう動いているようで。
こうやってHerokuの無料枠で本番環境の動きを確認できるのも、今だけかもしれませんね。
幸せな時代に開発できました。
参考: Building a more private web: A path towards making third party cookies obsolete