web_bonsaiの日記

web開発の学習日記です。誰に見せるためでもないただの日記です。

Next.jsのサービスを作り直す | Mac + Docker + Rails + Next.js その0049

参考にさせていただいたページ

参考にした過去記事

はじめに

久しぶりに触ったらコードも良くないし、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にブラウザでアクセスすると、ページが表示されます。

ブラウザでhttps-portalサービスを介してアクセスしてみる

以下のURLにブラウザでアクセスすると、ページが表示されます。

nginx.conf内でリクエストヘッダーとリファラをチェックして403を返す | Mac + Docker + Rails + Next.js その0048

はじめに

  • Next.jsでフロントエンドを作成して、そのドメインweb-bonsai.tech としています。
  • RailsAPIを作成して、そのドメイン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

参考にさせていただいたページ

はじめに

解決できませんでしたが色々試行錯誤した個人的なログとして残しています。

環境について

この記事はローカル環境での話です。

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

参考にさせていただいたページ

はじめに

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のインストール

などのセットアップをします。

frontendサービス用のリポジトリを作成してサーバーにcloneする

だいたい以前やった手順を参考に行います。

frontendサービスを本番環境でブラウザからアクセスできるようにする

だいたい以前やった手順を参考に行います。

ブラウザで確認してみる

ブラウザで以下の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は、

  • rails/config/environments/development.rb
  • rails/config/environments/production.rb

で以下のように使用されています。

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

ブラウザで確認してみる

ブラウザで

残タスク

  • frontendサービスを切り離して別のサーバーで運用する
  • api.web-bonsai.techはweb-bonsai.techからのリクエストだけ受け付けるようにアクセス制限する
  • frontendサービスに関する記述をapi.web-bonsai.techのリポジトリから削除する
    • docker-compose.yml
    • nginx/nginx.conf
    • .husky/pre-push

さくらの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レコード情報を書き換えます。

Aレコード情報をVPSIPアドレスにします。

さっき書き換えたMXの上の「A」の項目です。

ドメインのネームサーバ設定

ムームードメインにログインして行う設定です。

ドメインを登録するときは必要ですが、サブドメインのときはたぶん不要だと思います。

たぶんですが。

メールユーザーの新規追加

さくらのレンタルサーバコントロールパネルにログインし、サイドバーから「メール」をクリックして、メールアドレス一覧ページに遷移します。

以下の画像の「新規追加」ボタンをクリックし、メールユーザーを必要に応じて新規追加します。

送信してみる

さくらのレンタルサーバWebメールのページから試しに送信してみると、ちゃんと送信できるはずです。