2
1

More than 1 year has passed since last update.

GoサーバからFirestoreにアクセスするときに、セキュリティルールを有効にする

Last updated at Posted at 2021-12-16

TL;DR

サーバからFirestoreにアクセスするとき、ユーザのID Tokenを渡すことで、Firestoreのセキュリティルールにできる。

やりたいこと

image.png

奇妙な構成だが、仕事の事情でこういう風にしたい。

問題は、APIサーバからFirestoreにアクセスするとき、Firestoreのセキュリティルールが適用されないこと。APIサーバではFirebaseのadmin SDKを使うが、通常の使い方だと、サービスアカウントとしてFirestoreにアクセスすることになるため。

Firestoreのセキュリティルールに相当する認証や認可処理を自分で実装しないといけない。実装自体はそれほど手間でないのだが、Firestore側で認証/認可をするのに比べて、処理時間が結構かかる。

だから、APIサーバからFirestoreにアクセスするときに、Firestoreのセキュリティルールを適用させたい。

対処方針

Web FrontはFirebaseでユーザ認証しているので、ユーザのID Tokenは取得できる。このID TokenをWeb Front → APIサーバ → Firestoreの流れで送ることで、ユーザとしてFirestoreにアクセスすることになり、Firestoreのセキュリティルールが適用されるようになる。

Firebase SDKを使いつつ、セキュリティルールを有効にする方法

ctx := context.Background()

// 重要
// WithoutAuthenticationとWithGRPCDialOptionを指定する。
// WithoutAuthenticationがないとID Tokenが無視される。WithoutAuthenticationをつけるなら、WithTransportCredentialsもないとエラーになる。
app, _ := firebase.NewApp(ctx,
    &firebase.Config{ProjectID: "your-project-id"},
    option.WithoutAuthentication(),
    option.WithGRPCDialOption(grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, ""))),
)
client, _ := app.Firestore(ctx)

h := client.Collection("your-collection-id").
    Limit(10)

// 重要
// metadataにFirebaseのID Tokenを積む
ctx = metadata.AppendToOutgoingContext(ctx, "Authorization", "Bearer "+idToken)
it := h.Documents(ctx)

docs, _ := it.GetAll()

for _, doc := range docs {
    data := doc.Data()
    fmt.Println(data)
}

気になる点は、WithoutAuthenticationを指定している点。このオプションのDocを見ると、テストか公開されているデータへのアクセス時に指定すべき、と書かれている。この使い方が「公開されているデータへのアクセス時」に該当するのかがわからない(インターネットからアクセスできるので、公開されているとみなして良いと思っている)。

Firebase SDKを使わないで、セキュリティルールを有効にする方法

Firebase SDKではなく、生のFirestoreのgRPC APIを使う。実装例を示す。

まず、生のFirestoreのgRPC APIを使うためのモジュールをimport。

import (
    "context"
    "fmt"
    firestore "google.golang.org/genproto/googleapis/firestore/v1beta1"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"
    "google.golang.org/protobuf/types/known/wrapperspb"
    "io"
    "log"
)

生のgRPC APIを使ってFirestoreからデータをReadする。

address := "firestore.googleapis.com:443"
conn, _ := grpc.Dial(address, grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")))
defer conn.Close()

client := firestore.NewFirestoreClient(conn)

ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
// 重要
// gRPCリクエストのmetadataに、ID Tokenを積む
ctx = metadata.AppendToOutgoingContext(ctx, "Authorization", "Bearer "+idToken)

stream, _ := client.RunQuery(ctx, &firestore.RunQueryRequest{
    Parent: "projects/your-project-id/databases/(default)/documents",
    QueryType: &firestore.RunQueryRequest_StructuredQuery{
        StructuredQuery: &firestore.StructuredQuery{
            From: []*firestore.StructuredQuery_CollectionSelector{
                {
                    CollectionId:   "your-collection-id",
                    AllDescendants: false,
                },
            },
            Limit: &wrapperspb.Int32Value{Value: 10},
        },
    },
})
defer cancel()

var docs []*firestore.Document
for {
    res, err := stream.Recv()
    if err == io.EOF {
        break
    }

    if err != nil {
        log.Fatalln(err)
    }

    if res.Document != nil {
        docs = append(docs, res.Document)
    }
}

for _, doc := range docs {
    fmt.Println(doc)
}
  • 難点
    1. Firestoreのクエリを組み立てるのが面倒くさい
    2. このコードだと単に件数指定しているだけだが、取得範囲指定、Where、OrderByなどを指定するのがとても面倒くさい
    3. Firestoreのレスポンスを使いやすい形に変換するが面倒くさい
  • 補足
    • client.RunQueryというgRPC APIを使っているが、Firebase SDKでコレクションを取得するときにも使われている
    • SDKは色々と楽させてくれるだけで、最終的には生のgRPC APIを使っている

参考にした資料

2
1
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
2
1