firebase auth の idToken をデコードして uid の他に表示名やログイン用のメールアドレスなどを取得する

背景

こちら とは違ってほぼドキュメント通りなのですが、ドキュメントのサンプルコード だと token とった後、どうやってユーザー情報とったらいいのかわかりにくいのと、token 構造体のドキュメントみて uid とれたとして、それ以外の表示名とかメールアドレスとかどうやってとるのかちょっとわからないです。サンプルコードだと idToken を検証する verifyIDTokenAndCheckRevoked の次が uid を引数にして auth client の GetUser を呼んでる getUser なのでつられてそんなことしたくなりそうですけど、そんなことしたら firebase との通信が発生してしまって何百ms も余計にかかってしまいます

正解のポイント

  1. uid は Token 構造体の UID に string で入ってる
  2. 表示名やメールアドレスは Token 構造体の Claims の奥に入ってる

手順

設定

ドキュメントどおりに 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() の戻り値の token 構造体 のメンバ 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 が少し恋しくなったりします(バージョン間の差異がうざすぎて二度と戻りたくないですが)


Last Updated: 7/11/2020, 4:15:20 AM