Next.jsのサービスを作り直す | Mac + Docker + Rails + Next.js その0049
参考にさせていただいたページ
- Next.js(TypeScript) × Docker(Compose V2) 最速構築Tips. #初心者 - Qiita
- Next.js開発環境構築にdocker composeを使い倒した話
- Node.js Docker baseイメージには alpine < distroless < ubuntu+slim 構成がよさそう
参考にした過去記事
はじめに
久しぶりに触ったらコードも良くないし、Next.jsをはじめとして、色々とパッケージが古いのでディレクトリごと作成しなおします。
npx create-next-app@latest --ts する
frontendディレクトリが既にあるので、frontend_v2ディレクトリを作成します。
npx create-next-app@latest --ts frontend_v2
対話形式で選択できる初期設定は以下の通り選択しました。

frontend_v2/Dockerfileの作成とその記述内容
vim frontend_v2/Dockerfile
以下の通り記述しました。
# Debian の slim イメージを使う # - 色々入っている node は使わない # - alpine は使わない FROM node:20.12.2-bookworm-slim # 環境変数を定義 ENV APP_PATH /usr/src/app # docker-compose.yml の volumes に合わせて $APP_PATH ディレクトリを作成 RUN mkdir $APP_PATH # ホストマシンにある package.json をコンテナ内の $APP_PATH/package.json にコピー COPY package.json $APP_PATH/ # $APP_PATH ディレクトリでコマンド実行 WORKDIR $APP_PATH RUN npm install
docker-compose.ymlの編集とその記述内容
vim docker-compose.yml
以下の通りfrontendサービスの設定を変更します。
version: "3.9"
services:
https-portal:
image: steveltn/https-portal:1
ports:
- 10080:80
- 10443:443
restart: always
environment:
DOMAINS: 'localhost -> http://nginx:8000'
STAGE: local
volumes:
- ./https_portal/ssl_certs:/var/lib/https-portal
depends_on:
- nginx
nginx:
build: ./nginx
ports:
- "18000:8000"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/html:/var/www/html
- ./nginx/log:/var/log
depends_on:
- frontend
frontend:
build:
context: ./frontend_v2
tty: true
volumes:
- ./frontend_v2:/usr/src/app
environment:
- WATCHPACK_POLLING=true
command: 'npm run dev'
ports:
- "3001:3001"
frontend_v2/package.jsonの編集とその記述内容
package.jsonのscripts.devを以下の通り編集します。
"scripts": {
"dev": "next dev -p 3001",
〜略〜
},
ここまでやった段階でのfrontend_v2/package.jsonの記述内容は以下の通りです。
{
"name": "frontend_v2",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"react": "^18",
"react-dom": "^18",
"next": "14.2.0"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.0"
}
}
docker-compose build する
一度buildしてみます。
docker-compose build
docker-compose up する
upしてみます。
docker-compose up
ブラウザでNext.jsのサーバーに直接アクセスして確認してみる
以下のURLにブラウザでアクセスすると、ページが表示されます。
- Next.jsのアプリケーション: http://localhost:3001/
ブラウザでhttps-portalサービスを介してアクセスしてみる
以下のURLにブラウザでアクセスすると、ページが表示されます。
nginx.conf内でリクエストヘッダーとリファラをチェックして403を返す | Mac + Docker + Rails + Next.js その0048
はじめに
- Next.jsでフロントエンドを作成して、そのドメインを
web-bonsai.techとしています。 - RailsでAPIを作成して、そのドメインを
api.web-bonsai.techとしています。
APIはweb-bonsai.techからのリクエストだけ受け付けたいので、リクエストヘッダーとリファラをチェックするようにしたときのメモです。
nginx.confはGitHubのリポジトリにはコミットされないように、.gitignoreしています。
nginx.confを編集する
以下のようにしてみました。
server {
〜略〜
# invalidなリクエストかどうか判定するための変数を定義
set $invalid_request '';
# $invalid_refererのとき$invalid_requestを1にする
valid_referers server_names example.com;
if ($invalid_referer) {
set $invalid_request 1;
}
# $invalid_refererだったとしても、headers['X-API-PASSWORD']が一致したとき$invalid_request = ''に戻す
if ($http_x_api_password = 'abcde') {
set $invalid_request '';
}
if ($invalid_request) {
return 403;
}
〜略〜
}
nginx.confのコードについての説明
まず判定用の変数$invalid_requestを定義して、空文字列を入れておきます。
# invalidなリクエストかどうか判定するための変数を定義
set $invalid_request '';
リファラが無い場合や、リファラのサーバ名がexample.comでない場合は、以下のブロックで変数$invalid_requestが1になります。
# $invalid_refererのとき$invalid_requestを1にする
valid_referers server_names example.com;
if ($invalid_referer) {
set $invalid_request 1;
}
Next.jsでSSGビルドをするとき、getStaticProps()内でfetch()すると、リファラが無いので上記のブロックで変数$invalid_requestが1になってしまいますが、リクエストヘッダーに正しい X-API-PASSWORD が存在する場合は、変数$invalid_requestを空文字列に戻します。
ここでは 'abcde' と比較していますが、実際には文字数や文字種の多いパスワードと比較します。
# $invalid_refererだったとしても、headers['X-API-PASSWORD']が一致したとき$invalid_request = ''に戻す
if ($http_x_api_password = 'abcde') {
set $invalid_request '';
}
$invalid_requestが空文字列でない場合は403を返します。
if ($invalid_request) {
return 403;
}
Next.jsのgetStaticProps()の記述内容
以下の通り、headersを設定して送信すると、nginx.conf内では変数$http_x_api_passwordで参照できます。
export const getStaticProps = async () => {
const response = await fetch(`${process.env.API_BASE_PATH}/api/tasks`, {
headers: {
'X-API-PASSWORD': process.env.X_API_PASSWORD ?? '',
},
});
const props = await response.json();
return {
props,
};
};
環境変数の定義
frontend/.env.localに以下の通り定義します。
X_API_PASSWORD=abcde
frontendサービスでyarn buildする
いつも通り以下のコマンドでビルドします。
$ docker-compose run --rm frontend yarn build
おわりに
nginx.conf内で2つの変数の文字列を結合して判定したり、正規表現を使って判定するともっと適切な書き方がありそうな気がします。
また、web-bonsai.techのフォーム入力などからリクエストする場合には、httponlyでsecureなcookieなどを使って判定した方がよさそうだなと思っていますが、nginx.confに慣れていなくてつらいので今のところはここまでにしておきます。
POSTしたときに422エラーでActionController::InvalidAuthenticityTokenになって失敗したので解決したい | Mac + Docker + Rails + Next.js その0048
参考にさせていただいたページ
- 【Rails】 API開発で『Can't verify CSRF token authenticity』といわれたときの対応 - Qiita
- 【Rails API】CSRF 対策をあきらめないでちゃんとやる | みどりみちのブログ
- Rails API + SPAのCSRF対策例
- RailsのCSRF保護を詳しく調べてみた(翻訳)|TechRacho by BPS株式会社
- RailsでAPI用のアプリを作成(POST処理編) - 親バカエンジニアのナレッジ帳
はじめに
解決できませんでしたが色々試行錯誤した個人的なログとして残しています。
環境について
この記事はローカル環境での話です。
docker-composeで、railsアプリケーションには https://localhost でアクセスできるようにしていて、next.jsアプリケーションには https://localhost:10443 だったり、https://localhost:3001だったりでアクセスできるようにしています。
解決したいログ(ActionController::InvalidAuthenticityToken)
https://localhost:10443からhttps://localhostに、POSTリクエストしたときにrailsのログに以下のように表示されました。
| Started POST "/api/tasks" for 172.25.0.1 at 2022-09-11 00:48:57 +0000
| Cannot render console from 172.25.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
| Processing by Api::TasksController#post as */*
| Parameters: {"name"=>"aaaaaaaaaa", "description"=>"", "task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
| HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)
| Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms | Allocations: 444)
|
|
|
| ActionController::InvalidAuthenticityToken (HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)):
|
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token'
| activesupport (7.0.3.1) lib/active_support/callbacks.rb:400:in `block in make_lambda'
ログを読んでみる
以下の部分はデバッグ用のrails consoleに関するメッセージのため、今回は関係ありません。
| Cannot render console from 172.25.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
送信したパラメーターのオブジェクト構造が間違っていますね。
| Parameters: {"name"=>"aaaaaaaaaa", "description"=>"", "task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
送信したNext.jsのアプリケーションのオリジン(https://localhost:10443)とRailsアプリケーションのオリジン(https://localhost)が違います。
| ActionController::InvalidAuthenticityToken (HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)):
ステータスコード422であることがわかります。
| Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms | Allocations: 444)
request_forgery_protection.rbの中で判定をしていそうです。
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token'
パラメータが違う
まずパラメータが違いますね。
{
name: 'aaaaaaaaaa',
description: '',
}
を送っていますが、
{
task: {
name: 'aaaaaaaaaa',
description: '',
},
}
を送らないといけませんでした。今回の場合。
パラメータを修正して再送信してログを確認する
パラメータを修正して再度POSTしたら以下のようになりました。
パラメータに関するログが変化しましたが、ステータスコード422であることには変化がありません。
| Started POST "/api/tasks" for 172.24.0.1 at 2022-09-16 06:15:24 +0000
| Cannot render console from 172.24.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
| Processing by Api::TasksController#post as */*
| Parameters: {"task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
| HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)
| Completed 422 Unprocessable Entity in 3ms (ActiveRecord: 0.0ms | Allocations: 446)
|
|
|
| ActionController::InvalidAuthenticityToken (HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)):
|
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token'
| activesupport (7.0.3.1) lib/active_support/callbacks.rb:400:in `block in make_lambda'
オリジンのチェックをしないようにする
rails/config/environments/development.rbに以下の設定を追加します。
# クライアント側とドメインが異なるため、クロスオリジンの検証を無効にする config.action_controller.forgery_protection_origin_check = false
docker-compose down と up をし直す
一度 docker-compose down して up し直します。
再送信してログを確認する
再度POSTしたら以下のようになりました。
HTTP Origin headerに関するログが2箇所変化しましたが、ステータスコード422であることには変化がありません。
| Started POST "/api/tasks" for 172.24.0.1 at 2022-09-16 06:31:36 +0000
| Cannot render console from 172.24.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
| ActiveRecord::SchemaMigration Pluck (4.1ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
| Processing by Api::TasksController#post as */*
| Parameters: {"task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
| Can't verify CSRF token authenticity.
| Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms | Allocations: 583)
|
|
|
| ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.):
|
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token'
| activesupport (7.0.3.1) lib/active_support/callbacks.rb:400:in `block in make_lambda'
一旦AuthenticityTokenの確認を無効にする
あくまで一旦です。
セキュリティ面を犠牲にして、とにかくPOSTできるようにしたい。ということになってしまいます。
AuthenticityTokenの確認を無効にしたいcontrollerに以下の記述を追加します。
今回の私の場合は、rails/app/controllers/api/tasks_controller.rbを編集します。
class TasksController < ApplicationController skip_before_action :verify_authenticity_token 〜略〜 end
再送信してログを確認する
再度POSTしたら以下のようになりました。
ステータスコード200になって、レコードが追加されました。
| Started POST "/api/tasks" for 172.24.0.1 at 2022-09-16 06:48:12 +0000
| Cannot render console from 172.24.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
| Processing by Api::TasksController#post as */*
| Parameters: {"task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
| TRANSACTION (0.4ms) BEGIN
| ↳ app/controllers/api/tasks_controller.rb:18:in `post'
| Task Exists? (0.7ms) SELECT 1 AS one FROM "tasks" WHERE "tasks"."name" = $1 LIMIT $2 [["name", "aaaaaaaaaa"], ["LIMIT", 1]]
| ↳ app/controllers/api/tasks_controller.rb:18:in `post'
| Task Create (12.5ms) INSERT INTO "tasks" ("name", "description", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "aaaaaaaaaa"], ["description", ""], ["created_at", "2022-09-16 06:48:12.631905"], ["updated_at", "2022-09-16 06:48:12.631905"]]
| ↳ app/controllers/api/tasks_controller.rb:18:in `post'
| TRANSACTION (2.2ms) COMMIT
| ↳ app/controllers/api/tasks_controller.rb:18:in `post'
| Task Load (0.3ms) SELECT "tasks".* FROM "tasks" ORDER BY "tasks"."created_at" DESC
| ↳ app/controllers/application_controller.rb:3:in `map'
| Completed 200 OK in 39ms (Views: 0.2ms | ActiveRecord: 16.1ms | Allocations: 5153)
AuthenticityTokenの確認を有効にする
先ほど追加した以下の行を削除します。
skip_before_action :verify_authenticity_token
AuthenticityTokenをちゃんと確認してPOSTできるようにする
レスポンスヘッダーにAuthenticityTokenを入れるメソッドを定義する
レスポンスヘッダーにauthenticity_tokenを入れるためのset_csrf_token_headerメソッドをrails/app/controllers/application_controller.rbに定義します。
class ApplicationController < ActionController::Base
def set_csrf_token_header
response.set_header('X-CSRF-Token', form_authenticity_token)
end
end
各アクションの後にset_csrf_token_headerを実行する
今回の私の場合は、rails/app/controllers/api/tasks_controller.rbを編集します。
各アクションの後にset_csrf_token_headerメソッドを実行します。
class TasksController < ApplicationController after_action :set_csrf_token_header 〜略〜 end
X-CSRF-Tokenヘッダをexposeする
rack-corsの設定を変更します。
rails/config/initializers/cors.rbのを編集します。
expose: ['X-CSRF-Token'], を追記して以下のようにします。
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins "*"
resource "*",
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
expose: ['X-CSRF-Token']
end
end
docker-compose down と up をし直す
一度 docker-compose down して up し直します。
ブラウザでレスポンスヘッダーを確認する
再度POSTして、ブラウザでレスポンスヘッダーを確認したら、以下のようにX-CSRF-Tokenが追加されていることが確認できました。

X-CSRF-Tokenを受け取るためのエンドポイントの作成
POSTする前に受け取りたいのでそのためのエンドポイントを作成します。
X-CSRF-Tokenを受け取る
以下のようにすると受け取れました。
const token = response.headers.get('X-CSRF-Token');
X-CSRF-Tokenをリクエストヘッダーに入れてPOSTしてみる
POSTしてみたものの以下の状態は解消されず、一旦諦めました...。
ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.)
おわりに
難しい。
本番環境のバックエンドのアクセス制限をする | Mac + Docker + Rails + Next.js その0047
参考にさせていただいたページ
- Module ngx_http_referer_module
- ngx_http_referer_module モジュール 日本語訳
- Nginxのrefererでサーバを振り分ける方法を考えてみた - 株式会社クイックのWebサービス開発blog
- NginxのWebサーバーでコンテンツへの直リンクを制御する方法 | 4thsight.xyz
はじめに
web-bonsai.techサーバーからのリクエスト以外は、api.web-bonsai.techで403エラーを返すことにします。
api.web-bonsai.techのnginx/nginx.confを編集する
以下のようにリファラでリクエスト元のサーバー名をチェックして403を返すようにしました。
server {
〜略〜
valid_referers server_names web-bonsai.tech;
if ($invalid_referer) {
return 403;
}
〜略〜
}
frontendサービスを別サーバーに移管する | Mac + Docker + Rails + Next.js その0046
frontendサービス用のサーバーを契約してセットアップする
以前と同じく契約して
- サーバーのセキュリティ設定
- vimのインストール
- gitのインストール
- dockerのインストール
- docker-composeのインストール
などのセットアップをします。
- さくらVPSを契約する | Mac + Docker + Rails その0012 - web_bonsaiの日記
- さくらのVPSの初期セキュリティ設定をする | Mac + Docker + Rails その0015 - web_bonsaiの日記
- さくらのVPSのCentOS9にgitをインストールする | Mac + Docker + Rails その0020 - web_bonsaiの日記
- さくらのVPSのCentOS9にDocker Engineをインストールする | Mac + Docker + Rails その0021 - web_bonsaiの日記
- Docker Composeをインストールする | Mac + Docker + Rails その0022 - web_bonsaiの日記
frontendサービス用のリポジトリを作成してサーバーにcloneする
だいたい以前やった手順を参考に行います。
frontendサービスを本番環境でブラウザからアクセスできるようにする
だいたい以前やった手順を参考に行います。
- 本番環境でブラウザからアクセスできるようにする | Mac + Docker + Rails その0024 - web_bonsaiの日記
- 本番環境にNext.jsアプリケーションをデプロイする | Mac + Docker + Rails + Next.js その0035 - web_bonsaiの日記
ブラウザで確認してみる
ブラウザで以下のURLにアクセスしてみます。
とりあえずバックエンドもフロントエンドもそれぞれのサーバーで表示できるようになりました。
以下のドメインでそれぞれ運用していきます。
- api.web-bonsai.tech
- web-bonsai.tech
バックエンドとフロントエンドを別々のサーバーで運用したいのでバックエンドをサブドメインで公開する | Mac + Docker + Rails + Next.js その0045
やること
web-bonsai.techドメインで公開していたrailsアプリケーションのドメインをapi.web-bonsai.techに切り替えていきます。
Next.jsで作っているfrontendサービスはこれを機に別サーバーに移管して運用します。
docker-compose.ymlを編集する
https-portalサービスのenviroment.DOMAINSを以下のようにします。
environment:
DOMAINS: 'api.web-bonsai.tech -> http://nginx:8000'
appサービスのenviroment.RAILS_ALLOW_HOST1を以下の通りにします。
environment:
- RAILS_ALLOW_HOST1=api.web-bonsai.tech
rails/config/environments/development.rbとproduction.rbの修正
この環境変数RAILS_ALLOW_HOST1は、
で以下のように使用されています。
config.hosts << ENV.fetch("RAILS_ALLOW_HOST1")
nginx/nginx.confを編集する
以下の、serverのserver_nameのところのweb-bonsai.techとなっていたところを以下のようにapi.web-bonsai.techとします。
server {
〜略〜
server_name api.web-bonsai.tech
〜略〜
}
docket-compose downとupする
downします。
$ docker-compose down
upします。
$ docker-compose up -d
ブラウザで確認してみる
ブラウザで
- https://api.web-bonsai.tech/
- https://api.web-bonsai.tech/mpa/
- https://api.web-bonsai.tech/mpa/tasks/
- https://api.web-bonsai.tech/api/tasks
残タスク
さくらのVPSとメールボックスを同ドメインで利用するネームサーバ設定をサブドメインでやる | Mac + Docker + Rails + Next.js その0044
はじめに
APIをサブドメインで運用して、フロントエンドと分けたくなったので、 api.web-bonsai.tech のネームサーバ設定をしていきます。
ネームサーバ新規登録
以前やった「さくらのVPSとメールボックスを同ドメインで利用するネームサーバ設定 | Mac + Docker + Rails その0013 - web_bonsaiの日記」を参考にして、「ネームサーバ新規登録」をします。
さくらのVPSのコントロールパネルにログインし、サイドバーのメニューから「DNS登録」をクリックします。
「ネームサーバを登録」セクションのフィールドに、ドメイン api.web-bonsai.tech を入力して「ドメインを登録する」ボタンをクリックします。
「ネームサーバサービス」ページが表示され、 api.web-bonsai.tech のゾーン状態は「未設定」になっています。
さくらのレンタルサーバに独自ドメインを追加
同じページを参考に進めていきます。
さくらのレンタルサーバコントロールパネルにログインし、サイドバーのメニューから「ドメイン/SSL」の「ドメイン/SSL」をクリックします。
「ドメイン新規追加」ボタンをクリックします。
他社でドメインを取得したので、「他社で取得したドメインを移管せずに使う」のセクションの「追加」ボタンをクリックします。
「他社で取得された独自ドメインのサブドメインを追加」のセクションのフィールドを以下の通り入力し、「追加」ボタンをクリックしました。

api.web-bonsai.techドメインが追加されました。
ドメインコントロールパネルでネームサーバのMXレコード情報を書き換える
参考ページに重要事項として書きましたが、先にMXレコード情報を書き換えます。
サイドバーから「ネームサーバサービス」をクリックします。
「編集」ボタンをクリックして、MXの 10 @ となっている箇所を
10 ○○.sakura.ne.jp. に書き換えます。
○○の部分はさくらのメールボックスまたはさくらのレンタルサーバのアカウント名です。
また、ここでは最後の . を忘れないように注意してください。
ネームサーバのAレコード情報を書き換える
MXレコード情報が済んだので、次はAレコード情報を書き換えます。
さっき書き換えたMXの上の「A」の項目です。
ドメインのネームサーバ設定
ムームードメインにログインして行う設定です。
ドメインを登録するときは必要ですが、サブドメインのときはたぶん不要だと思います。
たぶんですが。
メールユーザーの新規追加
さくらのレンタルサーバコントロールパネルにログインし、サイドバーから「メール」をクリックして、メールアドレス一覧ページに遷移します。
以下の画像の「新規追加」ボタンをクリックし、メールユーザーを必要に応じて新規追加します。