0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React × NestJS × FirebaseAuthで作りながら理解する認証(後編)

Posted at

前編の続きです。

認証/認可がイマイチわかっていなかったので、ChatGPTに相談しながら実装してみました。

後編の今回はフロント側の実装を行います。

今回実装する認証の流れ

今回実装する認証は、以下のような流れを想定しています。

  1. フロント側でメールアドレスやGoogleアカウントなどでログインを行う
  2. Firebase AuthenticationがIDトークン(JWT)を発行する
  3. NestJS側でIDトークンを検証する
  4. 検証がOKなら続きの処理を行う
  5. 処理結果をレスポンスとして返し、フロントは画面に結果を表示する

🛠 実装のステップ

▪️前編(前回)

前編では先ほどの処理の流れの3、4を実装します。
バックエンド側での「認証」機能を作成します。

  1. Firebaseの環境構築
  2. NestJSでバックエンドを構築
  3. AuthGuard の作成(IDトークンのJWT検証)
    a. これで「認証」を行う

▪️後編(今回はこっち)

後編はフロントエンド側でログインし「IDトークン」を取得し、結果を表示します。

  1. フロントでの ID トークン取得ユーティリティ作成
    a. 「認証」はバックエンド側で行いますが、利便性のためフロントでも検証
  2. フロント画面でのログイン表示

フロントの環境構築

まずはフロントの環境を構築します!ビルドツールはVite、フロントのフレームワークはReactを使います。

npm create vite@latest firebase-auth-demo --template react-ts
cd firebase-auth-demo
npm install

新規登録・ログイン機能を作る

次は新規登録・ログイン機能を作ります。

authインスタンスをエクスポート

まずはFirebaseからauthインスタンスをエクスポートします。

authインスタンスは、Firebaseの認証機能を使うためのものです。設定したFirebaseプロジェクトへのアクセスの窓口として機能します。

このインスタンスごとに、現在ログインしているユーザーや設定を保持しています。

firebase.ts
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  // Firebase コンソールで作成したプロジェクトの設定値
};

// Firebase アプリを初期化
const app = initializeApp(firebaseConfig);

// Firebase Auth をエクスポート
export const auth = getAuth(app);

画面を作る

ReactのApp.tsxでログイン画面を作ります。

Firebaseは必要な機能をいい感じに提供してくれるので、新規登録はcreateUserWithEmailAndPassword()、ログインはsignInWithEmailAndPassword()関数を使うと簡単に認証を行ってくれます。

この関数を使うことで、Firebase Authenticationが内部的にIDトークン(JWT)を発行し、クライアントに渡します。

App.tsx
import { useEffect, useState } from "react";
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
} from "firebase/auth";
import { auth } from "./lib/firebase";

function App() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const register = () => {
    createUserWithEmailAndPassword(auth, email, password).catch(console.error);
  };

  const login = () => {
    signInWithEmailAndPassword(auth, email, password).catch(console.error);
  };

  return (
    <>
      <input placeholder="Email" onChange={(e) => setEmail(e.target.value)} />
      <input placeholder="Password" type="password" onChange={(e) => setPassword(e.target.value)} />
      <div style={{ marginTop: "1rem" }}>
        <button onClick={register}>新規登録</button>
        <button onClick={login}>ログイン</button>
      </div>
    </>
    )
}

export default App;

認証OK後の画面を作る

新規登録・ログインが完了すると(認証が完了すると)、Firebase Authenticationからuserインスタンスが取得できます。

このuserインスタンスがあるか?を監視することで、新規登録・ログイン後に画面を変化させます。

この監視の処理もFirebase側でいい感じのonAuthStateChanged()関数を用意してくれています。これをuseEffectで画面のレンダリング後に1回だけ実行します。

App.tsx
const [user, setUser] = useState<any>(null);

useEffect(() => {
  // ① リスナー登録して、変更を監視
  const unsub = onAuthStateChanged(auth, (user) => {
    setUser(user);// ② 認証状態が変わったら state を更新
  });
  // ③ クリーンアップ:コンポーネントが消えたらリスナー解除
  return () => unsub();
}, []);

// 省略

return (
  <div>
    {user ? (
      <>
        {/* 認証が完了したらuserがセットされるので、こっちの画面が表示される */}
        <p>Welcome, {user.email}</p>
        <button onClick={logout}>Logout</button>
      </>
    ) : (
      <>
        {/* userがなければログイン・新規登録画面を表示する */}
      </>
    )}
  </div>
)

バックエンドで作ったAPIを呼び出す

ここまではフロント側での認証でした。
前回バックエンド側で作ったAPIを呼び出してみます。

前回のおさらい

前回バックエンドでは/helloというエンドポイントを作りました。
そこではHTTPヘッダーにIDトークンを渡して、そのIDトークンが正しいか?を検証していました。

APIを呼び出してみよう

API呼び出し処理

「Helloを呼び出す」ボタンを押すとgetHello()関数が呼ばれます。

この関数の中では、まずauth.currentUserから現在のユーザーを取得します。
ログインまたは新規登録している場合はここにはユーザーの情報が入ります。万一入ってない場合はエラーにします。

user.getIdToken()で認証した後に発行されたIDトークンを取得できます。
引数のtrueはキャッシュを破棄するかどうかですが、ここでは説明は省きます。

トークンを取得できたら、HTTPヘッダーにトークンを設定してリクエストを送ります。これでバックエンドにリクエストを送れました!バックエンド側の処理は前編で実装済みです。

レスポンスを表示

リクエストが正常に処理されると、resにバックエンドから「Hello World!」が返されます。

これをmessageに格納して画面に表示させます。

App.tsx
import { auth } from './firebase';

const [message, setMessage] = useState<string>(null);

const getHello = async () => {
  // ログイン中のユーザーを取得する
  const user = auth.currentUser;
  if (!user) {
    throw new Error('ログインしてないよ!');
  }
  
  // IDトークンを取得してHTTPヘッダーに設定
  const token = await user.getIdToken(true);
  const res = await fetch(url, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });
  const {data} = await res.json();
  setMessage(data);
}

return (
  {/*好きなところにボタンを設置*/}
  <button onClick={getHello}>Helloを呼び出す</button>
  // getHello()した後のレスポンスを表示
  {message &&
    <p>{message}</p>
  }
)

これでフロント側の実装が完了しました!

まとめ

前編/後編にわけて認証の実装をしました。

繰り返しになりますが、認証とは そのユーザーが本人か? を検証する仕組みです。
認証の方法はいくつかあると思いますが、今回はベーシックな IDトークン(JWT) を使った認証を実装して簡単な流れを確認しました。

  1. 新規登録・ログインを行う
  2. ユーザーの登録/認証を行い、IDトークンを発行する
  3. 取得したIDトークンをHTTTPヘッダーに設定してAPIを呼び出す
  4. バックエンド側で渡されたIDトークンを検証して、ユーザーが本人か?を確認する
  5. 本人ならAPIの処理を行う。本人でないならエラーを返す

こんな感じで、「認証」をして安全なアプリを構築するんですね!

今回は「認証」を行いましたが、今度は権限(一般ユーザー/管理者 など)を検証する「認可」についても勉強してみようと思います。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?