くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Nuxt Auth Utils入門(Google認証編)

この記事を読んで使ってみたいなぁと思い、はや数ヶ月
ちょっと試してみようと、いろいろ調べてみたときの備忘録(*´ω`*)

Nuxt Auth Utilsとは

https://github.com/atinux/nuxt-auth-utils

  • 認証周りのライブラリ
  • クライアントサイド、サーバサイドを含めSSRに対応
  • 20以上OAuthプロバイダーに対応
  • nuxt buildのみ、nuxt generateは非対応

Nuxt Auth Utilsが提供しているもの

  • ユーザセッション周りのServer UtilsやComposable&Component
    • Server Utils(setUserSession()、etc..) ... 保存/取得クリアなどのセッション管理系
    • Composable(useUserSession()) ... 現在のログインユーザ関連の情報
    • Component(<AuthState>) ... loggedInclearなどを受け取れるコンポーネント
  • OAuth関連の処理を実装したEvent Handlers(20種類以上のプロバイダーに対応)
  • Password関連のUtils ... hash化やverifyメソッド

などなど。認証関連に必要なものが揃っている感じ

とりあえず試してみる

とりあえず、ミニマムな超シンプル版

ディレクトリ構成

こんな感じのディレクトリ構成の例

./
├── app
│   ├── middleware
│   │   └── auth.ts
│   └── pages
│       ├── home.vue
│       └── index.vue
├── server
│   └── api
│       └── auth
│           └── google.get.ts
└── nuxt.config.ts

ざっくりURLはこんな感じで、3つ用意

  • http://localhost:3000/ ... ログイン画面
  • http://localhost:3000/home ... ログイン後の画面
  • http://localhost:3000/api/auth/google ... Google認証用のAPI

前準備

Google認証に必要なクライアントIDとクライアントシークレットを準備

GCPの「APIとサービス」>「認証情報」> 「OAuth 2.0 クライアントID」から作成
Firebase AuthenticationでGoogle認証を有効してもOK。Firebaseが作ってくれる

クライアントIDを作成したら、以下も設定しておく
反映に少し時間がかかるので注意

nuxt-auth-utilsのインストールと設定

とりあえず、インストール

$ pnpm dlx nuxi@latest init google-auth-sample
$ pnpm i -D nuxt-auth-utils

nuxt.config.tsのmoduleに追加

export default defineNuxtConfig({
  compatibilityDate: '2024-11-01',
  
  // moduleに追加
  modules: ['nuxt-auth-utils'],
});

一旦、ここで起動

$ pnpm dev

起動すると、.envが生成される

# .env
NUXT_SESSION_PASSWORD=password-with-at-least-32-characters

ここに、前準備で用意したクライアントIDとクライアントシークレットを追加する

  # .env
  NUXT_SESSION_PASSWORD=password-with-at-least-32-characters
+ NUXT_OAUTH_GOOGLE_CLIENT_ID=<YOUR_CLIENT_ID>
+ NUXT_OAUTH_GOOGLE_CLIENT_SECRET=<YOUR_CLIENT_SECRET>

これで準備はOK

APIを作る

次にAPIを作っていく

Google認証用のハンドラーの、
defineOAuthGoogleEventHandlerが用意されてるので、
これだけでOK

// server/api/auth/google.get.ts
export default defineOAuthGoogleEventHandler({
  // 認証が成功したときの処理
  async onSuccess(event, { user }) {
    // ユーザセッションをCookieに保存
    await setUserSession(event, { user });
    
    // ログイン後画面にリダイレクト
    return sendRedirect(event, '/home');
  }
});

ログイン画面を作る

ログイン画面はこれだけ。作ったAPIを開くだけ

<!-- app/pages/index.vue -->
<template>
  <div>
    <div>ログイン画面</div>
    <a href="/api/auth/google">Googleログイン</a>
  </div>
</template>

※説明用に<a>を使っているけど、よくない
<button>などにするのがいい

認証用のmiddlewareを作る

認証チェックのmiddlewareをシンプル

// app/middleware/auth.ts
export default defineNuxtRouteMiddleware(() => {
  // 用意されたComposablesでログイン状態を取得
  const { loggedIn } = useUserSession();
  
  // 未ログインなら、ログイン画面にリダイレクト
  if (!loggedIn.value) return navigateTo('/');
});

ログイン後画面を作る

ログイン後のホーム画面もシンプルにこんな感じ
認証が必須なので、middleware: 'auth'を設定

<!-- app/pages/home.vue -->
<template>
  <div>
    <div>ホーム画面</div>

    <!-- ログイン情報の確認用 -->
    <div>user</div>
    <pre>{{ JSON.stringify(user, null, 2) }}</pre>

    <div>session</div>
    <pre>{{ JSON.stringify(session, null, 2) }}</pre>
  </div>
</template>
<script setup lang="ts">
definePageMeta({
  // 用意したapp/middleware/auth.tsを利用するように設定
  middleware: 'auth',
});

// 確認用のログイン情報の取得
const { session, user } = useUserSession();
</script>

以上!これでGoogleログインが完成!

あとは、pnpm devで起動して、
http://localhost:3000にアクセスして試せば、ログインできる
超簡単(*´ω`*)

Nuxt Auth Utilsは、何をしてくれているのか?

特にAPI部分のdefineOAuthGoogleEventHandlerが 便利だけど、不思議な感じがする

// server/api/auth/google.get.ts
export default defineOAuthGoogleEventHandler({
  async onSuccess(event, { user }) {
    await setUserSession(event, { user });
    return sendRedirect(event, '/home');
  }
});

やってくれていることは、OAuth2.0の認証フローを実行してくれている感じ
この図のNuxtの部分をほぼまるっとしてくれている

Googleのドキュメントだと、このあたり
HTTP/RESTを選ぶと、URLとかが確認できる

userには何が入っているのか?

実行すると、ホーム画面に表示されるけど、
onSuccessで渡されるuserをそのまま設定しているだけ

https://www.googleapis.com/oauth2/v3/userinfo
の返り値をそのまま使っている感じ

なので、OAuthプロバイダごとに、userの内容が異なるので注意

useUserSession()sessionuserの違いは?

コンポーザブルのuseUserSession()があるけど、
実はあまり違いがない

// 確認用のログイン情報の取得
const { session, user } = useUserSession();

ソースコードをみてみると、こんな感じ

// https://github.com/atinux/nuxt-auth-utils/blob/v0.5.7/src/runtime/app/composables/session.ts
// ...略
export function useUserSession(): UserSessionComposable {
  // 取得したユーザセッション
  const sessionState = useState<UserSession>('nuxt-session', () => ({}));
  // ...略

  return {
    // ...略
    // session、そのまま
    session: sessionState,
    // sessionのuserだけ
    user: computed(() => sessionState.value.user || null),
    loggedIn: computed(() => Boolean(sessionState.value.user)),
    // ...略
  }
}

これを知ったうえで、説明変数(session)を追加するとこんな感じっぽい

// server/api/auth/google.get.ts
export default defineOAuthGoogleEventHandler({
  async onSuccess(event, { user }) {
    const session = { user };
    await setUserSession(event, session);
    return sendRedirect(event, '/home');
  }
});

その他もろもろ

Session周りはh3のutilを使っている

中を見てみると、h3のuseSessionを使っている感じ

セッション名とか有効期限とか

h3のuseSessionを使っているので、
runtimeConfigを使って設定する感じ

runtimeConfig: {
  session: {
    // default
    name: 'nuxt-session',
    password: process.env.NUXT_SESSION_PASSWORD || '',
    cookie: {
      sameSite: 'lax'
    }
    // 有効期限を設定する場合
    // maxAge: 60 * 60 * 24 * 7 // 1 week
  }
}

これもh3のドキュメントを見るといい

onErrorが呼ばれるタイミング

defineOAuthGoogleEventHandlerには、onErrorも設定できる

export default defineOAuthGoogleEventHandler({
  async onSuccess(event, { user }) {},
  onError(event, error) {
    // エラーが発生したときの処理
  } 
});

どんなものがくるか、ソースを見てみると、こんな感じっぽい

  • ①設定が不足している場合
  • ②アクセストークン取得でエラーが発生した場合
// https://github.com/atinux/nuxt-auth-utils/blob/v0.5.7/src/runtime/server/lib/oauth/google.ts
export function defineOAuthGoogleEventHandler({
  config, onSuccess, onError,
}: OAuthConfig<OAuthGoogleConfig>) {
  return eventHandler(async (event: H3Event) => {
    config = defu(config, useRuntimeConfig(event).oauth?.google, {
      authorizationURL: 'https://accounts.google.com/o/oauth2/v2/auth',
      tokenURL: 'https://oauth2.googleapis.com/token',
      userURL: 'https://www.googleapis.com/oauth2/v3/userinfo',
      authorizationParams: {},
    }) as OAuthGoogleConfig

    // ...略

    // ①設定が不足している場合
    if (!config.clientId || !config.clientSecret) {
      return handleMissingConfiguration(event, 'google', ['clientId', 'clientSecret'], onError)
    }

    // ...略

    const tokens = await requestAccessToken(config.tokenURL as string, {
      // ...略
    })

    // ②アクセストークン取得でエラーが発生した場合
    if (tokens.error) {
      return handleAccessTokenErrorResponse(event, 'google', tokens, onError)
    }

    const accessToken = tokens.access_token
    const user: any = await $fetch(
      config.userURL as string,
      // ...略
    )

    return onSuccess(event, {tokens, user })
  })
}

ちゃんとやるときは、userはセットしない

全部、setUserSession()に設定すると、
見えちゃいけないのもクライアント側に流れてしまうので、
ちゃんと必要なものだけにしておく必要がある

// server/api/auth/google.get.ts
export default defineOAuthGoogleEventHandler({
  async onSuccess(event, { user }) {
    const account = await // ... dbからアカウント情報を取得するなどなど
    await setUserSession(event, { user: account });
    return sendRedirect(event, '/home');
  }
});

NUXT_SESSION_PASSWORDは本番時にも設定しておく

.envが無いときにpnpm devを実行すると、
NUXT_SESSION_PASSWORDしてくれる

ただ、本番環境でNUXT_SESSION_PASSWORD指定なしのままだと、
NUXT_SESSION_PASSWORDが空のままになるので、注意が必要

# .env
NUXT_SESSION_PASSWORD=password-with-at-least-32-characters

User/UserSessionの型を定義する

READMEに書いてあるとおり、型定義ファイルを用意すればOK

// auth.d.ts
declare module '#auth-utils' {
  interface User {
    // Add your own fields
  }

  interface UserSession {
    // Add your own fields
  }

  interface SecureSessionData {
    // Add your own fields
  }
}

export {}

権限周りもいい感じにする

権限周りなどもしたい場合は、nuxt-authorizationがよいらしい
nuxt-auth-utilsと互換性があり、permissionも定義/管理できる

参考にしたサンプル

実際のサンプルなどは、このあたりが参考になった


以上!! 思いの外、簡単にGoogleログインができてしまった(*´ω`*)

参考にしたサイト様