【Firebase】Authentication/認証の使い方|Googleアカウントとemail・パスワードによるログイン方法を実例で解説

firebase-prograshi(プロぐらし)-kv Firebase
記事内に広告が含まれていることがあります。

Webアプリ開発において、セキュリティと利便性を両立した「ユーザー認証」の実装は避けて通れない課題です。しかし、一から構築すると非常に工数がかかり、脆弱性のリスクなどの不安もあります。

そこで活用したいのが、Googleが提供するFirebase Authenticationです。

本記事では、定番の「Googleアカウント連携」と、汎用性の高い「メールアドレス・パスワード認証」の2パターンを実例付きで詳しく解説しています。

SDKの導入からコンソールの設定、実際のコード例まで、ステップバイステップで紹介しています。


Autheticationの有効化

まずはFirebaseでAuthenticationを有効化します。

Firebaseの対象のアプリに入り、左メニューの「構築 > Authentication」を選択します。


「始める」をクリックします。


ログイン方法の選択画面に移るので「メール/パスワード」を選択します。


「有効にする」にして保存します。


「新しいプロバイダを追加」をクリックします。

「Google」を選択します。


有効にし、プロジェクトの公開名とサポートメールアドレスを指定し、保存します。

MEMO

「ウェブSDK構成」は、既に作成済みのAndroidアプリで認証を追加していた場合や、Firebaseを通さずにGoogle Cloudコンソールで直接プロジェクトを作成し、そこでOAuthクライアントIDを作成していた場合に、そのIDをFirebase Authenticationで再利用するための設定です。

AndoroidアプリやGoogle Cloud コンソールでのプロジェクト作成がない場合や、あったとしてもそれれと無関係の場合は入力不要です。


以上で、メール/パスワードによる認証とGoogleによる認証が使えるようになります。


プロジェクトの生成

next.jsでテスト用のプロジェクトを作成します。

npx create-next-app@latest test-auth
next.jsのプロジェクト生成方法

next.jsでプロジェクトを作成する方法は下記をご参考ください。

> Next.jsとは何か?超簡単にWEBサイトを公開する方法を実例で解説|create-next-appツールの使い方


Success!と表示されれば完了です。


firebaseのインストール

firebaseを使うために必要なパッケージを作成します。

cd test-auth
npm install firebase

npm install firebaseは、FirebaseのSDKをインストールするコマンドです。

SDKとは、開発に必要なnpmパッケージ、CLIツール、管理画面などが含まれたキットです。

Point

npm install firebaseで変化があるファイルは「node_modukes」「package-lock.json 」「package.json」のみです。新たにフォルダやファイルが生成されるわけではありません。

Firebaseを動かすために必要なパッケージをインストールするのみです。


SDKの初期化設定(.env.localとfirebase.js)

Firebaseを動かすためのキット(SDK)は入りましたが、Googleのサーバーと接続するための認証情報は自分で作成しなければいけません

そこでルートディレクトリに以下のフォルダとファイルを作成します。lib/firebase.js

mkdir lib
ni lib/firebase.js

libは、libraryの略で慣習的にこの中に「firebase.js」という設定ファイルを設置しAPIキーなどの重要な接続情報(環境設定)を記述していきます。

ですが、一般公開するとそれらの重要な情報をみんなが見れる状態になってしまいます。

そこで .env.localというファイルを作成し、その中に重要な情報を記述します。


NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSy...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-app.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-app-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-app.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=123456789
NEXT_PUBLIC_FIREBASE_APP_ID=1:12345:web:abc123

※.gitignoreに.env.localが記載されている(アップロード対象外になっている)ことを確認してください。


import { initializeApp, getApps } from "firebase/app";
import { getFirestore } from "firebase/firestore"; //Firestoreの読み込み
import { getAuth } from "firebase/auth"; // Authも使う場合

//process.envで環境変数を読み込む
const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

// Next.jsは開発中に何度もリロードされるため、二重初期化を防ぐこの書き方が推奨されます
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];

const db = getFirestore(app);
const auth = getAuth(app);

export { app, db, auth };
export { db, auth }; でもいい

一番最後のexportで app, db, authを使えるようにしています。appを除外しても問題ありません。(authも不要な場合は除外して問題ありません)

export { db, auth };

appは、Firebase Storageを使う場合const storage = getStorage(app);や、Analyticsを使う場合 getAnalytics(app); に必要となります。


トップページの作成

app/pages.jsに以下を記述します。

"use client";

import { useState, useEffect } from "react";
import { auth } from "@/lib/firebase";
import { 
  signInWithEmailAndPassword, 
  createUserWithEmailAndPassword, 
  signInWithPopup, 
  GoogleAuthProvider, 
  signOut, 
  onAuthStateChanged 
} from "firebase/auth";

export default function AuthPage() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [user, setUser] = useState(null);

  // 1. ログイン状態の監視
  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
      setUser(currentUser);
    });
    return () => unsubscribe();
  }, []);

  // 2. メール/パスワードでサインアップ・ログイン
  const handleEmailLogin = async (e) => {
    e.preventDefault();
    try {
      await signInWithEmailAndPassword(auth, email, password);
    } catch (error) {
      // ユーザーが存在しない場合は新規登録を試みる例
      if (error.code === 'auth/user-not-found') {
        await createUserWithEmailAndPassword(auth, email, password);
      } else {
        alert("エラー: " + error.message);
      }
    }
  };

  // 3. Googleログイン
  const handleGoogleLogin = async () => {
    const provider = new GoogleAuthProvider();
    try {
      await signInWithPopup(auth, provider);
    } catch (error) {
      alert("Googleログインエラー: " + error.message);
    }
  };

  // 4. ログアウト
  const handleLogout = () => signOut(auth);

  if (user) {
    return (
      <div style={{ padding: "20px" }}>
        <h1>こんにちは、{user.displayName || user.email}さん</h1>
        <button onClick={handleLogout}>ログアウト</button>
      </div>
    );
  }

  return (
    <div style={{ padding: "20px", maxWidth: "400px" }}>
      <h2>ログイン / 新規登録</h2>
      
      {/* メールログインフォーム */}
      <form onSubmit={handleEmailLogin} style={{ marginBottom: "20px" }}>
        <input 
          type="email" 
          placeholder="メールアドレス" 
          value={email} 
          onChange={(e) => setEmail(e.target.value)} 
          style={{ display: "block", marginBottom: "10px", width: "100%" }}
        />
        <input 
          type="password" 
          placeholder="パスワード" 
          value={password} 
          onChange={(e) => setPassword(e.target.value)} 
          style={{ display: "block", marginBottom: "10px", width: "100%" }}
        />
        <button type="submit" style={{ width: "100%" }}>メールでログイン</button>
      </form>

      <hr />

      {/* Googleログインボタン */}
      <button onClick={handleGoogleLogin} style={{ width: "100%", marginTop: "20px", backgroundColor: "#4285F4", color: "white", border: "none", padding: "10px" }}>
        Googleでログイン
      </button>
    </div>
  );
}


画面は以下のようになります。


Googleアカウントでログインすると「ログイン中」になります。


なお、ログインするとAuthenticationの「ユーザー」に情報が保存されます。


メールアドレスで新規ログインするとプロバイダにメールアイコンが追加されます。


一度登録したメールアドレスでログインする場合、パスワードを間違えるとエラーが表示されログインできません。


コード解説

use clientでクライアントコンポーネントにする

Next.jsではコンポーネントはデフォルトではサーバーコンポーネントになっています。これは、サーバー側でのみ生成され、ブラウザ側では一切変更を加えないものです。

ですが、今回の場合、ユーザーの入力やボタンクリックなどブラウザ側での変更も必要になります。

この場合、Next.jsにこのページがブラウザ側で変更可能なクライアントコンポーネントであることとを宣言する必要があります。それが冒頭の下記コードです。

"use client";


useStateで画面上で変化するデータを扱う

ユーザー入力などにより画面上で変化するデータがある場合はuseStateを使います。まずはインポートします。

import { useState, useEffect } from "react";

useStateの構文は以下のようになります。

const [データを入れる変数, データを更新する関数] = useState(初期値);

ここでは、email, password, userの3つのデータを用意しています。

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [user, setUser] = useState(null);
useStateは必須

useStateを使わないと、入力内容が変化してもReactが画面を再読み込みしません。このため、画面上の表示が変わらなくなってしまいます。

useStateは入力内容に合わせて画面の表示も変化させるために必須な機能です。



Authenticationを使う準備

Authenticationを使うために必要な機能をインポートします。

import { auth } from "@/lib/firebase";
import { 
  signInWithEmailAndPassword, 
  createUserWithEmailAndPassword, 
  signInWithPopup, 
  GoogleAuthProvider, 
  signOut, 
  onAuthStateChanged 
} from "firebase/auth";


まずは1行目で、自分のプロジェクトの認証情報を「auth」として取得しています。

次に、Authenticationを使う上で必要な関数をインポートします。

関数機能
signInWithEmailAndPasswordメールとパスワードでログインするための関数です。
createUserWithEmailAndPassword新しくアカウントを作成(サインアップ)するための関数です。
signInWithPopupポップアップ画面を表示して認証するための関数です。(主にGoogleなどのSNSログインで使用)
GoogleAuthProvider「Googleアカウントを使います」という設定をFirebaseに伝えるためのクラスです。
signOut現在ログインしているユーザーをログアウトさせるための関数です。
onAuthStateChanged「ログイン中かログアウト中か」の状態を監視する関数です。ブラウザを開いた瞬間に「この人は誰か?」を確認するために必須です。


useEffectで何度もFirebaseにアクセスすることを防ぐ

useEffectは、Reactにおいて「画面が表示された時」や「特定のデータが変わった時」に、自動で実行する処理を予約するための仕組みです。

実際の使い方としては、データ変更の度に何度もFirebaseにアクセスすることを防ぐために使います超重要な役割です

useEffectの中に、現在のユーザー情報をFirebaseから取得するコードを記述します。監視対象のデータを空にすることで、初回のみ指定のコードを実行します。

そして、画面を離れるときにクリーンアップ関数を実行し、Firebaseとの通信を完全に切ります。

import { useState, useEffect } from "react";
import { 
  signInWithEmailAndPassword, 
  createUserWithEmailAndPassword, 
  signInWithPopup, 
  GoogleAuthProvider, 
  signOut, 
  onAuthStateChanged 
} from "firebase/auth";




  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
      setUser(currentUser);
    });
    return () => unsubscribe();
  }, []);


なお、useEffectの基本構文は以下のようになっており、画面表示後に実行する処理と、どの値が変わった場合にその処理を再度実行するかを指定することができます。

useEffect(() => {
  ページロード後に実行する処理
  return クリーンアップ関数
}, [監視対象]);

このとき、監視対象を空にすることで、初回マウント時のみ処理を実行する指示になります。

useEffect(() => {
  ページロード後に実行する処理
  return クリーンアップ関数
}, [監視対象]);

今回のコードもこのコードの形になっています。



onAuthStateChanged

onAuthStateChanged は、ユーザーがログイン中か、ログアウト中かをリアルタイムで監視し続ける見張り番としての関数です。

名前の通り、Auth(認証)の State(状態)が Changed(変わった)ときに、指定した処理を自動で実行します

const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
      setUser(currentUser);
    });

動きが生じるのは以下のタイミングです。

onAuthStateChangedが動くタイミング
  1. アプリを立ち上げた(リロードした)直後
  2. ユーザーがログインに成功したとき
  3. ユーザーがログアウトしたとき


const unsubscribe = onAuthStateChanged(対象のプロジェクト, (ユーザー) => { 実行する処理 });

なお、初回のページロード時はユーザー情報は空です。

また、onAuthStateChangedを実行すると、監視を停止するための関数が戻り値で戻ります。ここではその関数をunsbscribeという変数に入れています。

ページをリロードしてもログイン状態を保てる

通常、JavaScriptの変数はページをリロードすると消えてしまいます。しかし、Firebaseはブラウザの保存領域(LocalStorageなど)にログイン情報を隠し持っています。

このため、onAuthStateChanged は、アプリが起動した瞬間にその隠し情報をチェックしに行き、ログイン情報が残っている場合はログイン状態を復元します。


メールログインと作成| signInWithEmailAndPassword、createUserWithEmailAndPassword

メールアドレスとパスワードでログインする場合のロジックは以下のようになっています。

  // メールログイン・登録
  const handleAuth = async (e) => {
    e.preventDefault();
    try {
      // ログインを試行
      await signInWithEmailAndPassword(auth, email, password);
    } catch (error) {
      // ユーザーがいない場合は新規登録
      if (error.code === 'auth/user-not-found' || error.code === 'auth/invalid-credential') {
        try {
          await createUserWithEmailAndPassword(auth, email, password);
        } catch (err) {
          alert("認証エラー: " + err.message);
        }
      } else {
        alert("エラー: " + error.message);
      }
    }
  };

HTMLの form(送信ボタン)は、クリックするとページをリロードしようとする性質があります。e.preventDefault() により、リロードを防いでいます。

まずは最初のtryでFirebaseに指定したユーザーが登録されているかを確認します。

await signInWithEmailAndPassword(auth, email, password);

登録されているデータと一致した場合は、以降の処理をスキップして、ログイン状態にします。


登録データの中にユーザーが存在しない場合は、catchの処理に移ります。createUserWithEmailAndPasswordでユーザーを新規作成しようとします。

await createUserWithEmailAndPassword(auth, email, password);


新規登録できない場合やifで指定のエラーの内容に合致しない場合は、alertでエラーの内容を表示します。

        } catch (err) {
          alert("認証エラー: " + err.message);
        }
      } else {
        alert("エラー: " + error.message);
      }


Googleでログイン|signInWithPopup、signInWithRedirect、GoogleAuthProvider

ポップアップでGoogleアカウントでログインするには以下のコードを記述します。

await signInWithPopup(auth, new GoogleAuthProvider());


なお、ポップアップではなく、ログインページにリダイレクトし、ログイン後に戻ってくるようにしたい場合はsignInWithRedirectを使います。

await signInWithRedirect(auth, new GoogleAuthProvider());


ログイン中のレイアウト

今回のページはログイン中とログアウトでレイアウトが大きく変わります。

これはif文で実現しています。

if (user) {
  return <div>ログイン済みの画面の内容</div>;
}

return <div>ログイン前の画面の内容</div>;


ログイン中の場合は、userが存在するため、ログイン中の画面が表示されます。

  // ログイン済みの表示
  if (user) {
    return (
      <div className="min-h-screen flex flex-col items-center justify-center bg-gray-50">
        <div className="p-8 bg-white shadow-md rounded-lg text-center">
          <p className="text-xl mb-4 font-bold">{user.email} でログイン中</p>
          <button onClick={() => signOut(auth)} className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition">
            ログアウト
          </button>
        </div>
      </div>
    );
  }


signOut|ログアウト

ログアウトする場合は、signOut(auth)を実行します。

タイトルとURLをコピーしました