ReactNativeでFirebase JS SDKのユーザー情報が保存される場所を確認


ニッチな記事なので、メモ的な形式ですみません。

firebase-js-sdkの実装を確認

この辺りで環境ごとに使用するストレージの種類を定義している
https://github.com/firebase/firebase-js-sdk/blob/6b53e0058483c9002d2fe56119f86fc9fb96b56c/packages/auth/src/storage/factory.js#L79
https://github.com/firebase/firebase-js-sdk/blob/6b53e0058483c9002d2fe56119f86fc9fb96b56c/packages/auth/src/authstorage.js#L181
https://github.com/firebase/firebase-js-sdk/blob/6b53e0058483c9002d2fe56119f86fc9fb96b56c/packages/auth/src/utils.js#L679
環境を特定している処理を遡るとどこまでいくのかわからないけど、ともかくReactNative環境ではAsyncStorageを使用している。

fireauth.storage.AsyncStorage
https://github.com/firebase/firebase-js-sdk/blob/6b53e0058483c9002d2fe56119f86fc9fb96b56c/packages/auth/src/storage/asyncstorage.js

ユーザーの認証情報はこの辺りでJSON化してストレージに保存している。
https://github.com/firebase/firebase-js-sdk/blob/6b53e0058483c9002d2fe56119f86fc9fb96b56c/packages/auth/src/storageusermanager.js#L374

AsyncStorageの実装を確認

Android

AsyncStorageModule.java
https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modules/storage/AsyncStorageModule.java
ReactDatabaseSupplier.java
https://github.com/facebook/react-native/blob/aee88b6843cea63d6aa0b5879ad6ef9da4701846/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java
SQLiteを使用している。

DB名はRKStorageに、テーブル名はcatalystLocalStorageなっているはず。
https://github.com/facebook/react-native/blob/aee88b6843cea63d6aa0b5879ad6ef9da4701846/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java#L25

iOS

RCTAsyncLocalStorage.mm
https://github.com/facebook/react-native/blob/652fa1b8d485ef86bc332116b9790ef61b905a0a/React/CoreModules/RCTAsyncLocalStorage.mm
https://github.com/facebook/react-native/blob/652fa1b8d485ef86bc332116b9790ef61b905a0a/React/CoreModules/RCTAsyncLocalStorage.mm#L81
writeToFileでAppのDocumentsディレクトリに保存しているのがわかる。

https://github.com/facebook/react-native/blob/652fa1b8d485ef86bc332116b9790ef61b905a0a/React/CoreModules/RCTAsyncLocalStorage.mm#L22
/Documents/RCTAsyncLocalStorage_V1ディレクトリが用意される。

実機から内容を読み取ってみる

このような感じで、Firebase Authenticationでメールアドレス・パスワードによるログインをするだけの画面を用意し、確認してみました。
firebase.initializeAppは割愛します。

App.js
import React, { Component } from 'react';
import { StyleSheet, Text, View, AsyncStorage } from 'react-native';
import firebase from './js/firebase';

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentUser: null,
      storageData: null
    };
    firebase.auth().onAuthStateChanged(async (currentUser) => {
      console.log(currentUser);
      this.setState({ currentUser });
      if (currentUser && !this.state.storageData) {
        const keys = await AsyncStorage.getAllKeys();
        const values = await Promise.all(keys.map((key) => (AsyncStorage.getItem(key))));
        this.setState({
          storageData: JSON.stringify({ values, keys })
        });
      }
    });
    firebase.auth().signInWithEmailAndPassword('[email protected]', 'password');
  }

  render() {
    const { storageData } = this.state;
    return (
      <View style={styles.container}>
        <Text>{storageData}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

Android

端末をPCと繋いで、adbを使ってアプリのデータを確認します。

$ adb shell
[ユーザ名]:/ $

アプリのデータは/data/data/[パッケージ名]ディレクトリに入っています。

[ユーザ名]:/ $ cd data/data
[ユーザ名]:/data/data $ cd com.expofirebaseauthstoragetest
/system/bin/sh: cd: /data/data/com.expofirebaseauthstoragetest: Permission denied
[ユーザ名]:/data/data $

このようにそのままだとPermission deniedになってしまうので、アプリの権限でアクセスする必要があります。
参考:[Android] アプリの内部メモリを覗くとパーミッションでブロックされる

[ユーザ名]:/data/data $ run-as com.expofirebaseauthstoragetest
[ユーザ名]:/data/data/com.expofirebaseauthstoragetest $

ただし、リリースビルドのアプリではrun-asコマンドを使ってもアクセスできませんでした。
デバッグビルドかつandroid:debuggable="true"の場合のみ内部ファイルにアクセスできるようです。
そのため、Expoを使わず素のReactNativeアプリで確認しています。

[ユーザ名]:/data/data/com.expofirebaseauthstoragetest $ cd databases/
[ユーザ名]:/data/data/com.expofirebaseauthstoragetest/databases $ ls
RKStorage

RKStorageというデータベースが確認できました。とりあえずこれをコピーして、PCで中身を表示してみます。

[ユーザ名]:/data/data/com.expofirebaseauthstoragetest/databases $ cp RKStorage /mnt/sdcard
[ユーザ名]:/data/data/com.expofirebaseauthstoragetest/databases $ exit
$ adb pull /mnt/sdcard/RKStorage

SDカードのディレクトリは機種によって違うかもしれません。
adb pullによりRKStorageをPCに移動できたら、DBの中身をDB Browser for SQLite等のソフトで確認します。

catalystLocalStorageテーブルがあり、firebase:authUser:<API Key>:[DEFAULT]キーにJSON形式でユーザー情報が入っているのがわかります。
ちなみに、この形式はWebでIndexedDBに入っているときの形と同様です。

iOS

端末とMacを繋げ、Xcodeでアプリを起動してFirebaseへのログインが完了したら、
iExplorerを使ってアプリ内のファイルを確認します。

開発時なので見れていますが、リリースビルドのアプリ内のディレクトリはinfo.plistUIFileSharingEnabled(iTunesでファイルを共有できる設定)がYESでない限り表示できないようです。例えばGmailなどはこんな感じ↓

先ほどのアプリのディレクトリを見てみると、

RCTAsyncLocalStorage_V1ディレクトリにmanifest.jsonと、ランダム文字列のように見える名前のファイルがあります。

manifest.jsonの方はこんな感じ。

manifest.json
{"firebase:authUser:<API Key>:[DEFAULT]":null}

key/value形式になっていますが、ランダム文字列のような名前のファイルの方に実際のデータが入っていました。
このファイル名は上記のkeyをMD5でハッシュ化したもののようです。

a4f088f2e38522fd210d0ae5995f904c
{"uid":"xxxxxxxxxxx","displayName":null,"photoURL":null,"email":"[email protected]","emailVerified":false,"phoneNumber":null,"isAnonymous":false,"tenantId":null,"providerData":...

この最大文字数(1024字)を超えるものが別ファイルになり、それ以下の場合はmanifest.jsonにそのまま値を入れるという実装になっています。
https://github.com/facebook/react-native/blob/652fa1b8d485ef86bc332116b9790ef61b905a0a/React/CoreModules/RCTAsyncLocalStorage.mm#L24

まとめ

Android

  • ストレージの実装:SQLiteを使用
    • DB名:RKStorage
    • テーブル名:catalystLocalStorage
  • アクセス制限:デバッグビルドかつandroid:debuggable="true"のときのみ

iOS

  • ストレージの実装:アプリのDocumentsディレクトリにファイルとして保存
    • /Documents/RCTAsyncLocalStorage_V1/manifest.jsonにキーがあり、値は別ファイル(キーをMD5でハッシュ化したファイル名)に分かれている
  • アクセス制限:デバッグビルド、あるいはUIFileSharingEnabled:YESのときのみ