以前、以下の投稿で簡易な認証機能を試してみました。しかし、以下は認証フローのイメージを掴むための簡易なサンプルでしたので機能は不十分でした。
今回は、Nuxt-Authというモジュールを使って認証機能を組み込むための方法について見ていきたいと思います。
環境
- macOS Catalina (intel cpu)
- nuxt 3.4.1
- next-auth 4.22.0
- @sidebase/nuxt-auth 0.5.0-rc.0
- Node.js 18.15.0
Nuxt-Authモジュール
Nuxt3にあるNuxt-Authモジュールは、NextAuth.jsというNext.jsアプリケーション用に書かれた認証ライブラリをベースにNuxt3用に提供されているモジュールになっています。nuxt/authという類似名のモジュールがありますが、こちらはNuxt公式でNuxt3にbuilt-inで認証機能を追加するためにNuxt Layersという機能を使って開発されているようですが、まだworking
ステータスとなっているようです(本記事執筆時点、まだNuxt2の頃のまま?)。その間は、本記事にあるNuxt-Authモジュールの利用を推奨する、となっています。
Nuxt-Authモジュールは非常に多くの認証プロバイダをサポートしています。NextAuth.jsのサイトを見るとそのサポートの多さが分かります。ユーザの方が使われているだろうサービスプロバイダで困ることはなさそうです。
Nuxt-Authを使ったサンプル
Nuxt-Authモジュールをインストールして認証機能を実装していきたいと思います。今回はGoogleとGitHubプロバイダを使用してみます。
インストール
モジュールのインストールをします。@sidebase/nuxt-auth
は、0.5.0-rc.0
の版となっているようです。
1 2 |
pnpm i -D @sidebase/nuxt-auth pnpm i -D next-auth@4.22.0 |
続いてnuxt.configでモジュールの設定をします。以下は、全てのページでデフォルトで認証を有効にする設定をしています。
1 2 3 4 5 6 |
export default defineNuxtConfig({ modules: ["@sidebase/nuxt-auth"], auth: { enableGlobalAppMiddleware: true, // サイト全体で認証を必要にする }, }); |
その他の設定は公式ドキュメントで参照できます。
参考 https://sidebase.io/nuxt-auth/configuration/nuxt-config
認証機能の作成
/api/auth/[...].ts
のCatch-all Routeにハンドラーの定義を書くようになっています。Catch-all Routeは、リクエストが配下のパスにマッチしない場合にデフォルトで呼ばれるルートです。
参考 https://nuxt.com/docs/guide/directory-structure/pages/#catch-all-route
providers
というプロパティに認証プロバイダを設定します。Googleプロバイダを設定した例は以下のようになります。
server/api/auth/[…].ts
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import GoogleProvider from "next-auth/providers/google"; import { NuxtAuthHandler } from "#auth"; export default NuxtAuthHandler({ providers: [ // @ts-expect-error GoogleProvider.default({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), ], }); |
providersの配列には複数のプロバイダの設定が可能です。GoogleやGitHubといった複数のプロバイダを設定するとそれらプロバイダを選択できる認証画面が表示されます。GitHubも追加してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import GithubProvider from 'next-auth/providers/github' import GoogleProvider from 'next-auth/providers/google' import { NuxtAuthHandler } from '#auth' export default NuxtAuthHandler({ providers: [ // @ts-expect-error GoogleProvider.default({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), // @ts-expect-error GithubProvider.default({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], }) |
アプリケーションを起動しhttp://localhost:3000にアクセスすると以下のような認証画面に飛ばされることを確認できます。
app.vue
1 2 3 4 5 |
<template> <div> <NuxtPage /> </div> </template> |
pages/index.vue
1 2 3 4 5 |
<template> <div> <slot /> </div> </template> |
GoogleまたはGitHubを選択できる認証画面です。
認証プロバイダの設定
GoogleとGitHubのプロバイダを登録して実際に設定してみます。callback URLは、デフォルトでは/api/auth/callback/:provider
となっています。
Google API ConsoleからクライアントID、シークレットを取得します。
プロジェクト作成等の詳細は割愛します。
GitHub
上記の登録を完了し、.env
ファイルにそれぞれプロバイダのクライアントIDとシークレットを設定しアプリケーションを起動します。アプリケーションの認証画面で認証プロバイダのリンクをクリックすると以下のような認証プロバイダの要求画面が表示されます。
GitHub | |
認証によるページ保護
2つの設定方法があります。1つはサイト全体で保護するグローバル、もう1つはページごとに保護するローカルです。
グローバルで保護
サイト全体で保護する場合は、モジュールのオプションで設定できます。個別のページで認証をかけたくない場合は、definePageMeta
で設定することができます。
先ほどの例の通りモジュールのオプションで設定します。
1 2 3 4 5 6 |
export default defineNuxtConfig({ modules: ["@sidebase/nuxt-auth"], auth: { enableGlobalAppMiddleware: true, // サイト全体で認証を必要 }, }); |
個別のページで認証を要求しない場合は以下のようにします。
1 2 3 |
<script setup lang="ts"> definePageMeta({ auth: false }) </script> |
ローカルで保護
definePageMeata
で設定します。middlewareによって保護されます。
1 2 3 |
<script setup lang="ts"> definePageMeta({ middleware: 'auth' }) </script> |
Guest Mode
Guest Modeを使うと、認証済みユーザは認証ページを開かずredirectページに遷移させるといったフローを構築できます。自分で認証済みか否かをチェックすることもできますが、ライブラリ側で制御してくれるのは嬉しいと思います。
1 2 3 4 5 6 7 8 |
<script setup lang="ts"> definePageMeta({ auth: { unauthenticatedOnly: true, // 認証済みでない場合は参照できる navigateAuthenticatedTo: '/profile', // 認証済みの場合に遷移させるページ、指定なしは'/' }, }) </script> |
カスタム認証ページ
デフォルトで認証画面が用意されているのですぐに始めることができますが、通常はWebサイトのデザインに合わせて画面デザインすることが多いと思います。そのため、個別の認証ページに飛ばすことができます。これはNuxtAhtuHandlerで設定します。
server/api/auth/[…].ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import GithubProvider from "next-auth/providers/github"; import GoogleProvider from "next-auth/providers/google"; import { NuxtAuthHandler } from "#auth"; export default NuxtAuthHandler({ pages: { signIn: "/login", // 用意したログインページに設定 }, providers: [ // @ts-expect-error GoogleProvider.default({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), // @ts-expect-error GithubProvider.default({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], }); |
pages/login.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<script setup lang="ts"> definePageMeta({ auth: { unauthenticatedOnly: true, navigateAuthenticatedTo: "/profile" }, }); const { signIn } = useAuth(); </script> <template> <div> <h2>Login</h2> <div> <p>Sign-In Options:</p> <button @click="signIn('google')">Google</button> <button @click="signIn('github')">Github</button> </div> </div> </template> |
pages/profile.vue
1 2 3 4 5 6 7 8 9 |
<script setup lang="ts"> const { signOut, data } = useAuth(); </script> <template> <div> <p>Logged In As:{{ data?.user?.email }}</p> <button @click="() => signOut()">Sign Out</button> </div> </template> |
確認してみましょう。
http://localhost:3000/ の認証が必要なページにアクセスします。
ログインページ
ログイン後の/profileページ
Sign Outすると再びログインページに戻ります。
JWTトークンの使用
sessionStrategyをjwtにするとJWTを使えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import GithubProvider from "next-auth/providers/github"; import GoogleProvider from "next-auth/providers/google"; import SequelizeAdapter from "@next-auth/sequelize-adapter"; export default NuxtAuthHandler({ pages: { signIn: "/login", }, session: { strategy: "jwt", // jwtにする }, providers: [ // @ts-expect-error GoogleProvider.default({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GPOOGLE_CLIENT_SECRET!, }), // @ts-expect-error GithubProvider.default({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], }); |
ログインして開発者コンソールでcookieに格納されているsession情報をのぞくと以下のようにJWTの値が入っています。
Credentialsプロバイダ+Sequelizeでのログイン処理
sequelize adapterを使うとデータベースと連携しアカウントやユーザ登録がされるようになります。
sequelize-adapterを使う方法を見ていきます。まずはモジュールをインストールします。データベースはSQLiteを使います。
1 |
pnpm i -D sequelize sqlite3 @next-auth/sequelize-adapter |
NuxtAuthHandlerを以下のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import GithubProvider from "next-auth/providers/github"; import GoogleProvider from "next-auth/providers/google"; import SequelizeAdapter from "@next-auth/sequelize-adapter"; import { NuxtAuthHandler } from "#auth"; import { Sequelize } from "sequelize"; // データベース設定 const sequelize = new Sequelize({ storage: "./test.sqlite", dialect: "sqlite", }); export default NuxtAuthHandler({ pages: { signIn: "/login", }, providers: [ // @ts-expect-error GoogleProvider.default({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GPOOGLE_CLIENT_SECRET!, }), // @ts-expect-error GithubProvider.default({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], // https://authjs.dev/reference/adapter/sequelize // @ts-expect-error adapter: SequelizeAdapter.default(sequelize), // adapterの設定 }); |
上記ではモデルの定義をしていません。SequelizeAdapterはモデルがない場合、内部にもつモデル定義からテーブル作成をしてくれます(ただしproduction環境ではない場合)。実際にはsequelizeのsync
関数が呼ばれています。production環境では通常マイグレーションする必要があります。
ではこの設定にしてログインしてみましょう。ログインすると以下のようにコンソールにテーブル作成のSQLが出力されるのを確認できます。
1 2 3 4 |
[19:37:33] Executing (default): CREATE TABLE IF NOT EXISTS accounts (id UUID PRIMARY KEY, type VARCHAR(255) NOT NULL, provider VARCHAR(255) NOT NULL, provider_account_id VARCHAR(255) NOT NULL, refresh_token VARCHAR(255), access_token VARCHAR(255), expires_at INTEGER, token_type VARCHAR(255), scope VARCHAR(255), id_token TEXT, session_state VARCHAR(255), user_id UUID REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE); [19:37:33] Executing (default): CREATE TABLE IF NOT EXISTS sessions (id UUID PRIMARY KEY, expires DATETIME NOT NULL, session_token VARCHAR(255) NOT NULL, user_id UUID REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (session_token)); [19:37:33] Executing (default): CREATE TABLE IF NOT EXISTS verification_tokens (token VARCHAR(255) PRIMARY KEY, identifier VARCHAR(255) NOT NULL, expires DATETIME NOT NULL); [19:37:33] Executing (default): CREATE TABLE IF NOT EXISTS users (id UUID PRIMARY KEY, name VARCHAR(255), email VARCHAR(255), email_verified DATETIME, image VARCHAR(255), UNIQUE (email)); |
ログイン後にテーブルaccounts、users、sessionsを見るとレコードが登録されているのが分かります。
accountsテーブル
usersテーブル
sessionsテーブル
CredentialsProviderの使用
CredentialsProviderを使うとusernameとpasswordのような入力を使って既存のシステムを使った認証ができるようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import GithubProvider from "next-auth/providers/github"; import GoogleProvider from "next-auth/providers/google"; import CredentialsProvider from "next-auth/providers/credentials"; import SequelizeAdapter from "@next-auth/sequelize-adapter"; import { NuxtAuthHandler } from "#auth"; import { Sequelize } from "sequelize"; const sequelize = new Sequelize({ storage: "./test.sqlite", dialect: "sqlite", }); // @ts-expect-error const adapter = SequelizeAdapter.default(sequelize); export default NuxtAuthHandler({ pages: { // signIn: "/login", }, session: { strategy: "jwt", }, providers: [ // @ts-expect-error GoogleProvider.default({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GPOOGLE_CLIENT_SECRET!, }), // @ts-expect-error GithubProvider.default({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), // @ts-expect-error CredentialsProvider.default({ name: "Credentials", credentials: { username: { label: "Username", type: "text", placeholder: "tarou" }, password: { label: "Password", type: "password" }, }, async authorize( credentials: Record<"username" | "password", string> | undefined, req: any, ) { // ここに処理を書く // 以下はユーザがいない場合に新規に作成するというシンプルなサンプルで試している let user = await adapter.getUserByEmail(credentials?.username); if (!user) { user = await adapter.createUser({ email: credentials?.username, }); } console.log(credentials); console.log(req); return user; }, }), ], // https://authjs.dev/reference/adapter/sequelize adapter, }); |
EmailProviderの使用
EmailProviderを使うと検証用のトークンを生成した後メール認証リクエストを送信、メール受信者が認証用のURLをクリックしてユーザ登録を完了するというフローを構築できます。認証URLをコンソールに出力したのが以下です。
まとめ
Nuxt-Authモジュールを使うとNuxt3で認証プロバイダを用いた認証機能をクイックに実装することができました。また、アダプタを使うことでデータベースとの連携も簡単に行うことができます。
Nuxt-Authはまだrc版ですが、サクッと導入できプロバイダやアダプタなど機能も豊富でとても使いやすいため、今後Nuxt3でアプリケーション構築していくのに使っていきたいと思いました。
参考リンク
- https://sidebase.io/nuxt-auth/getting-started
- https://next-auth.js.org/getting-started/example
- https://nuxt.com/modules/nuxt-auth
- https://github.com/nuxt-community/auth-module
- https://nuxt.com/docs/guide/going-further/layers
- https://zenn.dev/nrikiji/articles/d37393da5ae9bc
コメント