# firebase auth の idToken をデコードして uid の他に表示名やログイン用のメールアドレスなどを取得する
# 背景
こちら とは違ってほぼドキュメント (opens new window)通りなのですが、ドキュメントのサンプルコード (opens new window) だと token とった後、どうやってユーザー情報とったらいいのかわかりにくいのと、token 構造体のドキュメントみて uid とれたとして、それ以外の表示名とかメールアドレスとかどうやってとるのかちょっとわからないです。サンプルコードだと idToken を検証する verifyIDTokenAndCheckRevoked (opens new window) の次が uid を引数にして auth client の GetUser を呼んでる getUser (opens new window) なのでつられてそんなことしたくなりそうですけど、そんなことしたら firebase との通信が発生してしまって何百ms も余計にかかってしまいます
# 正解のポイント
- uid は Token 構造体の UID に string で入ってる
- 表示名やメールアドレスは Token 構造体の Claims の奥に入ってる
# 手順
# 設定
ドキュメント (opens new window)どおりに firebase の SDK をインストールして
go get firebase.google.com/go
こちらでやったように サービスアカウント用秘密鍵ファイル を取得して設定します。すでに firestore のためにそうしてあれば、そのまま使えるので新たにすることはなにもないです
# idtoken を受けとってデコードする server
idToken のデコードは初期化が重いようで、初回が2回目以降より10倍ぐらい遅い(値は幅があるのですが、例えば scaleway の C1 server で初回が 800ms ぐらい、2回め以降が 65ms ぐらいだったりしました)ので、コマンドにしてしまうと毎回重くて仕方がないのでサーバにしておきます
gin だとこんなかんじでしょうか
package main
import (
"flag"
"log"
"fmt"
"context"
"github.com/UedaTakeyuki/erapse"
firebase "firebase.google.com/go"
"google.golang.org/api/option"
)
func main() {
// firebase
app := initializeAppWithServiceAccount()
// routes
r := gin.Default()
r.GET("/decodeIDToken/:idtoken", func(c *gin.Context) {
cntlDecodeIDToken(c, app /* *firebase.App */) // *firebase.App
})
r.Run()
}
func initializeAppWithServiceAccount() *firebase.App {
// [START initialize_app_service_account_golang]
opt := option.WithCredentialsFile("/path/to/the/keyfile/hogehoge.json")
app, err := firebase.NewApp(context.Background(), nil, opt)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
// [END initialize_app_service_account_golang]
return app
}
func cntlDecodeIDToken(c *gin.Context, app *firebase.App) {
defer erapse.ShowErapsedTIme(time.Now())
idtoken := c.Param("idtoken")
client, err := app.Auth(context.Background())
if err != nil {
log.Fatalf("error getting Auth client: %v\n", err)
}
token, err := client.VerifyIDToken(context.Background(), idtoken)
if err != nil {
log.Fatalf("error verifying ID token: %v\n", err)
}
log.Printf("Verified ID token: %+v\n", token)
// userrecord, err := client.GetUser(context.Background(), token.UID)
// log.Printf("UserRecord: %+v\n", userrecord)
c.JSON(200, gin.H{
"user_id": token.UID,
"display_name": token.Claims["name"],
"email": token.Claims["firebase"].(map[string]interface{})["identities"].(map[string]interface{})["email"].([]interface{})[0].(string),
})
}
WithCredentialsFile は こちらと同じで、このようにサービスアカウント用秘密鍵ファイルのパスを指定してもいいし、NewClient で環境変数から読み取ってもいいです
defer erapse.ShowErapsedTIme(time.Now()) はこれを関数の最初に書いておけばその関数の経過時間を標準エラー出力に表示してくれるので性能を意識するのに便利です、"github.com/UedaTakeyuki/erapse" を import して使います
VerifyIDToken で idToken をデコードします
# uid の取得
uid は、VerifyIDToken() (opens new window) の戻り値の token 構造体 (opens new window) のメンバ UID に入ってます
c.JSON(200, gin.H{
"user_id": token.UID,
"display_name": token.Claims["name"],
"email": token.Claims["firebase"].(map[string]interface{})["identities"].(map[string]interface{})["email"].([]interface{})[0].(string),
})
# そのほかのユーザ情報の取得
私は無知無学ゆえかドキュメントから読み取れなかったのですが、ジタバタしてたら Token 構造体の Claims メンバにはいっていました
表示名は Claims の name にはいっていたのですが
c.JSON(200, gin.H{
"user_id": token.UID,
"display_name": token.Claims["name"],
"email": token.Claims["firebase"].(map[string]interface{})["identities"].(map[string]interface{})["email"].([]interface{})[0].(string),
})
メール認証用のログインメールアドレスはちょっと深くてこんなかんじ
c.JSON(200, gin.H{
"user_id": token.UID,
"display_name": token.Claims["name"],
"email": token.Claims["firebase"].(map[string]interface{})["identities"].(map[string]interface{})["email"].([]interface{})[0].(string),
})
こういう時の go のタイプアサーションがうざくて、python が少し恋しくなったりします(バージョン間の差異がうざすぎて二度と戻りたくないですが)