1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

開志専門職大学情報学部Advent Calendar 2023

Day 5

nuxt3でOAuthの実装

Last updated at Posted at 2023-12-03

はじめに

Nuxt 3フレームワークを使用してWebアプリケーションを開発し、Firebase Authenticationを導入しました。
これにより、ユーザーはGoogleアカウントを利用してアプリケーションにログインすることができます。OAuthと呼ばれる認証プロセスを組み込んでいます。具体的には、ユーザーはGoogleの認証情報を使用してアプリケーションにアクセスし、同時にアプリケーションはユーザーのGoogleアカウント情報を直接知ることなく、ユーザーの同意を得て特定の情報にアクセスできるようになっています。

今回作成するもの

  • ルーティング
    • /signIn
    • /content
  • 説明
    • ルーティングを上記の2ページのみです。
    • /signInは、googleアカウントでサインインするためのページであり、だれでもアクセス可能です。
    • /contentは、googleアカウントでサインインしたユーザーのみアクセス可能です。
  • 上記の説明を図にしたもの

環境

Dockerfile
FROM node:20.10-alpine

WORKDIR /app

EXPOSE 3000
docker-compose.yml
version: '3.9'

services:
  nuxt:
    container_name: nuxt
    build: .
    volumes:
      - ./app:/app:cached
    ports:
      - "3000:3000"
    tty: true
    command: sh -c "npm install  && npm run dev"

プログラム

composables

  • user.ts
    • Firebaseのユーザー情報を管理するためのカスタムフックを定義
    • useStateフックを使ってuserの状態を管理しています。useStateの初期値はnull
    user.ts
    import { type User as firebaseUser } from 'firebase/auth';
    
    type User = {
        user: Ref<firebaseUser | null>;
        setUser: (newUser: firebaseUser | null) => void;
    };
    
    export const useUser = (): User => {
        const user = useState<firebaseUser | null>("user", () => null);
    
        const setUser = (newUser: firebaseUser | null) => {
            user.value = newUser;
        };
    
        return {
            user,
            setUser
        };
    };
    

  • auth.ts
    • useAuth関数は、signInとsignOutという2つの関数を持つオブジェクトを返します。
    • signIn関数は、Googleの認証プロバイダを使用してユーザーをサインインします。サインインが成功すると、結果のユーザー情報をsetUser関数を使って更新します。
    • signOut関数は、ユーザーをサインアウトします。サインアウトが成功すると、setUser関数を使ってユーザー情報をnullに更新します。
    auth.ts
    import {
        getAuth,
        GoogleAuthProvider,
        signInWithPopup,
        signOut as firebaseSignOut,
        type UserCredential
    } from 'firebase/auth'
    import { useUser } from '../composables/user'
    
    type Auth = {
        signIn: () => Promise<void>
        signOut: () => Promise<void>
    }
    
    export const useAuth = (): Auth => {
        const { setUser } = useUser()
    
        const signIn = async (): Promise<void> => {
            const auth = getAuth();
            const provider = new GoogleAuthProvider();
            await signInWithPopup(auth, provider)
                .then((result: UserCredential) => {
                    setUser(result.user);
                })
                .catch((error) => {
                    console.log(error);
                    alert(error.message);
                });
        };
    
        const signOut = async (): Promise<void> => {
            const auth = getAuth();
            await firebaseSignOut(auth)
                .then(() => {
                    setUser(null);
                })
                .catch((error) => {
                    console.log(error);
                    alert(error.message);
                });
        };
    
        return {
            signIn,
            signOut
        }
    }
    

middleware

  • useUserフックを使って現在のユーザー情報を取得し、そのvalueプロパティが存在しない(つまり、ユーザーが認証されていない)場合、ユーザーを/signInルートにリダイレクトします。
auth.global.ts
import type { RouteLocationNormalized } from "vue-router";
import { useUser } from "~/composables/user";

export default defineNuxtRouteMiddleware(async (to: RouteLocationNormalized) => {
    if (to.path == '/signIn') return;

    const { user } = useUser();

    if (!user.value) {
        console.log('not authenticated')
        return await navigateTo('/signIn')
    }
});

pages

  • /signIn
    • useAuthフックをインポートし、そのsignIn関数を呼び出すsignIn関数を定義
    • signIn関数の実行が完了した後に/contentルートに遷移します。
    pages/signIn
    <template>
        <div>
            <h1>signIn page </h1>
            <button @click="signIn()">signIn</button>
        </div>
    </template>
    <script setup lang="ts">
    import { useAuth } from '../composables/auth';
    
    const signIn = async (): Promise<void> => {
        await useAuth().signIn();
        await navigateTo('/content')
    }
    </script>
    

  • /content
    • ユーザーのuid、email、displayNameを表示しています。
    • seAuthとuseUserフックをインポートし、そのsignOut関数を呼び出すsignOut関数を定義
    • signOut関数の実行が完了した後に/signInルートに遷移します。
    pages/content
    <template>
        <div>
            <h1>content page</h1>
            <p>uid :{{ user?.uid }}</p>
            <p>email : {{ user?.email }}</p>
            <p>displayName : {{ user?.displayName }}</p>
            <button @click="signOut">signOut</button>
        </div>
    </template>
    <script setup lang="ts">
    import { useAuth } from "../composables/auth";
    import { useUser } from "../composables/user";
    
    const { user } = useUser();
    
    const signOut = async (): Promise<void> => {
        await useAuth().signOut();
        await navigateTo('/signIn')
    }
    </script>
    

動作

  • /signIn
    signIn.png
    google_popup.png

  • /contet

    • googleアカウントにログインすることで/contentにアクセスでき、uidとemail,displayNameを参照することができる。
      content.png

まとめ

Nuxt 3フレームワークを使用してWebアプリケーションを開発し、Firebase Authenticationを導入しました。

Firebase Hostingにデプロイしたので、以下のリンクから実際の動作を確認できます
https://nuxt3-ec8df.web.app/ 

参考文献

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?