10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Script SetUp】Vue3でログイン機能をつけたい!【Firebase Authentication】【TypeScript】

Last updated at Posted at 2022-10-31

本記事の内容

本記事ではScript SetUp、FireBase Authenticationを用いたログイン機能の実装を行います。

実装する機能は以下になります。

  • アカウント登録
  • ログイン
  • ログアウト
  • 未ログイン時、中の情報を見れないようにする

事前準備

事前準備として、以下の二つがあります。

  • axiosのインストール
  • 型定義

axiosのインストール

手順は以下の2つです。
①axiosのインストール
②axiosのインポート

axiosのインストール

以下のコマンドを実行し、axiosをインストールしてください

npm i axios

axiosのインポート

完了したらaxiosをインポートします。
この際、必要なURLを省略できるよう、baseURLの設定も行なっておきます。

src/axios/index.ts
import axios from "axios";

export const axiosComments = axios.create({
    //URL設定
    baseURL:
    'https://firestore.googleapis.com/v1/projects/vuejs-http-f75d6/databases/(default)/documents'
})

export const axiosAuth = axios.create({
    baseURL:
    'https://identitytoolkit.googleapis.com/v1'
})

export const axiosRefresh = axios.create({
    baseURL:
    'https://securetoken.googleapis.com/v1'
})

型定義

src/types/index.ts
//vuexのstate用型定義
export type AuthState = {
    idToken: null
};

//ログインの情報送信用型定義
export type AuthRequest = {
    email: string;
    password: string;
    returnSecureToken: boolean;
};

//ログインのレスポンスデータ用型定義
export type AuthResponse = {
    idToken: string;
    refreshToken: string;
    expiresIn: number;
};
//ログイン継続のレスポンスデータ用型定義
export type RefreshResponse = {
    id_token: string;
    refresh_token: string;
    expires_in: number;
};

Firebase Authenticationを使用したログイン処理

前述の通り、実装したい機能は以下の4つです。

  • アカウント登録
  • ログイン
  • ログアウト
  • 未ログイン時、中の情報を見れないようにする

アカウント登録/ログイン/ログアウト

ここでは、タイトル通り、アカウント登録・ログイン・ログアウトの機能を実装します。
そのためには以下の4つの処理が必要です。

①登録情報をサーバーに渡し、必要なデータをローカルストレージに保存

②ログイン時データをサーバーに渡し、必要なデータをローカルストレージに保存、ページを遷移させる処理

③ログインに必要なidTokenの有効期限が切れた時に、必要なトークン情報を取得し、ローカルストレージに保存する処理。

④ログアウト時、ローカルストレージに保存したデータを全て削除し、ログイン画面に遷移する処理

それぞれより具体的には以下の処理をする必要があります。

①登録情報をサーバーに渡し、必要なデータをローカルストレージに保存

  • データをサーバーに送信
  • レスポンスデータを受け取り、必要なデータを保存する処理

②ログイン時データをサーバーに渡し、必要なデータをローカルストレージに保存、ページを遷移させる処理

  • データをサーバーに送信
  • レスポンスデータを受け取り、必要なデータを保存する処理
  • ページ遷移

③ログインに必要なidTokenの有効期限が切れた時に、必要なトークン情報を取得し、ローカルストレージに保存する処理。

  • 有効期限の計算
  • 切れた際にidTokenの代わりとなるrefreshTokenを取得する処理
  • ローカルストレージに必要なデータを保存する処理

④ログアウト時、ローカルストレージに保存したデータを全て削除し、ログイン画面に遷移する処理

  • idTokenをなくす処理
  • ローカルストレージに保存してあるデータを削除する処理
  • ログイン画面に遷移する処理

ではコードを見ていきましょう。

ちなみに、axiosを使用する際に渡す、もしくは取得するデータに関してはこちらをご覧ください。

src/store/auth/index.ts
import { Module, ActionTree, MutationTree, GetterTree } from "vuex";
import { AuthState, Auth } from "../../types";
import { RootState } from "../../types/rootstate";
import { axiosAuth, axiosRefresh } from '../../axios';
import router from "../../router"
import { AxiosResponse } from "axios";

const state: AuthState = {
    idToken : ''
};
const getters: GetterTree<AuthState, RootState> = {
    idToken: state => state.idToken
};

const mutations: MutationTree<AuthState> = {
    updateIdToken(state, idToken: string) {
        state.idToken = idToken;
    }
};

const actions: ActionTree<AuthState, RootState> = {
    //③ログインに必要なidTokenの有効期限が切れた時に、必要なトークン情報を取得し、ローカルストレージに保存する処理。
    async autoLogin({commit, dispatch}){
        const idToken = localStorage.getItem('idToken')
        //もしidTokenがなかったら何もしない
        if (!idToken) return;
        const now = new Date()
        const expiryTimeMs = localStorage.getItem('expiryTimeMs')
        // 有効期限が切れているとき値を定める
        const isExpired = now.getTime() >= (+expiryTimeMs!);
        const refreshToken = localStorage.getItem('refreshToken');
        if (isExpired) {
          await dispatch('refreshIdToken', refreshToken);
        } else {
          //有効期限の残り時間を計算して、その時間経ったらrefreshTokenを発行させる処理。
          const expiresInMs = +expiryTimeMs! - now.getTime();
          setTimeout(() => {
            dispatch('refreshIdToken', refreshToken);
          }, expiresInMs);
          commit('updateIdToken', idToken);
        }
      },

      //②ログイン時データをサーバーに渡し、必要なデータをローカルストレージに保存、ページを遷移させる処理
      login({dispatch}, authData: AuthRequest){
        //サーバーへ以下の情報を送信
        axiosAuth
        .post(
            '/accounts:signInWithPassword?key=AIzaSyB_4rHWZuPdElvwvC3jSCsjZr54H1_0fPg',
            {
                email: authData.email,
                password: authData.password,
                returnSecureToken: true
            }
        )
        //レスポンスを受け取る
        .then((response: AxiosResponse<AuthResponse>)=>{
        //コミットして、受け取った値を渡す。
          dispatch('setAuthData', {
            idToken: response.data.idToken,
            expiresIn: response.data.expiresIn,
            refreshToken: response.data.refreshToken
          });
          //ページ遷移
          router.push('/')
        }) 
      },
    
      //④ログアウト時、ローカルストレージに保存したデータを全て削除し、ログイン画面に遷移する処理
      logout({ commit }) {
        commit('updateIdToken', null);
        localStorage.removeItem('idToken');
        localStorage.removeItem('expiryTimeMs');
        localStorage.removeItem('refreshToken');
        router.replace('/login');
      },
      //リフレッシュトークンを返し、継続的に利用できるようにする関数
      async refreshIdToken({dispatch}, refreshToken: string){
        await axiosRefresh
        .post('/token?key=AIzaSyB_4rHWZuPdElvwvC3jSCsjZr54H1_0fPg',{
          grant_type: 'refresh_token',
          refresh_token: refreshToken
        })
        .then((response: AxiosResponse<RefreshResponse>) =>{
          dispatch('setAuthData', {
            idToken: response.data.id_token,
            expiresIn: response.data.expires_in,
            refreshToken: response.data.refresh_token
          });
        })
      },

    //①登録情報をサーバーに渡し、必要なデータをローカルストレージに保存
      register({dispatch}, authData: AuthRequest){
        axiosAuth
        .post(
          '/accounts:signUp?key=AIzaSyB_4rHWZuPdElvwvC3jSCsjZr54H1_0fPg',
            {
                email: authData.email,
                password: authData.password,
                returnSecureToken: true
            }
        )
        .then((response: AxiosResponse<AuthResponse>)=>{
          dispatch('setAuthData', {
            idToken: response.data.idToken,
            expiresIn: response.data.expiresIn,
            refreshToken: response.data.refreshToken
          });
        }) 
      },
      //ローカルステージにidTokenとexpiryTImeMsを保存することによって、永続的にログイン状態でいられる。
      setAuthData({ commit, dispatch }, authData: AuthResponse) {
        const now = new Date();
        //有効期限が切れるときの秒数を定義
        const expiryTimeMs = now.getTime() + authData.expiresIn * 1000;
        commit('updateIdToken', authData.idToken);
        //ローカルストレージに保存
        localStorage.setItem('idToken', authData.idToken);
        localStorage.setItem('expiryTimeMs', expiryTimeMs.toString());
        localStorage.setItem('refreshToken', authData.refreshToken);
        //トークンの有効期限後、リフレッシュトークンを返し、継続的に利用できるようにする処理
        setTimeout(() => {
          dispatch('refreshIdToken', authData.refreshToken);
        }, authData.expiresIn * 1000);
      }
}

export const AuthModule: Module<AuthState, RootState> = {
    namespaced: true,
    state,
    getters,
    actions,
    mutations,
};

idTokenがある場合に、ログイン状態を保つため、以下のコードを追記します。

src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { store, key } from './store'

//autoLoginを一番最初に実行するよう設定
store.dispatch('Auth/autoLogin').then(() => {
    createApp(App).use(store, key).use(router).mount('#app')
})

未ログイン時、中の情報をブロックする処理

以下のコードをご覧ください。

src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import TodoListView from '../views/TodoListView.vue'
import RegisterView from '../views/RegisterView.vue'
import LoginView from '../views/LoginView.vue'
import { store } from '../store'

const routes: Array<RouteRecordRaw> = [
  {
    path: '/register',
    name: 'register',
    component: RegisterView
  },
  {
    path: '/login',
    name: 'login',
    component: LoginView
  },
  {
    path: '/,
    name: 'todo',
    component: TodoListView,
    //追記
    beforeEnter(to, from, next) {
      if (store.getters['Auth/idToken']) {
        next();
      } else {
        next('/login');
      }
    }
  },
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

ナビゲーションバーの変更

最後に、ログイン状態によってナビゲーションバーも変化する処理を行います。

src/App.vue
<script setup lang="ts">
import { computed } from 'vue'
import { useStore } from './store'

  const store = useStore()
  //idTokenがあれば、認証されていると判断する算出プロパティ
  const idAuthenticated = computed(()=>{
    return store.getters['Auth/idToken']!= ''
  })

  const logout = () => {
    store.dispatch('Auth/logout');
  }
</script>
<template>
  <nav>
    <template v-if="!idAuthenticated" >
      <router-link to="/register" class="header-item">Register</router-link> |
      <router-link to="/login" class="header-item">Login</router-link> |
    </template>
    <template v-if="idAuthenticated" >
      <router-link to="/">Kanban-Board</router-link> |
      <span to="/" class="header-item" @click="logout" >Logout</span> |
    </template>
    
  </nav>
  <router-view/>
</template>
10
2
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
10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?