🐱

Next.jsをVercelでデプロイして、Previewだけにbasic認証をかける…必要はあるのか?

2022/02/27に公開

これやる必要ある? みたいなものなんですけど。

思いついて試してみたら動いたのでメモっておきます。

多分もっと良い方法があると思いますし、真剣にセキュリティ的なことを考えるのならば、vercelの有料のパスワード機能を使うべきなのではと思ったりします。

あと、vercelの使い方なども試しきれてないので、探り探りになっていて冗長な記事です。間違っている部分も多いかもしれません。参考程度に読み流してもらえるとありがたいです。

Next.jsのMiddleware

Next.js v12からMiddlewareというものが実装されました。

https://nextjs.org/docs/middleware

色々使えます。

middlewareはリクエストが完了する前に実行されるため、認証やABテストなど、様々なもの使えます。

以下に例があります。

https://vercel.com/docs/concepts/functions/edge-functions

この中にbasic認証があります。

この例をもとに、vercelの環境変数を使ってbasic認証を環境ごとに切り替えます。

全環境にbasic認証をかける

とりあえず全環境にbasic認証を設定してみます。

以下のサンプル通りにやるだけです。

https://github.com/vercel/examples/tree/main/edge-functions/basic-auth-password

_middleware.tsをbasic認証をかけたいファイルと同階層のフォルダに入れておきます。

Next.jsでは/pages配下に置いておくだけで、全ページにbasic認証がかかります。

_middleware.ts
import { NextRequest, NextResponse } from 'next/server'

export function middleware(req: NextRequest) {
  const basicAuth = req.headers.get('authorization')

  if (basicAuth) {
    const auth = basicAuth.split(' ')[1]
    const [user, pwd] = Buffer.from(auth, 'base64').toString().split(':')

    if (user === '4dmin' && pwd === 'testpwd123') {
      return NextResponse.next()
    }
  }

  return new Response('Auth required', {
    status: 401,
    headers: {
      'WWW-Authenticate': 'Basic realm="Secure Area"',
    },
  })
}

パスワードがべた書きですが、後程vercelの環境変数に変更します。

環境関係なく、vercelのプロジェクトにbasic認証をしたい場合も、パスワードは環境変数にしておくと良いと思います。

vercelの環境変数を設定する

vercelでは環境変数を設定可能です。しかもPreview、Development、Production毎に設定できます。Previewではブランチ毎に設定できるようです。

また、環境変数はすべての値が暗号化されるようになったと以下の公式にあります。

https://vercel.com/changelog/environments-variables-per-git-branch

設定方法

vercel内のプロジェクトに入り、setting ⇒ Environment Variables で、環境変数を設定できます。

NAMEとVALUEを入力し、指定のENVIRONMENTにチェックを入れてAddボタンを押すだけです。

今回の例では、ユーザーIDをBASIC_AUTH_USER、パスワードをBASIC_AUTH_PASSWORDとして設定しておきます。また、basic認証をかけるかどうかのENABLE_BASIC_AUTHも設定しておきます(ENABLE_BASIC_AUTHはidかパスの環境変数で代替可能なんですけど)。

PreviewとDevelopmentにだけbasic認証をかけるので、この2つにだけチェックを入れて設定します。

vercelの各環境について

少しだけvercelの環境についてです。なんだかよくわからなかったので色々試した結果をメモしておきます。

まず前提として、過去も含めた全てのPreviewとProductionに対してブラウザのアドレス欄にurlを入力すれば、外部から参照可能です。(Developmentは試してないです)

urlをランダムに入力して当てるのは人力では不可能でしょうが、企業としての安全性を考えると、辞めた人の中に悪い人がいてurlを控えてて何かに悪用する…………なんてことをリスクととらえる人もいそうです。そういう意味ではbasic認証だったとしてもちゃんと運用を考えないといけないですけど。

Preview、Development、過去のProductionにはx-robots-tag: noindexが入る

Production(Current)以外にはHTTPヘッダーにx-robots-tag: noindexが入っています。Productionだったとしても、Currentがついていないものには入っています。

そのため、通常は検索エンジンにインデックスされることはありません(自分は心配性なので、検索エンジンがバグったら…とか考えてしまうのですけど)。

SEO的な対策はされているようです。

ただし、独自ドメインを反映するとpreviewでもx-robots-tag: noindexが入らない、という記事を見ました。

https://zenn.dev/keitakn/articles/nextjs-vercel-create-staging

現在(2022年2月時点)もそうなのかは試せていないのでわからないです。

vercelの各環境はどんな時に作られるのか

公式の説明は以下です。

https://vercel.com/docs/concepts/deployments/environments

GitHubとの連携のみの話ですが、各環境がどんな時に作られるかを以下にメモします。

Production

Productionはmainにプッシュしたりmainにマージすると反映されます。

これは多分ですが、Procductionに設定したブランチが更新されたら、ということなのではと思います。vercel上からmain以外のブランチ(developとか)を本番環境としてデプロイすることができるからです。

developを本番としてデプロイした後は、本番環境とされるブランチがdevelopに変わり、developをpreviewのつもりでプッシュしたらProcductionになっている、のかもしれませんが、これはまだ試せていません。

とりあえず、mainだけのGithubレポジトリをvercelでデプロイすると、そのままmainがProductionとしてデプロイされて公開されその後のProductionはmainになります。

デプロイは運用すれば何度もすることになるわけですが、その都度過去のProductionは履歴が残り続けます。

最新のProductionには(Current)とついてます。Currentがついているものが本番環境になります。メインのurlからアクセスできる場所がProduction(Current)です。

過去のProductionはそのまま残っていますが、メインのurlはcurrentになっているので、各Productionに固有に振られたurlからしかアクセスできなくなっています。

また、current以外にはx-robots-tag: noindexが追加されているようです。

Preview

PreviewはProduction用のブランチ以外にプッシュするとデプロイされて作成されます。

Gitを使う場合は、だいたいmainに反映する前の本番前ブランチ(developとか)や、その他の開発用のブランチを複数作るかと思いますが、それらをプッシュすると自動でデプロイされて作られるのがPreviewです。

これはこれで問題があって、特定のブランチだけをデプロイできるようにvercelで設定できるといいな~と思います。

回避策は以下の記事に載っていました。

https://zenn.dev/catnose99/articles/b37104fc7ef214

Development

Developmentはvercelのコマンドでvercel devとすることで作られます。

_middleware.tsを修正してbasic認証をかける

先ほどの例を少し変更して、ID・パスをBASIC_AUTH_USER BASIC_AUTH_PASSWORDで設定し、ENABLE_BASIC_AUTHによってbasic認証をかけない分岐処理を設定しています。

やっていることは単純で、ENABLE_BASIC_AUTHが設定されていない環境の場合はミドルウェアチェーンが継続されるだけです。

それ以外の場合は、basic認証が入ります。

_middleware.ts
import { NextRequest, NextResponse } from "next/server";

export function middleware(req: NextRequest) {
+  if (!process.env.ENABLE_BASIC_AUTH) {
+    return NextResponse.next();
+  }
  const basicAuth = req.headers.get("authorization");

  if (basicAuth) {
    const auth = basicAuth.split(" ")[1];
    const [user, pwd] = Buffer.from(auth, "base64").toString().split(":");

    if (
+      user === process.env.BASIC_AUTH_USER &&
+      pwd === process.env.BASIC_AUTH_PASSWORD
-      user === '4dmin' && pwd === 'testpwd123'
    ) {
      return NextResponse.next();
    }
  }

  return new Response("Auth required", {
    status: 401,
    headers: {
      "WWW-Authenticate": 'Basic realm="Secure Area"',
    },
  });
}

これでProduction以外の環境にはbasic認証がかかります。

この書き方が良いのかどうかはmidlewareが良くわかってないので、イマイチ判断が付きませんが。処理速度とか大丈夫なんでしょうかね。vercel上で試した時はサンプルページを上げているだけなので表示速度は全然変わらないのです。

このやり方では過去のProductionにはbasic認証はかからない

vercelのProductionはデプロイごとの履歴が残ります。何かあった時にすぐに戻す対応もできて便利です。

ですが、今回の例は、各環境ごとの変数設定のため、過去のProductionにはbasic認証はかかりません。そのため、urlをベタ打ちすれば外部からでも過去のProductionにアクセスできます。

過去のProductionは現在公開しているバージョンではないわけで、Previewをアクセス制限したいと思うのならば、過去のProductionも同等だよな~と、vercelを使っていて思いました。

ですが、過去のProductionにbasic認証をかけるmidlewareの書き方は思いつきませんでした。vercelに対する知識不足もあって、できるのかできないのかの判断もできませんでした。

運用で回避策を考えてみる

思いついた回避策は運用での対応になってしまいますが、過去のProductionは消してしまう、でしょうか。間違えてcurrentを消してしまうリスクがありますが。

過去のProductionを消してしまう場合、緊急で1つ前に戻したい時にどうするかが問題になりそうです。

ブランチのpreview環境データは残っているので、それを本番環境にデプロイすればよさそうな気がします。そんな機能がvercelにはあります。その際に、環境変数は本番のものが適用されるため、basic認証はかからず本番環境として機能します。

ただし、vercelの管理画面上では、developというブランチ名のデータがProductionになっているので、混乱しそうではあります。

試してないので良くわかりませんが、vercel上のcurrentをmainに修正するのを忘れて、developのブランチにプッシュしたら本番に上がっちゃった、なんてことが起きるんですかね?

あくまで緊急対応、ということで運用でカバーする感じでしょうか。

vercel上でなくて良ければ、Gitで特定のコミットまで戻すというのもありですかね。戻したmainをvercelで再度デプロイすれば、vercel上のProductionブランチは変更されずに、前回の環境に戻せそうです。

この辺りの運用は実験して試してみないとある程度安全なフローはつくれなさそうだなと思いました。Next.jsのサンプルで色々試してみようと思います。

何か他に方法はないものか

Next.jsのバージョン12のリリースノートを見た時に、middlewareのサンプルにbasic認証があったのでちょっとやってみたのですが、vercelのデプロイ環境がよくわからなくてあれこれやった時のメモがこの記事です。

サーバーを借りているならば、ステージング環境はサブドメインで切って、ipアドレスでアクセス制限とかで良いと思うのですが、vercelだとどうすれば良いのか…と、ふと思ってやってみたのです。

vercelでは有料機能にてパスワード管理ができるようですが、個人が気軽にちょっと試せる感じではないので何かないかなとbasic認証を触ってみました。

個人で勉強するぐらいならば、basic認証自体いらないでしょうし、企業として運用するのならば有料機能を使うでしょうし、わざわざpreview環境(と過去のProduction)だけbasic認証をするという運用はいらないのかな~と思います。これだけ長く色々書いておいてあれですが。

こういう機能に触ると、どうしても「運用フロー」を考えてしまうので、難しいな~と思います。特に公開周りの運用はミスすると大きな損失になりやすいですし。対策はしつつ、リスクとリターンを理解した上で運用するしかないとは思うのですけど。

正直vercelの使い方が良くわかっていないので、色々誤解しているかもしれません。Githubやvercelを上手く使って簡単にできる方法があるのかもしれません。

もっと良い方法がないかな~とは思っていますが、とりあえずの試行錯誤をメモとして残しておきたくて色々書いておきました。

今後も少しずつvercel上で実験してみたいと思います。

Discussion