Nuxt.js+Firestoreの場合に安全にSSRする方法

Nuxt.jsをサーバーで動かしてFirestoreを使い、且つサーバーサイドレンダリングしたい時には色々考えることが出てきます。懸念点と対応した方がよさそうな点をまとめてみます。

SSRしない場合との違い

SSRせずクライアント上でFirestoreからデータを取る場合ですが、匿名認証にしろ、SNSアカウントの認証にしろ、認証しているかどうかでセキュリティルールによりデータを守りやすく、他者によるFirebaseプロジェクトの設定を盗用しての勝手な操作を防いだりすることができます。

しかし、asyncData内によるSSRとなると、そのクライアント側の認証情報を利用することはできません。そうなるとデータを守れないどころか、そもそもセキュリティルールが設定されているとデータにアクセスすることすらできません。こういう時にどうすれば良いかを考えます。

どのように解決するか

Cloud Functionsの利用

今回はCloud Functionsを利用する方法を考えました。具体的にどうするかというと、asyncData内ではfunctionsからデータを取るだけにします。例えば下記のような感じです。

  async asyncData({ params }) {  
    const response = await axios.get(functionUrl)  
    return response.data  
  }  

呼び出し方はどうであれ、通信が発生するので上記のようにリクエストは1回にした方が良いでしょう。functions側でそのページ専用のアクションを作ってデータを返します。

export const showUser = functions.https.onRequest((req, res) => {  
  return cors(req, res, async () => {  
    const user = await User.getUserByUsername(req.query.id)  
    res.json({  
      user,  
      posts: await Post.getPosts(user.uid, 5)  
    })  
  })  
})  

データの取得方法

しかし、これでは先程のasyncDataと同様、セキュリティルールの関係でデータが取れないのでは? と思われるかもしれません。ただ、Firebaseにはサーバー用のFirebase Admin SDKというものがあります。これはセキュリティルールに関係なくデータを扱うことができるため、これを利用します。具体的には下記のような形です。

import * as admin from 'firebase-admin'  

const firestore = admin.firestore()  
const usersCollection = firestore.collection('users')  

export async function getUser(uid: string) {  
  const querySnapshot = await usersCollection  
    .where('uid', '==', uid)  
    .get()  
    .catch(error => {  
      console.error(error)  
      return null  
    })  

  if (!querySnapshot || querySnapshot.size == 0) {  
    return null  
  }  

  const user = querySnapshot.docs[0].data()  
  user.id = querySnapshot.docs[0].id  
  return user  
}  

クライアント側とは微妙にcollectionの取り方が違うだけで、基本的にはほぼ同じです。このようにしてサーバーサイドレンダリングのためのデータ取得処理を行うことができます。

セキュリティルールを無視で良いのか?

今回の例のfunctions側でのデータ取得処理に関しては、ルールを無視で問題ありません。

というのも、そもそもSSRの目的としては、クローラに認識してもらうためのSEO対策になるかと思います。つまり、ユーザー認証が必要なデータを取る必要は一切無いということになります。あくまでも誰もが閲覧できるパブリックなデータを取るだけです。そのため基本的には認証に関するセキュリティルールをこの場で考慮する必要はありません。ただ、corsはちゃんと挟んでおきましょう。

まとめ

やってみて思いましたが、これって普通のサーバーを使った開発と同じですよね!? なんとなく手軽さが失われた感じはありますが、それでもまあサーバーを管理する必要が無いのは大きなメリットではあることに変わりはないでしょう。

他にも色々と方法はあるかもしれませんが、とにかくどの様な方法にしろ、どのようにデータを守るかはしっかり考えて構築が必要となります。

以前認証とセキュリティルールについて書いた考察もありますので、もし気が向いたらそちらもぜひ御覧ください。

Firebaseの匿名認証はなんのためにあるのか - セキュリティ編