クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【Firebase】Authenticationでログイン設定を作る(メール/パスワードによるユーザー認証)

Firebaseの続きです。前回はRealtime DatabaseにFetch APIでCRUD操作しました。作ってみて気がついたがこのままだと誰でもデータを編集できちゃう。そうなってくると必要なのはユーザー認証設定。Authenticationで実現できるようなのでトライ。ドキュメキュメントにはなかったリダイレクト設定も調べて実現しました。それではいきましょう!

【目次】

※参考:前回記事
【Firebase】Realtime DatabaseにFetch APIでCRUD操作する - クモのようにコツコツと

※参考:Web開発環境についてのまとめ(随時更新)
qiita.com

前回のおさらい

Realtime DatabaseにFetch APIでCRUD操作できた!

https://cdn-ak.f.st-hatena.com/images/fotolife/i/idr_zz/20210115/20210115053235.jpg

  • Create(追加):POST
  • Read(取得):GET
  • Update(更新):UPDATE
  • Delete(削除):DELETE

サーバ側の設定をしていないがあらかじめRealtime Database側にレスポンスを返す設定が用意されているということだ!

詳細は前回記事を参照

※参考:【Firebase】Realtime DatabaseにFetch APIでCRUD操作する - クモのようにコツコツと

ただ、このままだと誰でもデータを編集できてしまう。そうなってくると次に欲しいのはユーザー認証機能。

Authenticationコンソールの設定

ユーザー追加設定

ユーザー認証はFirebaseのAuthentication*1というプロダクトで実現できる。

※参考:Firebase Authentication

Firebaseのコンソールで「Authentication」を開く(「Users」タブ)。 f:id:idr_zz:20210120070140j:plain

今回はユーザーの追加/削除を作らずログイン/ログアウトだけを検証したい。この画面であらかじめゲストユーザーを作っておこう。 f:id:idr_zz:20210120070638j:plain

ゲストユーザーが追加された*2!なるほど、Firebase側で「ユーザー UID」を追加してユーザーを認識するんだ。これによってユーザーのパスワードは管理者には見せない仕組みになっている。 f:id:idr_zz:20210120070838j:plain

ユーザー設定の変更

一番右の三点アイコンでオプション設定が開く f:id:idr_zz:20210120072327j:plain

パスワードの再設定、アカウント無効、アカウント削除がある f:id:idr_zz:20210120072150j:plain

パスワードの再設定はメアドにメールが送られるようだ。これによってユーザーだけがパスワードを編集できる。 f:id:idr_zz:20210120072454j:plain

アカウント無効は管理者側でできる f:id:idr_zz:20210120072551j:plain

アカウント削除も管理者側でできる f:id:idr_zz:20210120072613j:plain

メール認証を有効にする

次に「Sign-in method」タブを開く。おお、メール、Google、Twitterなどいろんな認証設定がある! f:id:idr_zz:20210120072904j:plain 今回はシンプルそうな「メール/パスワード」から体験したい。

初期は全て「無効」だが、カーソルを載せると鉛筆アイコンで変更できる f:id:idr_zz:20210120073019j:plain

「メール/パスワード」の変更画面 f:id:idr_zz:20210120073119j:plain

「有効」にして保存 f:id:idr_zz:20210120073201j:plain

「メール/パスワード」だけが有効になった! f:id:idr_zz:20210120073303j:plain

参考ドキュメント

ここからコード作成に入る。基本的にはドキュメントを参考に進める。

※参考:ウェブサイトで Firebase Authentication を使ってみる
※参考:JavaScript でパスワード ベースのアカウントを使用して Firebase 認証を行う

しかし、メール/パスワード認証のドキュメントは肝心の処理内容が空っぽだったりして詳細情報を調べるのに苦心した。*3

ドキュメントに埋め込まれているこちらの動画、作業の流れがイメージしやすかった♪ www.youtube.com

HTMLページ

ログインページを作る

新規で「login.html」というページを作る。

※参考:firebase-hosting-test/login.html at main · ryo-i/firebase-hosting-test · GitHub

入力フォームとログインボタン

<section>
        <h1>今日の一句へのログイン</h1>
        <p>ログインしてください。</p>
        <label>
            Email<input type="text" name="email" size="30" maxlength="30" class="email">
        </label>
        <label>
            Pass<input type="password" name="pass" size="30" maxlength="30" class="pass">    
        </label>
        <input type="button" value="ログイン" class="loginBtn">
    </section>

.emailにメアド、. passにパスを入力し、.loginBtnボタンを押すとログインできるようにする。

bodyタグの最後にfirebase系のリンクを追加

    <!-- update the version number as needed -->
    <script defer src="/__/firebase/8.2.1/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script defer src="/__/firebase/8.2.1/firebase-auth.js"></script>
    <script defer src="/__/firebase/8.2.1/firebase-analytics.js"></script>
    <!-- 
    initialize the SDK after all desired features are loaded, set useEmulator to false
    to avoid connecting the SDK to running emulators.
    -->
    <script defer src="/__/firebase/init.js?useEmulator=true"></script> 
    <script src="js/auth.js"></script>
  • 「firebase-app.js」はFirebase本体
  • 「firebase-auth.js」はAuthentication本体
  • 「firebase-analytics.js」はGoogleアナリティクス設定
  • 「init.js」はこのアプリ固有の初期化情報
  • 「auth.js」は今回新たに作るログイン設定のファイル

以前、Realtime Databaseの時に初期化情報をコメントアウトしても動いたのは「init.js」に初期化情報が書かれているからだった!

※参考:【Firebase】Realtime Databaseにブラウザから直接CRUD操作する - クモのようにコツコツと

ログアウトボタンを作る

前回作った「今日の一句」ページ「fetch.html」にログアウトボタンを追加する。

※参考:firebase-hosting-test/fetch.html at main · ryo-i/firebase-hosting-test · GitHub

            <p>現在ログイン中です。</p>
            <input type="button" value="ログアウト" class="logoutBtn">

.logoutBtnボタンを押すとログアウトしてログインページにリダイレクトさせたい。さらにログアウト中にこのページにアクセスしてもログインページにリダイレクトされるようにしたい。

bodyタグの最後

    <!-- update the version number as needed -->
    <script defer src="/__/firebase/8.2.1/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script defer src="/__/firebase/8.2.1/firebase-auth.js"></script>
    <script defer src="/__/firebase/8.2.1/firebase-analytics.js"></script>
    <!-- 
    initialize the SDK after all desired features are loaded, set useEmulator to false
    to avoid connecting the SDK to running emulators.
    -->
    <script defer src="/__/firebase/init.js?useEmulator=true"></script> 
    <script src="js/auth.js"></script>
    <script src="js/fetch.js"></script>

こちらには前回作ったFetch APIのCRUD操作設定の「fetch.js」があったが、それに加えてFirebaseのファイルと「auth.js」のリンクも追加する。

JSコード

ここからはJSコード。新規で「auth.js」を作成

※参考:firebase-hosting-test/auth.js at main · ryo-i/firebase-hosting-test · GitHub

DOMContentLoadedイベント(DOM読み込み後に実行)

処理の全体を下記のイベントで囲う。

document.addEventListener('DOMContentLoaded', () => {
    // 処理内容
});

処理を普通に書くと「firebase-app.js」や「firebase-auth.js」で定義されているfirebase.auth()が認識されなかった。

そのため全体をDOMContentLoadedのイベントで囲って、DOM(およびbodyタグ内にscriptタグでリンクされているJSファイル)が全部読み終わってから処理を実行するようにしたら、認識された。

scriptタグに書かれているdefer属性が処理順番に影響しているようだった。

※参考:<script> タグに async / defer を付けた場合のタイミング - Qiita

DOM取得

    // DOM
    const email = document.querySelector('.email');
    const pass = document.querySelector('.pass');
    const loginBtn = document.querySelector('.loginBtn');
    const logoutBtn = document.querySelector('.logoutBtn');
  • 変数email.emailを取得
  • 変数pass.passを取得
  • 変数loginBtn.loginBtnを取得
  • 変数logoutBtn.logoutBtnを取得

先ほど「login.html」と「fetch.html」に作ったログイン/ログアウトにつかうDOMを取得する

ログイン状態のチェック

「fetch.html」のページを開いた時にログインチェックを行い、ログアウトしていたらログインページ「login.html」にリダイレクトさせる。

// Login Check
    const loginCheck = () => {
        const pathname = window.location.pathname;
        console.log(pathname);
        firebase.auth().onAuthStateChanged((user) => {
            if (user) {
                console.log('ログイン済み!');
                if (pathname === '/login.html') {
                    window.location.replace('fetch.html');
                }  
            } else {
                console.log('未ログイン!');
                if (pathname === '/fetch.html') {
                    window.location.replace('login.html');
                } 
            }
        });
    };
    loginCheck();
  • 変数loginCheckの値はアロー関数
  • 変数pathnameでページのパスpathnameを取得(コンソールにパスを表示)
  • firebase.auth()onAuthStateChanged()メソッドを実行、引数はアロー関数で引数はuser
  • もしuserが認識されたらコンソールに「ログイン済み!」と表示
    もしパスが/login.htmlだったらwindow.locationfetch.htmlにリダイレクト
  • userが認識されなかったらコンソールに「未ログイン!」と表示
    もしパスが/fetch.htmlだったらwindow.locationlogin.htmlにリダイレクト
  • loginCheck()実行(ページ読み込み時)

ログイン状態のチェックについてはこちらのドキュメントにあった。onAuthStateChanged()でチェックする

※参考:Firebase でユーザーを管理する

しかし、ドキュメントだとチェック後の処理が空っぽでリダイレクトさせる方法がよくわからなかったw f:id:idr_zz:20210122050339j:plain


調べるとそのものズバリなQAを発見!普通にJSのwindow.locationを使えばいいのかな?

※参考:Firebaseログイン後のページへのリダイレクト - Javascript

こちらはVue/Nuxt環境での事例だが、実際にwindow.locationを使って実現している!

※参考:Nuxt/VuexでFirebase Authenticationを使ってユーザー認証機能を作る - フリーランチ食べたい

ということで自分もこの方法を取ることにした。


window.locationでリダイレクトしたあとにブラウザの「戻る」ボタン押すと戻れちゃった、、

リダイレクトがブラウザの履歴に残ってしまうようだ。replace()を追加したら回避できた♪

※参考:ブラウザの戻るボタンを押せなくする(ブラウザバック禁止) - Qiita


なお、こちらのドキュメントでlinkWithRedirect()というメソッドを見つけたが、この方法だと動かなかった。

※参考:JavaScript を使用してアカウントに複数の認証プロバイダをリンクする  |  Firebase

これはGoogleなどの外部プロバイダの認証ページから戻るためのメソッドのようだ。

※参考:User | JavaScript SDK  |  Firebase

ログイン処理

ログイン処理を作る

    // Login Event
    const login = () => {
        const emailVal = email.value;
            const passVal = pass.value;
            firebase.auth().signInWithEmailAndPassword(emailVal, passVal)
            .then((user) => {
                console.log('ログイン成功!');
                loginCheck();
            })
            .catch((error) => {
                const errorCode = error.code;
                const errorMessage = error.message;
                console.log('ログイン失敗!');
                console.log(errorCode);
                console.log(errorMessage);
                alert('ログインに失敗しました');
            });
    };
  • 変数loginの値はアロー関数
  • 変数emailValemailの入力値を取得
  • 変数passValpassの入力値を取得
  • firebase.auth()signInWithEmailAndPassword()メソッドを実行(引数はemailVal, passVal
  • then()でログイン成功時の処理を(引数はuser) コンソールに「ログイン成功!」とuser情報を表示 loginCheck()を実行(リダイレクト)
  • catch()でエラー処理を設定(引数はerror) 変数errorCodeでエラーコードを取得
    変数errorMessageでエラーメッセージを取得
    コンソールで「ログイン失敗!」とエラーコード、エラーメッセージを表示
    アラートで「ログインに失敗しました」表示

ログイン成功するとloginCheck()でリダイレクトする。ログイン失敗はアラートでわかるようにした。

メールアドレス/パスワードの方法はこちらのドキュメント

※参考:JavaScript でパスワード ベースのアカウントを使用して Firebase 認証を行う

signInWithEmailAndPassword()でログインできる(長い名前w)

※参考:Auth | JavaScript SDK  |  Firebase

ドキュメントではログイン後の処理内容がまたしても空っぽw f:id:idr_zz:20210122050538j:plain

やりたいことはページのリダイレクトだったので先ほどのloginCheck()の方法を調べた次第。


ログインボタンのクリックイベント

    if (loginBtn) {
        loginBtn.addEventListener( 'click', login, false );
    }
  • もしloginBtnが存在したらloginBtnをクリックしたときloginを実行

loginBtnが「fetch.html」ページでは存在しないため、このページではエラーになった。そのため、if文でloginBtnの存在を判定してから実行するようにした。

ログアウト処理

ログイン後、ログアウトもできるようにしたい。ログアウトボタンでログアウトする((できれば一定時間が経ったらログアウト、とかもしたいが今回は調べきれず)。

先ほどのログインボタンイベントがベース(処理は少ない量だったのでそのまま中に書いた)。

    // Logout Event
    if (logoutBtn) {
        logoutBtn.addEventListener( 'click', () => {
                firebase.auth().signOut();
                loginCheck();
        }, false );
    }
  • もしlogoutBtnが存在したらlogoutBtnをクリックした時に
    firebase.auth()signOut()メソッドを実行
    loginCheck()を実行

こちらもlogoutBtnが「login.html」に存在しないのでif文で存在を判定してから実行。

ログアウトはsignOut()で実行される。こちらは短い名前w

※参考:Auth | JavaScript SDK  |  Firebase

ログアウト後にloginCheck()を実行してログインページにリダイレクト


なお、今回はやってないがログイン後にユーザー情報も取得できそう。

※参考:Firebase でユーザーを管理する

例えば「こんにちは○○さん」とアカウント名を表示したり、「○○さんの投稿一覧」だけをピックアップしたり、ページの内容を動的に変更できる。

ブラウザ挙動

ログアウト状態で「fetch.html」にアクセスすると

※参考:今日の一句ページ Realtime DatabaseにFetch APIでCRUD

ログインページ「login.html」にリダイレクトされる。 f:id:idr_zz:20210122044427j:plain

※参考:ログインページ
Realtime DatabaseにFetch APIでCRUD

Authenticationのコンソールで設定したゲストユーザーのメール/パスワードを入れる f:id:idr_zz:20210122044607j:plain

ゲストユーザーのメール/パスワードは下記

  • Email: guest@guest.test
  • Pass: guest001

「ログイン」ボタンを押すと「今日の一句」ページにリダイレクトされる f:id:idr_zz:20210122044753j:plain

※参考:Realtime DatabaseにFetch APIでCRUD

「ログアウト」ボタンを押すと再びログインページにリダイレクトされる f:id:idr_zz:20210122044427j:plain

※参考:ログインページ
Realtime DatabaseにFetch APIでCRUD

全然違うメール/パスワードを入れてみる f:id:idr_zz:20210122045446j:plain

「ログインに失敗しました」のアラートが出る f:id:idr_zz:20210122045514j:plain


ソース(GitHub)

※参考:ログイン機能JS
firebase-hosting-test/auth.js at main · ryo-i/firebase-hosting-test · GitHub

※参考:ログインページHTML
firebase-hosting-test/login.html at main · ryo-i/firebase-hosting-test · GitHub

※参考:ログアウトボタンHTML
firebase-hosting-test/fetch.html at main · ryo-i/firebase-hosting-test · GitHub

最後に

ということでFirebaseのAuthenticationを使ってメール/パスワードによるユーザー認証設定を作ることができました!

ちょっとした体験版などでゲストユーザーによるログイン機能を作るならこれで十分な気がしますね。いきなり個人情報(メアド、Googleアカウント、SNSアカウントなど)を入れるのは抵抗がある人もいると思うので。

Firebaseのメール/パスワード認証のドキュメントではログイン判定以外の処理内容は空っぽだったのでw、その中に入れるリダイレクト設定の参考になれば幸甚です。

今後、Google認証やTwitter認証なども試してみたく思います。それではまた!


※参考:Web開発環境についてのまとめ(随時更新)
qiita.com

*1:Authenticationという単語は「認証」という意味でAuthenticは「本物の」という意味。よく見るAuthは単語としてはなかったので略語と思われる。

*2:メール送信は行わないので架空のメアドにしたが問題なさそう

*3:書かなくてもわかるよね的な認識なのか、あまり力を入れていないオマケ的な位置付けなのか