Webプログラミング

React + Firebaseのミニアプリを作る【Firebase SDK編】

前回から引き続き、React + Firebaseでミニアプリを作成します。

React + Firebaseのミニアプリを作る【画像アップロード】

前回から引き続き、React + Firebaseでミニアプリを作成します。 今回は画像のアップロードを行えるようにします。   React + Firebaseでの画像アップロードに関して ...

続きを見る

 

前回までで画像アップロードも含めてほとんどの機能は実装できていました。ただし前回はCloud Storageの認証を切っていたため、誰でも画像をアップロードしている状態になってしまっています。

 

Cloud Storageに関してもログインしていなければ使用できないようにしたいのですが、Auth REST APIを使っていると上手く連携が取れません。

そこで今回はREST APIを使って実装したCRUD・認証をFirebase SDKを使用して書き直します。

 

ソースコードについて

本記事の前提となるコードはこちらから↓

ダウンロードした方はfirebaseConfigとaxiosのbaseURLをご自身のプロジェクト用に書き換えることを忘れずに。

 

完成コードはこちらから↓

 

認証

REST APIでは自分でトークンに関する実装をしなくてはいけませんでしたが、Firebase SDKを利用すると自分で何かを実装する必要はありません。目的にあったメソッドを利用するだけです。

ユーザ登録・ログイン

まずは作成してあるsrc > firebase > firebase.jsに以下のimport文を追加します。

import "firebase/auth";

 

ユーザをメールアドレス・パスワードで登録する場合はcreateUserWithEmailAndPasswordを使います。

firebase.auth().createUserWithEmailAndPassword("メールアドレス", "パスワード");

 

ログインにはsignInWithEmailAndPasswordを使います。

firebase.auth().signInWithEmailAndPassword("メールアドレス", "パスワード");

この後に例えばuserIdを取得したい場合は、

const userId = firebase.auth().currentUser.uid;

このようにユーザ情報を取得することが出来ます。

 

以上のメソッドを利用して、actions > auth.jsは以下のように書き換えられます。

import firebase from "../firebase/firebase";

export const authenticate = (isSignUp, data) => async dispatch => {
  dispatch(showBackdrop());
  const messageString = isSignUp ? "ユーザ登録" : "サインイン";
  try {
    if (isSignUp) {
      await firebase
        .auth()
        .createUserWithEmailAndPassword(data.email, data.password);
    } else {
      await firebase
        .auth()
        .signInWithEmailAndPassword(data.email, data.password);
    }
    dispatch(signinSuccess()); // アクションの生成
    dispatch(showMessage(messageString + "に成功しました。", "success")); // 成功のメッセージを表示
  } catch (error) {
    const errorCode = error.code;
    const errorMessage = error.message;
    console.log(errorMessage);
    // 通信が失敗した場合もエラーメッセージを表示する
    dispatch(apiFailed(messageString + "に失敗しました。コード" + errorCode));
  }
};

 

これでユーザ登録・ログインは出来ます。

確認してみましょう。

ログインに関してはCRUDの方を作っていないので分かりづらいですが、ユーザ登録はFirebaseコンソールで登録されてるのが確認できるはずです。

補足

ログインに関しても通信後にconsole.log(firebase.auth().currentUser);のようにすればログインしていることが確認できます。ログインしていなければcurrentUserはnullになります。

 

ログアウト

ついでにログアウトも実装しておきましょう。

ログアウトにはsignOutを使用します。トークン認証の場合はトークンを削除する必要がありましたが、SDKを利用すれば自分で何かを実装する必要はありません。

 

export const signout = () => async dispatch => {
  try {
    await firebase.auth().signOut();
    dispatch(showMessage("サインアウトに成功しました。", "success"));
    dispatch(signoutSuccess());
  } catch (error) {
    console.log(error);
  }
};

const signoutSuccess = () => {
  return {
    type: ACTION_TYPES.SIGN_OUT
  };
};

 

自動でログイン

リロードするごとにメールアドレス・パスワードを入力するのは面倒ですので、認証状況に応じて自動でログイン状態にしてくれるようにしたいと思います。

 

こちら↓を見てもらうのが確実ですが、(ブラウザで)Firebase SDKを使用している場合は特別な設定をしなくても、(リロードしても)認証状態は維持されています。

ブラウザ ウィンドウを閉じたり React Native でアクティビティが破棄されたりした場合でも、状態が維持されることを示します。この状態をクリアするには、明示的なログアウトが必要です。Firebase Auth のウェブ セッションは単一のホストを生成元とするため、単一のドメインでのみ永続化されることに注意してください。

 

つまり認証情報を確認し、認証できていればアプリ側(stateのisAuth)の認証状態を変更すればいいわけです。

これにはonAuthStateChangedを使えます。

export const checkUser = () => dispatch => {
  dispatch(showBackdrop());
  firebase.auth().onAuthStateChanged(user => {
    if (user) {
      dispatch(signinSuccess());
    } else {
      // No user is signed in.
      dispatch(hideBackdrop());
    }
  });
};

(checkTokenではなくcheckUserになっています。呼び出し側のContent.jsも変更しておいて下さい。)

このようにすることで、自動的にアプリをログイン状態に出来ます。

 

 

SDKでの認証全般の参考↓

 

認証に関しては一通り書き換えが出来ました。次はCRUDを対応していきます。

 

CRUD

Firebase SDKを使うにあたって共通するのがReferenceを使うということです。(画像アップロードでも使用しました)

A Reference represents a specific location in your Database and can be used for reading or writing data to that Database location.

(参照は、データベース内の特定の場所を表し、そのデータベースの場所へのデータの読み取りまたは書き込みに使用できます。 by Google 翻訳) 

このReferenceに対して.once, .update,.removeなどでデータベースの操作を行います。

 

始める前にsrc > firebase > firebase.jsに以下のimport文を追加します。

import "firebase/database";

 

Read

データの読み取りは.onceを使うことで可能です。

補足

.onもデータの読み取りですが、これは(子要素も含め)データベースに変更があるたびにトリガーされるため、リアルタイム性が必要なアプリ(チャットアプリなど)に使います。

今回はアクセスするときに一度だけデータを取得すればいいので.onceを使います。

 

actions > subjects.js > readSubjectメソッドのロジック部分を以下のように変更します。

const userId = firebase.auth().currentUser.uid;
const subjectObject = await firebase
  .database()
  .ref(`${userId}/subject`)
  .once("value");
const subjectList = Object.keys(subjectObject.val()).map(id => ({
  ...subjectObject.val()[id],
  id: id
}));
dispatch(readSubjectSuccess(subjectList));

.onceで取得したデータは.val()で中身のデータを取得できます。

 

これでログインすればデータが読み出せるはずです。

 

詳細画面のQ&Aについても同様に取得しましょう。

actions > subjects.js > getSubjectメソッドのロジック部分は以下のようになります。

const userId = firebase.auth().currentUser.uid;
const subject = await firebase
  .database()
  .ref(`${userId}/subject/${id}`)
  .once("value");
const questionAnswers = await firebase
  .database()
  .ref(`/${userId}/qa/${id}`)
  .once("value");
dispatch(getSubjectSuccess(id, subject.val(), questionAnswers.val()));

Subjectと同じようにfirebase.database().ref("パス").once("value")でデータを取得し、.val()で中身を取り出しています。

 

Readに関してはこれでOKです。

 

Create

データを新規作成する場合、.setがありますが、今回のように保存するオブジェクトのキーをIDにしたい場合はまずキー(ID)を取得して、その後.updateを使用する必要があります。

actions > subjects.js > createSubjectメソッド

const userId = firebase.auth().currentUser.uid;

// アップロード処理
const imageName = new Date().toISOString() + data.image[0].name;
const imageUrl = await uploadTaskPromise(data.image[0], imageName, userId);

const subject = {
  title: data.title,
  image: imageUrl,
  imageName: imageName
};
// キー(subjectId)を取得
const newSubjectKey = firebase
 .database()
 .ref(`${userId}/subject`)
 .push().key;

// 取得したキーを元に${userId}/subject/${newSubjectKey}にデータを保存
await firebase
 .database()
 .ref(`${userId}/subject/${newSubjectKey}`)
 .update(subject);

dispatch(createSubjectSuccess(data, newSubjectKey, imageUrl));
dispatch(createInitialQuestion(newSubjectKey));

firebase.database().ref(`${userId}/subject`).push().keyでキーを取得することで.updateで"userId/subject/subjectId"にデータを作成できます。

 

Q&Aに関しても同じように変更しましょう。ただしキーはsubjectのものを使用するため、.updateを使用するだけでOKです。

actions > questionAnswers.js > createInitialQuestionメソッドのロジック部分を変更します。

const userId = firebase.auth().currentUser.uid;
await firebase
  .database()
  .ref(`${userId}/qa/${id}`)
  .update(qas);

 

これで新規作成に関しては修正出来ています。

 

Update

データの新規作成では.updateを使用しました。データの更新でも同じように.updateを使用します。

actions > subjects.js > updateSubjectメソッドのロジック部分は以下のようになります。

const userId = firebase.auth().currentUser.uid;
let imageUrl = oldImageUrl; // 画像の更新がなければ元のURLをセットする.

// data.imageはFileListなので注意
if (data.image.length !== 0) {
  // 画像の更新がある場合
  const imageName = new Date().toISOString() + data.image[0].name;
  imageUrl = await uploadTaskPromise(data.image[0], imageName, userId);
  data.image = imageUrl;
  data.imageName = imageName;
  await firebase
    .database()
    .ref(`${userId}/subject/${data.id}`)
    .update(data);
  await storage.ref(`/images/${userId}/${oldImageName}`).delete();
} else {
  // 画像の更新がない場合
  delete data.image; // imageが空のFileListになっているので削除しておく
  await firebase
    .database()
    .ref(`${userId}/subject/${data.id}`)
    .update(data);
}

 

Q&Aに関しても同様です。

actions > questionAnswers.js > updateQuestionAnswers

export const updateQuestionAnswers = (id, data) => async dispatch => {
  try {
    const userId = firebase.auth().currentUser.uid;
    await firebase
      .database()
      .ref(`${userId}/qa/${id}`)
      .update(data);
    dispatch(updateQuestionAnswersSuccess());
    dispatch(showMessage("保存に成功しました。", "success"));
  } catch (error) {
    dispatch(apiFailed("update question failed"));
  }
};

const updateQuestionAnswersSuccess = () => {
  return {
    type: ACTION_TYPES.UPDATE_QUESTION_ANSWERS
  };
};

 

単にupdateメソッドを使うだけなので特別なことはありません。

 

編集が出来るか確認してみて下さい。

 

 

Delete

最後に削除を行います。

方法はシンプルでreferenceに対してremoveを呼び出すだけです。

 

actions > subjects.js > deleteSubjectメソッドのロジック部分は以下のようになります。

const userId = firebase.auth().currentUser.uid;
await firebase
  .database()
  .ref(`${userId}/subject/${id}`)
  .remove();

 

Q&Aに関しても同様です。

actions > questionAnswers.js > deleteQuestionAnswers

const userId = firebase.auth().currentUser.uid;
await firebase
  .database()
  .ref(`${userId}/qa/${id}`)
  .remove();

 

これで削除も完了です。

 

 

SDKへの修正完了

これでREST APIを使用したCRUD・認証をSDKを使って書き直すことが出来ました。

REST APIではトークンを自分で保存したり、通信の際に認証情報を付け加えたりと手間がかかりましたが、SDKを利用するとあらかじめ用意されたメソッドを利用するだけで済むということが分かっていただけたのではないでしょうか。

 

【実装編2】でもまとめた通り、SDKはこの他にもメリットがあり、他のFirebaseの機能との連携が取りやすいというメリットがあります。

 

最後にCloud Storageに認証をかけて、セキュリティ面を強化しましょう。

(今まで使っていたaxiosファイルやトークン関連のファイルは削除しておいて下さい。)

 

Cloud Storageで認証をかける

FirebaseコンソールのCloud Storageメニューからルールを編集し、認証情報がない場合にアップロードできないように変更します。

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

上の例は一例です。

ファイルサイズの制限なども出来ますので色々と試してみて下さい。

 

この状態でアプリを動かしてみて下さい。

 

今まで通り動作すると思いますし、認証されていなければアップロードはできないので今までよりもセキュアになっています。

 

 

次回は作ったアプリをデプロイし、実際にWeb上に公開してみたいと思います。

 

 

お楽しみに!

 

 

 

おまけ

Firebaseの認証情報ですが、ローカルに以下のように保存されており見ることが出来ます。

 

 

 

だから何?という程度の話ですが....

ローカルに保存されてますよ、という話。

 

この記事は参考になりましたか?

下記のボタンよりアンケートにご協力お願いします。

-Webプログラミング
-, , ,

© 2020 エンジニアの本棚