１回のGoogleログインで、モバイルアプリ・自社サーバー・Googleサービス・Firebaseを連携させるフロー

@eaglesakura です。

アプリ（Android/iOS）とFirebaseはFirebase Authを使うと簡単に認証を行えます。

ですが、単純にFirebase Authだけを使うと、自社サーバー（Appengine含む）からユーザー権限でAPIを呼び出せず（例えばユーザーのGoogle Driveにアクセスする、等）、サーバーとアプリ両方の認証が必要になるなどユーザーとしては認証が複数回発生して煩雑になります。

自社サーバーとGoogle APIとFirebaseを連携させ、なおかつユーザーフレンドリーさを保つために「一度のログインで全リソースへのアクセスを行なう」ために必要な処理をざっくりとまとめました。

SDKが整備されている環境・言語（node.jsとかJavaとか）であれば苦労なく行えますが、整備されていない場合や一部だけ実装する必要がある場合等は参考になるかと思います。

# アプリからFirebaseへログイン

## Firebase Authの制限

アプリからのログインは基本的に、 [Firebase Authentication](https://firebase.google.com/docs/auth/) を利用することで簡単に行なえます。

ただし、これはアプリ <--> Firebase間の連携だけを前提とした場合です。ログインされたユーザーにはFirebaseが自動的にUnique IDを割り振ります。また、対応しているのはFirebaseがサポートしているプロバイダ(Google, Facebook, Twitter等）だけです。

そのため、LINE等の別サービスを組み合わせる場合、もしくは別なサービスのID/PASSでログインさせる場合等はこの方法を使えません。

## Custom Tokenによる認証

そこで、Firebaseと自社サーバーを連携させることにします。
[カスタム認証システム](https://firebase.google.com/docs/auth/android/custom-auth)に対応すると、次のような処理も行えます。

 * ログインの可否を自社サーバーでハンドリングできる
 * 任意のユーザーIDを割り振れる(ただし文字数制限あり)
 * 認証にカスタム情報を付与できる
    * 例えば、 `is_eaglesakura:true` のようにカスタマイズした情報を付与でき、それを [Firebase Rules](https://firebase.google.com/docs/database/security/?hl=ja)からも参照できる

## JWTによるトークン発行元証明

この記事のフローで重要になるキーワードがJWT(Json Web Token)です。

JWT自体の詳細についてはggると大量にでてきます。JWTは非常に長い文字列で、大まかに覚えておきたいのは次の特性です

 1. ヘッダ・ユーザー情報・署名を持つ
 1. 有効期限がある
 1. 暗号化はされていないので誰でも内容を見ることができる
 1. 署名をされているので、改ざんを検出できる
 1. URL-SAFEであるため、扱いやすい

JWTはそれ自体に署名がついているため、「誰が生成したトークンであるか」を証明することができます。また、有効期限と署名解析時間の問題から、発行 ～ 有効期限切れまでの間に鍵が解析される恐れは（現代のマシンスペックでは）まずないでしょう。

Firebase Custom TokenはJWTを利用して「事前に登録してあるサーバーが発行したトークンである」ことを確認し、ログイン制御（必要に応じてユーザー作成）を行います。

 1. 自社サーバーは、非公開の秘密鍵を持つ
 1. 自社サーバーは、JWTに秘密鍵で署名する
 1. JWTを受け取ったFirebaseは、署名をチェックしてJWTが正規ルートで作成されたトークンであることを確認する

## Firebaseサービスアカウントの作成

Custom Tokenを使ったログインを行なうためには、Firebaseのサービスアカウントを作成する必要があります。

[Firebase Console](https://console.firebase.google.com)からプロジェクトを開き、Overview > プロジェクトの設定 > サービスアカウント > Firebase Admin SDKからサービスアカウントを作成し、秘密鍵（JSONファイル）をダウンロードします。

この秘密鍵情報はダウンロードする度に変更になるので、紛失したら素直に再発行するしかありません。

![firebase-consoke2.png](https://qiita-image-store.s3.amazonaws.com/0/15670/abbb62d9-2cb6-1f15-9732-b5d05c3c35d5.png)


JSONには次のような情報が書き込まれています。

```json
	{
	  "type": "service_account",
	  "project_id": "プロジェクト名",
	  "private_key_id": "fa9a899ee2909bd96321a200b124dbb6639ea63d",
	  "private_key": "長ったらしい秘密鍵の文字列",
	  "client_email": "firebase-adminsdk@example-project.iam.gserviceaccount.com",
	  "client_id": "123456789012345678901",
	  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
	  "token_uri": "https://accounts.google.com/o/oauth2/token",
	  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
	  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk%40example-project.iam.gserviceaccount.com"
	}
```

## 署名付きJWTの作成

JWTは広く使われているので、様々な言語でライブラリが存在しています。 [jwt.io](https://jwt.io/)のサイトにアクセスすると、JWTのデコードや各言語のライブラリへの誘導をしてくれるので、覚えておくと開発・検証が楽になります。

Golangであれば、[github.com/dgrijalva/jwt-go](https://github.com/dgrijalva/jwt-go) が広く使われているようです。

```go

type TokenSource struct {
	jwt.StandardClaims
	Uid    string`json:"uid,omitempty"`
	Claims map[string]interface{}`json:"claims,omitempty"`
}
```

```go
source := TokenSource{
	StandardClaims:jwt.StandardClaims{
		ExpiresAt: time.Now().Unix() + 3600,
		IssuedAt:time.Now().Unix(),
		Audience:"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
		Issuer:"サービスアカウントのメールアドレス",
		Subject:"サービスアカウントのメールアドレス",
	},
	Uid:"FirebaseのユーザーID、任意で指定可能",
	Claims:map[string]interface{},
}

// ユーザーに任意の情報を持たせるときはこうする
source.Claims["is_eaglesakura"] = true

// 秘密鍵のロード
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte("秘密鍵の文字列"));

// JWTの生成
jwtToken := jwt.NewWithClaims(jwt.SigningMethodRS256, source)

// 署名したJWTの文字列を生成
signed, err := jwtToken.SignedString(privateKey)
```

生成すると、次のような文字列が生成されます。"."で区切られた３つのブロックで、jwt.ioで表示させると中身が丸見えであることが確認できます。ただし、署名を付与しているためよほどの計算資源を投入しなければ改ざんはできません。

```
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQi ...中略... bWFpbCJ9.fSTqP1G1N ...中略... 2388cIWwR1vjqSLF7q8RNrkv1w
```


![jwt.png](https://qiita-image-store.s3.amazonaws.com/0/15670/a7eafe21-0980-322a-4c1a-51847b2a13c6.png)

## Androidからのログイン

正しいJWTを生成できれば、アプリからのログインは簡単です。
[ドキュメントに従い](https://firebase.google.com/docs/auth/android/custom-auth?hl=ja)、Firebase SDKに対してトークンを渡せばログインは完了します。

```java
FirebaseAuth.getInstance().signInWithCustomToken(token)
```

ログインが正常完了すると、ユーザーIDがサーバーに登録されます。このとき、Firebase Authの標準プロバイダーとは違い、メールアドレスを登録することはできません（それっぽい幾つかのClaimを試しましたが、どれも認識してくれませんでした）

![Custom Token Login](https://qiita-image-store.s3.amazonaws.com/0/15670/1829f552-5a02-d229-090a-76fd7e1739d9.png)

# Googleログインと連携する

FirebaseのユーザーUIDハンドリングを自社サーバーで行えるようになりましたが、逆に言えばログイン情報の正当性を自社サーバーが保証しなければなりません。

例えば自社で発行したID/PASSを使っている場合はDBに問い合わせる等の処理が必要になりますが、それは負荷の大きな処理です（自社でID/PASSを管理するコストは非常に高い）。

そこで、ユーザーのチェックをGoogleサーバーに行わせることにします。Android/iOS共に、GoogleアカウントによるログインはSDKが提供されているため簡単に実装できます。また、SDKを経由してGoogle標準のログイン画面を出したほうが、ユーザーの信頼という意味で適切です。

例えば、アプリに表示されたログイン画面が、悪意あるフィッシングサイトでないことを、WebViewではユーザーが確認できません（URLが見えないので）。

Googleログインについても、Firebaseのコンソールを利用すると簡単に事前設定を行ってくれます。Overview > プロジェクトの設定 > 全般 > アプリ から、アプリのpackage名とkeystoreのSHA1（Androidの場合）を登録します。SHA1フィンガープリントはオプション扱いですが、未登録の場合はGoogleログインに失敗するので注意してください。

![register.png](https://qiita-image-store.s3.amazonaws.com/0/15670/3cd05362-1edf-6d5c-b699-e0cd23239f5e.png)

アプリ登録 > google-services.json をダウンロードしたあとの実装については [google-services-plugin ドキュメント](https://developers.google.com/android/guides/google-services-plugin) を参考にしてください。

実装後、ログインする際に渡すGoogleApiClient.Builderの最低限の設定は次のようになります。

```java
    public static GoogleApiClient.Builder newFullPermissionClient(Context context) {
        GoogleSignInOptions options = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(context.getString(R.string.default_web_client_id))
                .requestEmail()
                .build();
        return new GoogleApiClient.Builder(context)
                .addApi(Auth.GOOGLE_SIGN_IN_API, options)
                ;
    }
```

ログインすると、Googleからトークンが返却されます。

```java
    @OnActivityResult(REQUEST_GOOGLE_AUTH)
    void resultGoogleAuth(int result, Intent data) {
        GoogleSignInResult signInResult = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        if (signInResult.isSuccess()) {
            GoogleSignInAccount signInAccount = signInResult.getSignInAccount();
            // Googleが発行したJWTを得る
            String idToken = signInAccount.getIdToken();
        } else {
            // ログイン失敗...
        }
    }
```

Firebase Authの標準実装では得られたidTokenをFirebase SDKに直接渡しますが、Custom Tokenを使う場合は一旦自社サーバーに送り、自社サーバーのログイン処理を行わせます（例えば、初回ログインであればユーザー作成処理等）。

ちなみに、Googleが返却される値の中にある `idToken` `id_token` はJWTのフォーマットに従っているため、簡単にデコードが行なえます。

## サーバーでGoogleのIdTokenを検証する

アプリからサーバーに送られてきたJWTが正しいものかどうか、自前で検証しなければなりません。幾つかの言語ではSDKが用意されていますが、それ以外の環境・言語でもGoogleが公開しているAPIによって検証を行えます。

```go
// https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={IdToken}
// 上記APIで200が返ってくれば、Googleが発行したJWTであることが保証される
resp, err := urlfetch.Client(ctx).Get("https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=" + googleLoginToken)

// respを検証する...
```

200である場合、返却されるJSONは次のようなフォーマットになります。
IdToken(JWT)が改ざんされている場合や期限切れの場合、エラーが返却されます。

```json
{
 "iss": "https://accounts.google.com",
 "iat": "1487646761",
 "exp": "1487650361",
 "aud": "hogehoge.apps.googleusercontent.com",
 "sub": "108644137959533113614",
 "email_verified": "true",
 "azp": "fugafuga.apps.googleusercontent.com",
 "email": "example@gmail.com",
 "name": "Takeshi Yamashita",
 "picture": "https://lh5.googleusercontent.com/path/to/picture/photo.jpg",
 "given_name": "Takeshi",
 "family_name": "Yamashita",
 "locale": "ja",
 "alg": "RS256",
 "kid": "926d60cd055911f75f324adde1708d932f9595af"
}
```

`email` `name` の値を見れば、ユーザーのメールアドレス等が得られるため、ユーザーの初期情報として十分かと思われます。

ただし、この値は素通りさせてはいけません。絶対に `aud` の値を検証するようにしてください。これは「誰に対して発行されたトークンであるか」を示します。ここで見覚えのないIDを素通りさせてしまうと、攻撃者が作った適当なトークンでログインされてしまう恐れがあります。

 1. 攻撃者が適当なGCPプロジェクトを用意しする
 2. 攻撃者が[1]で作った適当なアプリからログインし、正規ルートでGoogleのIdTokenを手に入れる
 3. 攻撃者が[2]で得たIdTokenを使ってログインを行なう
 4. `aud` のチェックをしない場合、[3]でログインが行えてしまう

Googleが用意しているSDKを経由すれば内部で `aud` の検証を行ってくれますが、自力で検証する場合は忘れずに行なうようにしましょう。

ユーザーの妥当性がチェックできたら、前述のCustom Token用JWTを発行してアプリに返却し、アプリ側でCustom Tokenによるログインを行わせれば完了です。

# Firebase認証情報を自社サーバーで利用する

これでFirebaseの認証・ユーザー管理を自社サーバーが全てハンドリングできるようになりました。

次に問題となるのは、ログイン後の継続した自社サーバーとアプリとの通信認証です。
多くの場合、ログイン後、自社サーバーが発行したトークンをアプリ内に保存し、通信時にHttpのヘッダに乗せることで認証を行なうことが多いかと思います。

AndroidやiOSでは基本的に一度アプリでログインしたあとはずっとログインしっぱなしであるため、何度もログインさせることは（セキュリティ上の必要な場合を除けば）望ましくありません。そのため、ログイン済の通信トークンを端末のどこかに置いておく必要があります。

JWTはトークン改ざんを検出できるためかなり安全な通信トークンとなりますが、一般的には有効期限が短い（１時間）ため、定期的な再発行が必要になります。ですが、再発行のために必要なGoogleログイン情報も１時間で有効期限が切れるため、再利用ができません。

GoogleのOAuthのようにrefresh_tokenを用いて定期的にトークンを交換させる場合はわざわざAPIを設ける必要があり、コストが高くなります。また、永続利用可能な通信トークンをサーバーから発行した場合でも、アプリ側で「安全にトークンを保存する」というコードを記述しなければなりません。

手軽にこれらの問題を解決するため、Firebase Authのトークン管理の仕組みを拝借してみましょう。Firebase SDKは内部でトークンのリフレッシュを行ってくれるため、Firebaseの通信トークンをそのまま自社サーバーの通信トークンとして流用することができます。

また、アプリ内でのトークン管理もFirebase SDKに任せることができます。

## Firebaseの通信トークンを得る

Androidの場合、トークンの取得は簡単です。
取得したトークンはJWTのフォーマットで、なおかつ有効期限が１時間です。この情報をhttpの `Authorization` ヘッダに乗せるなど、適当な手段でAPIのパラメータと一緒に送ります。

このトークンは結構長い文字列なので、ヘッダに載せきれない場合はquery stringに乗せる等、適当な代替手段を考える必要があるかもしれません。

```java
// Tokenを取得する
// 実際には非同期処理なので、取得待ちを行なう必要がある
FirebaseAuthorizeManager.getInstance().getToken(true);
```

Firebase SDKのgetToken()から得られるJWTは次のようになります。

![custom token.png](https://qiita-image-store.s3.amazonaws.com/0/15670/10e4a2a6-f1e7-b068-1eb4-113495acb401.png)


## サーバーでFirebase Tokenの検証を行なう

サーバーに送られてきたトークンは、「Googleが、自分のFirebaseプロジェクトのために署名したデータである」ということを確認しなければなりません。

`自分のFirebaseプロジェクトのために署名した` ことは、JWTの `aud` や `iss` をチェックすることで確認できます。次に、そもそも送られてきたトークンがGoogleが署名したデータであることを検証しなければなりません。

そのときに使用するのが、ヘッダ部に付与されている `kid` の値と、Googleの公開鍵サーバーです。FirebaseのTokenは、Googleが持つ秘密鍵で署名されるため、Googleの公開鍵サーバーから公開鍵を取得すれば署名が正しいことをチェックできます。

公開鍵の取得は、次のURLで行えます。

 * https://www.googleapis.com/robot/v1/metadata/x509/{サービスアカウントのメールアドレス}
 * Googleが署名するデータを検証する場合は次のアドレスで固定
    * https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com


```json
{
 "06cc632ebf3d40d879839fe44af4cdddf6b56c57": "-----BEGIN CERTIFICATE----- ...中略 ...-----END CERTIFICATE-----\n",
 "3d7f476544bf10f0024de011f19b6973c210c7ff": "-----BEGIN CERTIFICATE----- ...中略 ...-----END CERTIFICATE-----\n",
 "1328564e372a3c5829cfc11895453e157f88dd48": "-----BEGIN CERTIFICATE----- ...中略 ...-----END CERTIFICATE-----\n",
 "dd624715cdffb176a7c5d80d3e748282a811f7fb": "-----BEGIN CERTIFICATE----- ...中略 ...-----END CERTIFICATE-----\n"
}
```

先程のスクリーンショットの例だと、 `06cc632ebf3d40d879839fe44af4cdddf6b56c57` というkidで署名されているので、 `06cc632ebf3d40d879839fe44af4cdddf6b56c57` とペアになっている公開鍵で検証を行えます。

最初にCustom Tokenを作成したときの署名情報も、このAPIを使って公開鍵を得ることができます（自分のサービスアカウントのURLを試してみてください）。

注意点として、Googleの公開鍵は定期的に追加・削除されてしまいます（JWT自体が１時間しか有効期限が無いため、基本的に困りません）。ですので、Unit Test等で「この鍵が存在しているはず」とか、「アプリにGoogle公開鍵一覧を内蔵する」ような実装は避けて、正しく鍵を得るようにしましょう。

Googleの公開鍵は生存期間が非常に短いので、オンメモリでキャッシュし、見知らぬ `kid` が来たらリロードで問題ないかと思います。

署名の検証は、jwt-goの場合だと次のように行います。

```go
rawToken, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) {
	kid := token.Header["kid"].(string)
	return jwt.ParseRSAPublicKeyFromPEM([]byte("kidに対応した公開鍵の文字列"))
})

// Verifyに失敗してもrawTokenは返却されるので、errorをチェックする
if strings.Contains(err.Error(), "crypto/rsa") {
	// Verify error
	return nil, newTokenError(err)
}

// TODO: audをチェックする

// 問題ない鍵ならば、`user_id`の値からユーザーとして扱う
```

これで、通信トークンの問題も解決できました。
自社サーバーとの通信はそこまで気を使わなくても力技でなんとかなると思うので、これは一例だと思っていてください。

# 自社サーバーからFirebaseのリソースにアクセスする

Firebase SDKが用意されているプラットフォームであれば、自由にリソースアクセスできるので、それを使いましょう。
もしSDKが用意されていない場合、REST APIを使ってアクセスすることができます。

## Firebase Databaseへサービスアカウント権限でアクセスする

自社サービスでFirebaseと連携する場合で、かつSDKが用意されていない言語やプラットフォームであれば、APIを直接利用してデータベースやストレージへアクセスしなければなりません。

GoogleのAPI全体のルールとして、認証はJWTのトークンではなく、OAuth2のトークンを利用する必要があります。
ですので、自社サーバーからFirebaseへアクセスするためには、先程作ったJWTトークンをサービスアカウント用のOAuth2トークンに交換しなければなりません。

サービスアカウントでトークンを取得するのは非常に簡単で、Googleが規定するデータをJWTのフォーマットでPOSTしてあげます。

Scopeは使いたい機能によって様々ですが、Firebase Databaseにアクセスしたい場合は `https://www.googleapis.com/auth/firebase https://www.googleapis.com/auth/userinfo.email` の２つが必須です。
複数個のスコープが必要な場合は、半角スペースで区切ります。

```go

type TokenSource struct {
	jwt.StandardClaims
	Scope  string`json:"scope,omitempty"`
	Claims map[string]string`json:"claims,omitempty"`
}
```

```go

source:TokenSource{
	StandardClaims:jwt.StandardClaims{
		ExpiresAt: time.Now().Unix() + 3600,
		IssuedAt:time.Now().Unix(),
		Audience:"https://www.googleapis.com/oauth2/v4/token",
		Issuer:"サービスアカウントのメールアドレス",
		Subject:"サービスアカウントのメールアドレス",
	},
	Scope:"https://www.googleapis.com/auth/firebase https://www.googleapis.com/auth/userinfo.email",
	Claims:map[string]string{},
}

// jwtで変換してサービスアカウントの鍵で署名する...

// 署名後のjwtをPOSTする
values := url.Values{}
values.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
values.Add("assertion", 署名済jwt文字列)
resp, err := urlfetch.Client(it.context).PostForm("https://www.googleapis.com/oauth2/v4/token", values)

// 200であれば、access_tokenが手に入る

```

得られたTokenは、`Authorization` ヘッダに `Bearer ${access_token}` として与えれば、Firebase Databaseにアクセスすることができます。このトークンはService Accountなので、DatabaseのRulesを無視したあらゆるリソースにアクセス・書き換えを行えます。

このトークンの有効期限も１時間です。すぐに使えなくなるため、オンメモリキャッシュに保存したり、Memcache等の安価な一時領域に置いておくと良いかと思います。

```bash
curl -H "Authorization: Bearer {access_token}" https://{プロジェクト名}.firebaseio.com/.json
```

## Firebase Storageにサービスアカウント権限でアクセスする

Firebase StorageはGoogle Cloud Storageと統合されています。
サービスアカウント側からはGCSのAPIでアクセスし、アプリ側からはFirebase SDKを経由したアクセスをすると管理が楽になるかと思います。

自社サーバーからアクセスする場合、スコープに `https://www.googleapis.com/auth/cloud-platform` を加えます。また、GCSのAPIで指定するバケット名は `{プロジェクト名}.appspot.com` で固定となります。

API一覧は [公式ドキュメント](https://cloud.google.com/storage/docs/json_api/v1/) を確認してください。

例えば、Firebase Storageに格納されているファイル一覧を取得するには次のように使います。

```bash
curl -H "Authorization: Bearer {access_token}"  https://www.googleapis.com/
storage/v1/b/{firebaseプロジェクト名}.appspot.com/o
```

# 自社サーバーからユーザーのGoogleアカウントリソースにアクセスする

自社サーバーで定期的な処理を行いたい場合（例えばユーザーのGoogle Driveにバックアップする等）は、自社サーバーからユーザーリソースへのアクセス手段を確保しなければなりません。

前述のように、GoogleのAPIアクセスはOAuth2のaccess_tokenに統一されています。サーバーのJWTやユーザーがGoogleから受け取ったJWTからOAuth2のaccess_tokenを生成できれば楽ですが、残念ながら直接的な手段が提供されていないので、少し遠回りして作成する必要があります。

GoogleログインのSDKには、オプションとして「サーバー用の認証コードを作成する」という項目が用意されており、それを利用することでOAuth2トークン取得が可能になります。

## アプリでの処理追加

Firebaseからアプリ情報を作成すると、一緒に `WebClient` という情報がGCPの認証情報に登録されます。
自動的に生成されるこのクライアントIDとクライアントシークレットをそれぞれアプリに追記します。

ありがたいことに、サーバー側にOAuthのコールバック用ハンドラを記述する必要はなく、SDKが全て行ってくれます。

![google web client2.png](https://qiita-image-store.s3.amazonaws.com/0/15670/0f6946e7-dbc2-2d38-fb9a-d7aa6d544b6a.png)

![web-client.png](https://qiita-image-store.s3.amazonaws.com/0/15670/068c744f-d0b4-6ad4-88bb-e593586d2671.png)

```java
    // builderにrequestServerAuthCodeを追加する
    public static GoogleApiClient.Builder newFullPermissionClient(Context context) {
        GoogleSignInOptions options = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(context.getString(R.string.default_web_client_id))
                .requestEmail()
                 // ↓この行を追加する
                .requestServerAuthCode("クライアントID.apps.googleusercontent.com", true)
                 // ↓サーバー側で欲しい権限があったら、ここで追加しておく
                .requestScopes(new Scope("https://www.googleapis.com/auth/firebase"))
                .build();
        return new GoogleApiClient.Builder(context)
                .addApi(Auth.GOOGLE_SIGN_IN_API, options)
                ;
    }
```

`requestServerAuthCode`を行なうと、Googleのログイン画面で１項目追加されて、「オフラインアクセスを許可するか」と聞かれます。ここではいを選ぶと、サーバーでOAuthトークンを発行するための認証コードを取得できます。

![add-auth.png](https://qiita-image-store.s3.amazonaws.com/0/15670/ea551108-8a92-6781-b6bf-a8842ae64cf3.png)


```java
// サーバーに送信する認証コード
String authCode = signInAccount.getServerAuthCode();
```

## サーバーでユーザーアクセス用のOAuth2トークンを得る

サーバーに送信されたAuthCodeは、サービスアカウントのときと同じ手順でOAuth2トークンに変換しなければなりません。ただし、このAuthCodeが有効なのはたった１回です。

初回のトークン取得後は、 `refresh_token` を使って１時間ごとに交換しなければなりません。また、ユーザーが明示的にアクセス権限を取り消した場合等の理由でリフレッシュトークンもアクセストークンも使えなくなる場合があります。

その場合、認証エラーとして正しくハンドリングするようにしましょう。もし適当なハンドリングだと、永遠にリトライしてしまう等の不具合が発生するかもしれません。

```go
// 初回のトークンを取得する
values := url.Values{}
values.Add("client_id", "クライアントID")
values.Add("client_secret", "クライアントシークレット")
values.Add("grant_type", "authorization_code")
values.Add("code", "端末から送られてきた認証コード")

resp, err := urlfetch.Client(it.context).PostForm("https://www.googleapis.com/oauth2/v4/token", values)
// 200なら、"access_token"と"refresh_token"を確保する
```

```go
// トークンが期限切れした場合にリフレッシュトークンを使ってトークンを再発行する
values := url.Values{}
values.Add("client_id", "クライアントID")
values.Add("client_secret", "クライアントシークレット")
values.Add("grant_type", "refresh_token")
values.Add("refresh_token", "事前に得ていたリフレッシュトークン")
resp, err := urlfetch.Client(it.context).PostForm("https://www.googleapis.com/oauth2/v4/token", values)
```


得たOAuth2のaccess_tokenからユーザー情報を得るには、IdTokenを送らずともGoogleのユーザー情報APIを叩くことでも代用できます。 

```bash
curl -H "Authorization: Bearer {access_token}" https://www.googleapis.com/oauth2/v1/userinfo?alt=json
```


これで一度のログイン画面だけで「Googleアカウントの認証」「Firebaseへのログイン・アクセス制御」「サーバーからユーザーのGoogleアカウントへのアクセス」が全て行えるようになりました。

# 最終的な認証 / 処理手順

## 事前準備

 - [ ] GCPのプロジェクトを作成する
 - [ ] FirebaseプロジェクトとGCPプロジェクトをリンクする
 - [ ] Firebaseにサービスアカウントを登録する
 - [ ] Firebaseのサービスアカウントの秘密鍵を取得する
 - [ ] FirebaseにAndroid/iOS等のアプリ情報を登録する
 - [ ] 必要なGoogle APIを有効化する

## ログイン手順

 1. [端末] Googleログインのオプションでサーバーアクセスに必要なスコープを追加する
 1. [端末] オプションでWeb ClientのクライアントIDを追加する
 1. [端末] Googleログインで得たIdTokenとサーバーアクセスコードをサーバーに送信する
 1. [サーバー] 送られてきた情報ので妥当性をチェックし、OAuthの認証情報取得はリフレッシュトークン保存等を行なう
 1. [サーバー] サーバーでJWTを生成し、端末に返却する
 1. [端末] 端末は返却されたJWTでCustom Tokenによるログインを実行する
 1. [端末] 正常にFirebaseログインが完了すればログイン完了

## ログイン以後の通信手順

 1. [端末] 通信前にFirebaseログイン情報の通信トークンを取得する
 1. [端末] Firebaseのトークンをhttpのヘッダ等に乗せて自社サーバーのAPIを呼び出す
 1. [サーバー] Firebaseのトークンが正しく署名されているか検証する
 1. [サーバー] 署名が正しいなら、`user_id` のユーザーとしてサーバーの処理を行なう

## サーバーがFirebaseのリソースにアクセスする

 1. [サーバー] JWTを生成し、サービスアカウント用のOAuth2トークンを取得する
 1. [サーバー] AuthorizationヘッダにOAuth2トークンを載せてREST APIを叩く

## サーバーがユーザーのGoogleリソースにアクセスする

 1. [サーバー] 端末から受け取ったアクセスコードからrefresh_tokenを得ておく
 1. [サーバー] access_tokenが向こうになっている場合（期限切れなど）、refresh_tokenを使ってアクセストークンを取得しなおす
 1. [サーバー] AuthorizationヘッダにOAuth2トークンを載せてREST APIを叩く

# エンドポイント一覧

自分でもよく忘れるのでエンドポイントや役立つサイト一覧をここにメモしておきます。

 * Google API Console
    * https://console.developers.google.com 
 * Firebase Console 
    * https://console.firebase.google.com 
    * 先にGCPのプロジェクトを作成してからリンクさせるほうが良い
 * JWTの検証を行なう
    * https://jwt.io
 * GoogleのID Tokenを検証する
    * https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=
 * サービスアカウントの公開鍵を得る
    * https://www.googleapis.com/robot/v1/metadata/x509/{アカウント}
    * Googleはコレで固定
        * https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com
 * OAuth2アクセストークンを得る
    * https://www.googleapis.com/oauth2/v4/token
 * OAuth2アクセストークンの検証を行なう
    * https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={アクセストークン}