はじめに
前回まではデータベースとORMツールのセットアップを行なった上でNext.jsのサーバーアクションからデータベースへの登録処理を行いました。
今回はリクエストがきたセッションに対してログイン済み、未ログインどちらなのかを判定しつつ、それぞれの状態でアクセスできる画面の設定を行います。今回の内容は比較的ボリュームのある内容となっているため、実際のログイン処理はこの次の記事でご紹介していきます。
認証システムの設計
非常に小さなシステムではありますが認証の仕組みについて簡単に設計していこうと思います。
メールアドレスとパスワードを使ってログインするというところまでは既にご存知かと思いますが、ログイン済み、未ログインの場合にそれぞれ画面へのアクセスがどこまでできるかという問題です。
最初の図は基本的なユーザーの利用の流れとなります。トップ画面に来たユーザーは、アカウント登録画面でアカウントを登録し、ログイン画面でログイン処理を行いログインできたら設定画面(一般的なウェブサイトでいうマイページの役割。今回はユーザー情報が表示される画面)に遷移します。
(設定画面についてはこれから作成していきます。)
既にアカウントを登録済みの場合は、トップ画面からログイン画面に遷移しログイン処理を行います。
設定画面はユーザー情報が表示される画面となり、未ログイン状態でアクセスされると困るのでアクセス拒否するよう設定します。
またログイン済みのユーザーがアカウント登録画面やログイン画面にアクセスしても特にすることはないため、設定画面にリダイレクトするよう設定します。
このようなイメージでログイン済み、未ログインの場合の画面アクセスを設定して行きます。
ログイン済みユーザーにだけ見せたい画面などがあると思いますが、それをどのように実現するかを解説していくのが今回の記事です。
今回のゴール
この記事の中ではログイン処理は実装しません。まずは画面に対するアクセス制御を設定しつつ、未ログインのユーザーがアクセスできない画面に遷移しようとするとログイン画面に戻されるという処理を実装します。
(ログイン処理についてはこの次の記事で解説して行きます)
Auth.jsについて
Auth.jsは、オープンソースの認証ライブラリです。Next.jsアプリケーションに簡単に認証機能を追加するためのライブラリであり、対応プロバイダーはOAuth (Google, Facebook, Twitterなど)、Email、Credentials、任意のカスタムプロバイダーに対応しています。セッション管理はクッキーを使用したセッション管理が標準で提供され、ユーザーの認証状態を簡単に管理できます。
今回のケースだとブラウザからのリクエストがあったときにそのユーザーがログイン済み、未ログインどちらなのかを判定する役目を担っています。
こちらのページに対応するフレームワークが記載されています。
現在のところNext.js、Astro、Express、Nuxt、Remix、SvelteKitなど様々なフレームワークに対応しております。
またGoogleやGithub、Twitterなどの80以上のサービスと連携できるプロバイダーを提供されているため、これらを使用した認証の仕組みを素早く作ることも可能です。
Next.jsのミドルウェアについて
ミドルウェアをアプリケーションに統合することで、パフォーマンス、セキュリティ、ユーザーエクスペリエンスを大幅に向上させることができます。
ミドルウェアが特に効果を発揮する一般的なシナリオには、次のようなものがあります。
- 認証と認可: 特定のページやAPIルートへのアクセスを許可する前に、ユーザーの身元を確認し、セッションクッキーをチェックする。
- サーバーサイドリダイレクト: 特定の条件(ロケール、ユーザーの役割など)に基づいて、サーバーレベルでユーザーをリダイレクトします。
- パスの書き換え: リクエストプロパティに基づいてAPIルートやページへのパスを動的に書き換えることで、A/Bテスト、機能ロールアウト、レガシーパスをサポートします。
- ボット検知:ボットトラフィックを検知してブロックすることで、リソースを保護します: ボットトラフィックを検出してブロックすることで、リソースを保護します。
- ロギングと分析: ページまたはAPIで処理する前に、リクエストデータを取得して分析します。
今回のケースだとアクセスに来たユーザーがAuth.jsによってログイン済み、未ログインを判定したのちに、どの画面にアクセスしに来たかを見て、適切な画面に振り分ける役目を担っています。
例えば、未ログインのユーザーが設定画面にアクセスしようとしているので、ログイン画面にリダイレクトするというのをミドルウェアで実装して行きます。
Auth.jsのセットアップ
Auth.jsの公式ドキュメントに基本的には合わせて実施して行きます。
Auth.jsライブラリをインストールします。
$ npm install next-auth@beta
次に環境変数AUTH_SECRET
を.env
に登録します。
この環境変数はライブラリがトークンと電子メール検証ハッシュを暗号化するために使用するためのものとなります。
下記のコマンドを実行するとランダムな文字列が返ってきます。
openssl rand -base64 33
このような感じです。
$ openssl rand -base64 33
gw7e/vp3ogJ9/8j3YQVhb+jCjF7aJpaacn20uzqm831i
例えば、上記の文字列が返ってきたら下記のように.env
で定義します。
AUTH_SECRET="gw7e/vp3ogJ9/8j3YQVhb+jCjF7aJpaacn20uzqm831i"
Auth.jsの動作確認
プロジェクトのトップにauth.ts
ファイルを作成します。
import NextAuth from 'next-auth'
import github from 'next-auth/providers/github'
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [github],
})
動作確認用としてgithub
認証用のプロバイダーを設定しておきます。
次にNext.jsのAPIルートを設定します。
app/api/auth/[[...nextauth]]
ディレクトリはいかにroute.ts
ファイルを作成します。
import { handlers } from '@/auth'
export const { GET, POST } = handlers
http://localhost:3000/api/auth/providers
にアクセスするとAuth.jsで設定しているプロバイダーの一覧が返ってきます。
Next.jsのミドルウェアの動作確認
プロジェクトのトップにmiddleware.ts
ファイルを作成します。
まずは動作確認用として下記のようなコードを書きます。
import type { NextRequest } from 'next/server'
export function middleware(req: NextRequest) {
console.log('middleware', req.nextUrl.pathname)
return
}
export const config = {
matcher: '/auth/:path*',
}
コードの解説をします。
import type { NextRequest } from 'next/server'
export function middleware(req: NextRequest) {
console.log('middleware', req.nextUrl.pathname)
return
}
ミドルウェアがどのような処理をするかを定義しています。
実際の処理内容としてはリクエストを受け取り、そのリクエストのURLをコンソールログで表示するだけとなります。
export const config = {
matcher: '/auth/:path*',
}
matcher
を使ってどのパスにアクセスがあった際にミドルウェアを動かすかを設定します。
今回は/auth
で始まるパスにアクセスがあった場合にミドルウェアが動きます。
なので2つの処理を合わせると、
`/auth`で始まるパスにアクセスがあった場合に、サーバーサイドでコンソールログにリクエストのURLを表示する
ということになります。
実際にhttp://localhost:3000/auth/register
にアクセスしてみましょう。
下記のようにログが表示されればOKです。
$ npm run dev
> tutorial-nextjs-14-auth@0.1.0 dev
> next dev
▲ Next.js 14.0.4
- Local: http://localhost:3000
- Environments: .env.local, .env
✓ Ready in 1766ms
✓ Compiled in 252ms (232 modules)
✓ Compiled /middleware in 201ms (63 modules)
middleware /auth/register
また、トップ画面http://localhost:3000
にアクセスしても特にサーバーサイドでメッセージが表示されないことも確認できるかと思います。
未ログイン時のアクセス制御の実装
Auth.jsとNext.jsのミドルウェアの動作確認ができましたので実際の実装に入っていきます。
エッジランタイムについて
実装の前にエッジランタイムについて軽く触れておきます。
詳細は下記のドキュメントをご参照ください。
Edge here is borrowed from the network engineering folks and refers to a compute node (i.e. server) that is located on the edge of a network, i.e. closer to the users.
ここでいうエッジとは、ネットワークエンジニアリングの人たちから借りたもので、ネットワークのエッジ、つまりユーザーにより近い場所にあるコンピュートノード(つまりサーバー)のことである。
So when we say edge runtimes, we mean a server-side JavaScript runtime that is not Node.js and is optimized to run on these edge compute nodes (servers). That generally means that the code is executing closer to your users on lower power hardware that is optimized for other things like quick startup times, low memory usage, etc.
つまり、エッジ・ランタイムとは、Node.jsではないサーバーサイドのJavaScriptランタイムを意味し、エッジ・コンピュート・ノード(サーバー)上で実行するように最適化されている。 これは一般的に、起動時間の速さやメモリ使用量の少なさなど、他のことに最適化された低消費電力のハードウェア上で、よりユーザーの近くでコードが実行されることを意味する。
ただしデータベースアクセスについては現状エッジ・ランタイム上で動かすことができないケースが多いため、今回のようにログイン時にデータベースにアクセするような場合を想定するとエッジ・ランタイムでは動かさないように設定していく必要があります。
エッジランタイムで動かす処理とそうでない処理を分割していきます。
プロジェクトトップにauth.config.ts
ファイルを作成します。
このファイルがエッジランタイムで動くものを定義することとなります。
import type { NextAuthConfig } from "next-auth";
import github from "next-auth/providers/github";
export default { providers: [github] } satisfies NextAuthConfig;
次にAuth.jsからPrismaを使うためのライブラリをインストールします。
$ npm install @auth/prisma-adapter
先ほど動作確認で使用したauth.ts
を以下に書き換えます。
+ import { PrismaAdapter } from "@auth/prisma-adapter"
import NextAuth from "next-auth";
+ import authConfig from "./auth.config";
+ import db from "./lib/db";
export const { handlers, signIn, signOut, auth } = NextAuth({
- providers: [github],
+ adapter: PrismaAdapter(db),
+ session: { strategy: "jwt" },
+ ...authConfig,
});
セッション管理をJWTで行うこととし、またGithubプロバイダーをauth.config.ts
で定義しましたのでこちらからは消しておきます。
このように2つのファイルに役割を分割することでNext.jsのミドルウェアでauth.config.ts
を使用できるようになります。
ルートの設定
認証システムの設計のセクションでルートによって異なる挙動となるよう設計しましたが、幾つかの処理で使われるためルートの設定をまとめておきます。
プロジェクトのトップでroute.ts
ファイルを作成します。
/**
* 公開ページのURLを格納する配列
* これらのページについては認証不要でアクセス可能
*/
export const publicRoutes: string[] = ["/"];
/**
* 認証用ページのURLを格納する配列
* これらのページについてはログイン済みの場合、設定ページにリダイレクトさせる
*/
export const authRoutes: string[] = ["/auth/login", "/auth/register"];
export const apiAuthPrefix = "/api/auth";
export const DEFAULT_LOGIN_REDIRECT = "/settings";
ミドルウェアの設定
上記のroute.ts
を用いてNext.jsのミドルウェアを下記のように設定します。
import NextAuth from 'next-auth'
import authConfig from './auth.config'
import {
apiAuthPrefix,
authRoutes,
DEFAULT_LOGIN_REDIRECT,
publicRoutes,
} from './route'
const { auth } = NextAuth(authConfig)
export default auth((req) => {
const { nextUrl } = req
const isLoggedIn = !!req.auth
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix)
const isPublicRoute = publicRoutes.includes(nextUrl.pathname)
const isAuthRoute = authRoutes.includes(nextUrl.pathname)
if (isApiAuthRoute) {
return
}
if (isAuthRoute) {
if (isLoggedIn) {
return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl))
}
return
}
if (!isLoggedIn && !isPublicRoute) {
return Response.redirect(new URL('/auth/login', nextUrl))
}
return
})
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
}
コードの解説をしていきます。
import authConfig from './auth.config'
(※一部省略)
const { auth } = NextAuth(authConfig)
auth.config.ts
ファイルを呼び出しミドルウェアでAuth.jsが使えるように定義しています。
const { nextUrl } = req
const isLoggedIn = !!req.auth
リクエストからログイン済み、未ログインの判定をしています。Auth.jsを使うとこのように簡単にログイン状態を確認することができます。
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix)
const isPublicRoute = publicRoutes.includes(nextUrl.pathname)
const isAuthRoute = authRoutes.includes(nextUrl.pathname)
同じくリクエストからどのリソースにアクセスするのかを判定しています。
if (isApiAuthRoute) {
return
}
if (isAuthRoute) {
if (isLoggedIn) {
return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl))
}
return
}
if (!isLoggedIn && !isPublicRoute) {
return Response.redirect(new URL('/auth/login', nextUrl))
}
return
- isApiAuthRoute(
/api/auth
)へのリクエストであれば特に何もしません。 - isAuthRoute(
['/auth/login', '/auth/register']
)へのアクセスで、ログイン済みのセッションであれば、DEFAULT_LOGIN_REDIRECT
(/settings
)へリダイレクトさせます。また未ログインの場合であれば特に何もしません。 - 未ログインでisPublicRoute(
/
)以外のアクセスの場合は、ログイン画面にリダイレクトさせます。
これでアクセス制御の実装は完了しました。
設定画面の準備
最後に動作確認用として設定画面を用意します。
/app/(protected)
配下にlayout.tsx
ファイルを作成します。
const ProtectedLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div className="flex h-full items-center justify-center bg-gradient-to-br from-sky-100 to-blue-300">
{children}
</div>
)
}
export default ProtectedLayout
次に、/app/(protected)/page
配下にlayout.tsx
ファイルを作成します。
const SettingsPage = () => {
return <div>設定画面</div>
}
export default SettingsPage
動作確認
現在は未ログイン状態なのでhttp://localhost:3000/settings
にアクセスするとログイン画面にリダイレクトされます。
これだけだとイメージつきにくいかと思いますので、ミドルウェアでリダイレクトしている処理をコメントアウトしてどのような挙動になるか確認してみます。
if (isAuthRoute) {
if (isLoggedIn) {
return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl))
}
return
}
- if (!isLoggedIn && !isPublicRoute) {
- return Response.redirect(new URL('/auth/login', nextUrl))
- }
+ // if (!isLoggedIn && !isPublicRoute) {
+ // return Response.redirect(new URL('/auth/login', nextUrl))
+ // }
return
})
再びhttp://localhost:3000/settings
にアクセスし、設定画面が表示されたらOKです。動作確認できましたらミドルウェアのコメントアウトは戻しておいてください。
設定画面がJSONをそのまま表示しており見づらい状態となっていますが後半の章でJSONで返す項目を増やしつつ、見やすいUIに作り替えていく予定です。
さいごに
今回はリクエストがきたセッションに対してログイン済み、未ログインどちらなのかを判定しつつ、それぞれの状態でアクセスできる画面の設定を行いました。
次回は実際のログイン処理を行う画面を作成し、ログイン済みのセッションがアクセス可能な画面にアクセスできることを確認していきます。
次の記事はこちら
関連する技術ブログ
Next.jsとAuth.jsで認証機能を実装するチュートリアル
Next.jsでアプリケーションを作る時に必要となる認証機能をどのように実装するかをご紹介する記事となります。アカウント登録から始まり、ログイン、ログアウト、ページごとのアクセス制御、OAuth、二要素認証、パスワードリセットなど認証に関連する様々な機能をコードベースでご紹介します。
shinagawa-web.com
Next.jsでログイン画面を作ってメールアドレス/パスワードでログインできるようにする
Next.jsとAuth.jsを活用して、メールアドレスとパスワードによるログイン機能を実装する方法を解説します。Zodによるバリデーション、データベースアクセス、セッション管理、エラーハンドリングなどの実装手順を詳しく紹介。設定画面へのリダイレクトやログアウト処理もカバーしています。
shinagawa-web.com
Webアクセシビリティの完全ガイド:Lighthouse / axe による自動テスト、WCAG基準策定、キーボード操作・スクリーンリーダー対応まで
Webアクセシビリティの課題を解決するための包括的なガイド。Lighthouse / axe を活用した自動テストの設定、WCAGガイドラインに基づく評価基準の整備、キーボード操作やスクリーンリーダー対応の改善、カラーコントラストの最適化、ARIAランドマークの導入、フォームやモーダルの操作性向上まで詳しく解説。定期的なアクセシビリティレポートを活用し、継続的な改善を実現する方法も紹介します。
shinagawa-web.com
チャットアプリ(画像・PDF送信、ビデオ通話機能付き)
お客様固有の要件を除き一般的なチャットアプリに求められる最低限の機能を実装しデモアプリとしてご紹介いたします。
shinagawa-web.com
React × Tailwind CSS × Emotionで実践するコンポーネント設計ガイド:デザインシステム・状態管理・再利用性の最適解
React、Tailwind CSS、Emotion、Storybook、Figma、Next.jsを活用したコンポーネント設計のベストプラクティスを紹介。デザインシステムに基づく命名規則、適切な状態管理、再利用性を高める抽象化、アクセシビリティ対応、スタイルガイドラインの整備、テーマ設定、バージョン管理、ドキュメント作成まで、モダンフロントエンド開発に欠かせない知識を徹底解説します。
shinagawa-web.com
Chakra UI・ShadCN・Material UIを活用したデザインシステムの構築と運用
デザインシステムの要件定義から適用ガイドラインの策定、UIコンポーネントの設計・実装、レスポンシブ対応、アクセシビリティ強化まで、幅広い観点で解説。Chakra UI・ShadCN・Material UIなどのUIフレームワークを活用しながら、カラーパレットやタイポグラフィの統一、コードスタイルの整備、開発者・デザイナー向けのドキュメント作成、プロトタイピング、ユーザビリティ向上のためのテスト戦略までを包括的に取り上げます。
shinagawa-web.com
管理ダッシュボード機能(グラフ表示、データ取り込み)
一般的な家計簿アプリとして求められる最低限の機能を実装しデモアプリとしてご紹介いたします。
shinagawa-web.com
Mock Service Worker (MSW) を使ったAPIモックとテストの効率化
MSW(Mock Service Worker)を使用して、フロントエンド開発やテスト環境でのAPIモックを効率的に行う方法を解説します。Mock Service Workerの基本的な使い方から、Jestテストでの活用方法、さらにテストを簡単にするための設定手順を紹介します。
shinagawa-web.com
弊社の技術支援サービス
無駄なコストを削減し、投資対効果を最大化する
クラウド費用の高騰、不要なSaaSの乱立、開発工数の増加――これらの課題に悩んでいませんか?本サービスでは、クラウドコストの最適化、開発効率向上、技術選定の最適化 を通じて、単なるコスト削減ではなく、ROIを最大化する最適解 をご提案します。
shinagawa-web.com
最新技術の導入・検証を支援するPoCサービス
Remix、React Server Components、TypeScript移行、クラウドサービス比較、マイクロサービス、サーバーレス、デザインシステムなど、最新技術のPoC(概念実証)を通じて、最適な技術選定と導入を支援します。貴社の開発課題に合わせた検証・実装で、ビジネスの成長を加速させます。
shinagawa-web.com
開発生産性を最大化するための支援サービス
開発チームの生産性向上、コードの品質管理、インフラの最適化まで、様々な側面からサポートします。コードベースのリファクタリングから、テスト自動化、オンボーディング強化まで、プロジェクトの成功に必要なすべての支援を提供。御社の開発現場が効率的に機能するように、技術的な障害を取り除き、スムーズな開発を実現します。
shinagawa-web.com
開発品質向上支援 – 効率的で安定したプロダクトを実現
フロントエンドからバックエンド、データベースまで、開発プロセス全体を最適化し、安定したプロダクト作りをサポートします。コードレビューの仕組み、型定義の強化、E2Eテスト環境の構築など、開発の各ステップにおけるベストプラクティスを導入することで、より効率的でバグの少ない、そしてユーザー満足度の高いサービス提供を支援します。
shinagawa-web.com
Webアプリのセキュリティ強化支援
Webアプリの脆弱性対策からインフラのセキュリティ強化まで、包括的なセキュリティ支援を提供。OWASP Top 10対策、JWT認証の最適化、APIのアクセス制御、依存パッケージの監査、セキュアコーディングの標準化など、実践的なアプローチで開発現場の安全性を向上させます。
shinagawa-web.com
目次
お問い合わせ