📌

【Next.js】middlewareでベーシック認証やってみる

2022/11/20に公開

概要

前回でNext.jsでMiddlewareについて勉強してみたらやってみたくなるのがベーシック認証。

https://zenn.dev/kiriyama/articles/b0d6f8b2362107

という事で今回はベーシック認証をやってみる。

最終目的

最終的には、トップページからAboutページに移動した場合にベーシック認証を求められる。
入力して合っていた場合は、Aboutページを表示して、間違っていた場合は、「Auth Required」と表示させる。

実装

実装にあたって、まずはプロジェクトを立ち上げる。
とりあえずtypescriptにしておきます。

npx create-next-app basic-auth-app --typescript

設置場所

前回、説明しているが、middlewareはルートディレクトリに設置している。

  • pages/api/auth.ts
  • pages/index.ts
  • pages/about.ts
  • middleware.ts
  • .env

環境変数(.env)ファイルはベーシック認証に必要なユーザー名とパスワードを記載しておく為のファイル。
.envmmiddleware.tsはルートディレクトリに設置しています。

.envファイル

環境変数(.env)のサポートはバージョン 9.4 以上の Next.js から組み込まれています。
まずは.envファイルです。適当に下記のようにしました。

USER=hogehoge
PASSWORD=fugafuga

呼び出すには以下のようにprocess.env.環境変数名となる。

process.env.USER
process.env.PASSWORD

middleware.ts

今度はmiddleware.tsについて。

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

//Aboutページだけにベーシック認証を設定
export const config={
    matcher:"/about"
}

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

    if(basicAuth){
        const authValue=basicAuth.split(" ")[1]
        const [user,pwd]=atob(authValue).split(":")
       
        if(user===process.env.USER && pwd === process.env.PASSWORD){
            return NextResponse.next()
        }
    }

    url.pathname="/api/auth"

    return NextResponse.rewrite(url)
}

auth.ts

pages/api/auth.ts
import type { NextApiRequest,NextApiResponse } from "next";
export default function handler(_:NextApiRequest,res:NextApiResponse){
    res.setHeader('WWW-authenticate','Basic realm="Secure Area"')
    res.statusCode=401
    res.end(`Auth Required`)
}

トップページとAboutページ

こちらについては、通常のページなのでシンプルにしています。

pages/index.tsx
import type { NextPage } from 'next'
import Link from 'next/link'

const Home: NextPage = () => {
  return (
    <div>
      <h1>トップページ</h1>
        <Link href="/about">Aboutページ</Link>
    </div>
  )
}
export default Home

Aboutページも同様です。

pages/about.tsx
import type { NextPage } from 'next'

const About:NextPage = () => {
  return (
    <div>
        <h1>About</h1>
    </div>
  );
};

export default About;

動作確認

上記のコードを書いて実行すると以下のようになる。

ベーシック認証を求められる画面

失敗した画面

成功したらAboutページに移動する

べーシンク認証の流れ

上記のコードの流れは以下のような処理になっている(はず)。

  1. トップページからAboutページへ移動
  2. Aboutページの前にmiddlewareを実行する。
  3. middlewareでhttpリクエストのヘッダを見て、Authorizationというヘッダーを確認
  4. 最初は設定されていない(null)ので、auth.tsを実行する
  5. auth.tsではWWW-AuthenticateにBasic realm="値"というヘッダがセットされた401レスポンスを受け取ったら、IDとPASSWORDを入力するダイアログを表示する。
  6. ユーザはIDとPASSWORDを入力する。
  7. ブラウザは入力された値をAuthrizationヘッダに設定したあと、再度middlewareを実行
  8. Authorizationヘッダを解析して正しいIDとパスワードが設定されているかを確認する。
  9. envファイルに設定されてる値と入力された値が一致したら、Aboutページへ移動

上記の流れを確認するためにmiddleware.tsauth.tsconsole.logで確認すると流れが分かりやすいかも。

middleware.ts
export function middleware(req:NextRequest){
    const basicAuth=req.headers.get('authorization')
    const url=req.nextUrl
    
    console.log("middlwware実行")
    console.log("authorization:"+basicAuth)
    
    if(basicAuth){
        const authValue=basicAuth.split(" ")[1]
        console.log("authValue")
        console.log(authValue)
	
        //ユーザー名とPWDを取り出す
        const [user,pwd]=atob(authValue).split(":")

        console.log(user,process.env.USER)
        console.log(pwd,process.env.PASSWORD)
    }
}
auth.ts
export default function handler(_:NextApiRequest,res:NextApiResponse){
    console.log("auth.ts")
}

それぞれconsole.logを実行してみて実際に流れをみると、以下のように出力されます。

最初にAboutページへ移動した時

ベーシック認証を求められて入力したが一致しなかった場合
一致しなかった場合は再度、auth.tsを実行している

ユーザー名とパスワードを入力して一致した場合
一致した場合は、NextResponse.next()でAboutページへ移動してるのでauth.tsは実行されず

まとめ

ベーシック認証をやってみたけど、割とこれはテンプレのような気がするな。
以下のサイトが参考になりました。ありがとうございます。

https://fwywd.com/tech/next-env
https://dabohaze.site/next-js-12-2-0-middleware-basic-authentication/
https://qiita.com/memomaruRey/items/eb33d9d6226adcab070a
https://bitstar.jp/blog/2016/01/21/12076/

Discussion