Nuxt3：Nuxt-Authで認証機能作ってみた

# はじめに
Nuxtのインストールやプロジェクトの作成方法、詳しい概念については説明しません。
本記事はNuxt3での認証機能の一例の紹介になります。
TypeScript初心者なのでコードが汚いのは許してください！

# 本記事の目的
Nuxt3で認証機能を実装したときのメモです。
[こちら](https://debug-life.net/entry/4932#toc6 "Nuxt3でNuxt-Authモジュールを使って認証処理をつくる[1]")のサイトがとても参考になりました。
上記参考サイトではフロント部分の記述が少な目だったので、
本記事ではフロント部分も情報として残せればと思います。

# Nuxt-Authとは
[Nuxt-Auth](https://nuxt.com/modules/nuxt-auth)モジュールは、NextAuth.jsというNext.jsアプリケーション用に書かれた認証ライブラリをベースにNuxt3用に提供されているモジュールとなっている。
Nuxt-Authモジュールは非常に多くの認証プロバイダをサポートしている。
公式の[サンプルページ](https://nuxt-auth-example.sidebase.io/)　※本記事とはモジュールのバージョンが違うので注意



# 実装
環境は次の通りです。
本記事の方法でやる場合、nuxt-auth と @sidebase/nuxt-auth はnpmなどでインストール必須。

- windows11
- node: 16.17.0
- nuxt: 3.3.2
- next-auth: 4.22.0
- @sidebase/nuxt-auth: 0.5.0
- vuetify: 3.1.4

## 必須ファイル
プロバイダーや自作認証ページの設定には `server/api/authに[...].ts` を作成する必要があります。
これは/api/auth/ 以下のアクセスをキャッチオールするためです。
その他デフォルトのログインページを活用しない場合は各々用意する必要があります。

## 認証プロバイダ
Nuxt-Authモジュールは非常に多くの認証プロバイダをサポート。
OAuth (Github, Google, Twitter, Azure, ...)

### server
カスタムページからログインをするには `NuxtAuthHandler`に`pages`を設定。
`signIn`で指定したページがログインページとして認識されるようになります。
何も設定をしないとデフォルトのログインページを使うことになります。

`next-auth/providers`から使用したいプロバイダーをimportし、
`providers`に使用したいプロバイダーを設定すればOAuthを用いたログインが可能になります。


```server/api/authに[...].ts
import GithubProvider from "next-auth/providers/github"
import CredentialsProvider from 'next-auth/providers/credentials'
import { NuxtAuthHandler } from "#auth";

export default NuxtAuthHandler({
	pages: {
		signIn: "/login", // 用意したログインページに設定
	},
    providers: [
        // Github
        GithubProvider.default({
            clientId: process.env.GITHUB_CLIENT_ID!,
            clientSecret: process.env.GITHUB_CLIENT_SECRET!,
        }),
    ],
})
```

### front
簡易的に書くとこのような形になります。
`signIn()`を`useAuth`から呼び出します。
`v-btn`に`@click="signIn('provider')"`を指定してあげると、
指定したプロバイダーのOAuthページに飛びます。
（カスタムページにも書きますが、scriptタグに関数を作成しそこでsignInメソッドを呼ぶこともできます）

```login.vue
<template>
    <v-btn @click="signIn('github')" type="submit" >Githubでサインイン</v-btn>
</template>

<script lang="ts" setup>
    const { status, data, signIn } = useAuth()
</script>

<style scoped>
</style>
```

## カスタムページ
本記事のメインです。
多くの場合は自前のログインページを使いたいのではないでしょうか。
そんな人のためにNuxt-Authは個別の認証ページでログイン処理が作れます。
<img align="center" width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691844/af72a32b-5a7a-3511-f872-80356e346561.gif" />



### server
`CredentialsProvider.default`の中に自作のログイン処理を書いていきます。
必要なのは`async authorize(credentials: { email: string, password: string })`
credentials にはログインページから送られてくる入力内容を定義します。

`authorize`メソッドの中には return で null かオブジェクトを返します。
オブジェクトを返却する場合形式は限定され、コード上のオブジェクト形式だけです。
何かを返却する場合は認証に成功し、null を返却する場合は認証に失敗した状態になります。

```server/api/authに[...].ts
import { NuxtAuthHandler } from "#auth";
import bcrypt from "bcrypt";
import { sql } from '@vercel/postgres';

export default NuxtAuthHandler({
	pages: {
		signIn: "/login", // 用意したログインページに設定
	},
    providers: [
        CredentialsProvider.default({
			name: 'Credentials',
//			credentials: { 
//				デフォルトのログインページを利用する場合はここに設定を記述
//              詳しくは参考文献
//			},
			async authorize(credentials: { email: string, password: string }) 
            {
				const dbUserId:string = await getEmailUser(credentials.email);
				if(dbUserId === "No acount")
				{		
					return null;
				}

				const correctPassword:boolean = await checkPassword(dbUserId, credentials.password);
				if(correctPassword)
				{
					return {
                        name: "name : sample",
                        email: credentials.email,
                        image: "url"
                    }
				}
				else 
                {
					return null	
				}
        	}
        })
    ],
})
```



### front
ログイン用のフォームなどです、デザインの細かい設定はいくつか消してます。
```login.vue
<template>
	<div class="align-center justify-center" style="height: 100vh">
		<v-sheet width="400" class="mx-auto">
			<v-form fast-fail @submit.prevent="loginMailPass">
				<v-alert type="warning" density="compact" 
                    v-if="showAlert === true"
					id="error-email-required" aria-live="assertive"
				>
					メールアドレスとパスワードを確認してください！
				</v-alert>
				<v-text-field  v-model="email" label="User Mail"></v-text-field>
				<v-text-field  v-model="password" label="Password"></v-text-field>
				<v-btn type="submit" v-if="status === 'unauthenticated'">サインイン</v-btn>	
			</v-form>
		</v-sheet>
	</div>
</template>

<script lang="ts" setup>
definePageMeta({
	auth: {
		unauthenticatedOnly: true, // 認証済みでない場合は参照できる
		navigateAuthenticatedTo: '/', // 認証済みの場合に遷移させるページ、指定なしは'/'
	},
})

import { ref } from "vue";
const { status, data, signIn } = useAuth()
const email = ref("");
const password = ref("");
let showAlert = ref(false);

const loginMailPass = async () => {
	const result = await signIn('credentials', { 
        email: email.value, password: password.value, 
        redirect: false, callbackUrl: "/" 
    });

	if(status.value === 'unauthenticated')
	{
		showAlert.value = true
	}
	else if(status.value === 'authenticated')
	{
		showAlert.value = false;
		navigateTo('/') // redirect:falseで認証後の遷移ができなかったので書いてます
	}
}
</script>

<style scoped>
</style>
```

`definePageMeta`で認証済みユーザーのサイトアクセスに関する設定ができます。
認証済みユーザがサイトにアクセスした際にログインページに遷移するかを`unauthenticatedOnly`で設定し、認証済みの場合は遷移する必要がないので遷移先を`navigateAuthenticatedTo`で設定します。
その他で説明する`enableGlobalAppMiddleware`がtrueに設定されていないと機能しません。

```ts
definePageMeta({
	auth: {
		unauthenticatedOnly: true, // 認証済みでない場合は参照できる
		navigateAuthenticatedTo: '/', // 認証済みの場合に遷移させるページ、指定なしは'/'
	},
})
```

フォームの入力を渡したいのでscriptタグに自作のメソッドを定義してそこでsignInメソッドを呼びます。
フォームの値はオブジェクトの中にいれその他リダイレクトをするか、コールバック先のURLなどが設定できます。
当たり前ですがredirectがtrueになってないとcallbackで指定したパスに飛びませんので注意が必要です。


```ts
await signIn('credentials', { email: email.value, password: password.value, redirect: false, callbackUrl: "/" });
```

signInメソッドの戻り値は次の通りで認証の状態が返却されるわけではありません。
signInメソッドが動いたかの情報が返却されるイメージです。
```ts
// console.log(result);
error: null // エラーが出た時　error: CredentialsSignin
ok: true 
status: 200
url: "http://localhost:3000/"
```

認証情報は`status`と`data`に入れられてます。
statusの中身は unauthenticated か authenticated です。
dataの中身は`server/api/auth[...].ts/authorize()`でreturnした中身が格納されています。


# その他
ログイン処理をするタイミングについてです。
`nuxt.config.ts`にauthを記述し`enableGlobalAppMiddleware`をtrueにすると
サイトに初めてアクセスした段階でログインページに飛ばされます。
```nuxt.config.ts
auth: {
    enableGlobalAppMiddleware: false, // trueの場合は最初にログインページを表示
}
```

# まとめ
Nuxt-Authで認証機能作ってみました。
間違ってたら教えてください。


# 参考文献
1. [Nuxt3でNuxt-Authモジュールを使って認証処理をつくる](https://debug-life.net/entry/4932#toc6 "Nuxt3でNuxt-Authモジュールを使って認証処理をつくる[1]")
1. [Nuxt-Auth公式](https://nuxt.com/modules/nuxt-auth)
2. [公式サンプルページ](https://nuxt-auth-example.sidebase.io/)