[Firestore] Google認証で特定のドメインのユーザーだけアクセスを許可する

Firebase AuthenticationでGoogleアカウントによるログインを実装するのは簡単ですが、デフォルトだと全てのGoogleアカウントでログインできるようになっています。

社内向けのアプリケーションなどでは、特定のドメインのユーザーのみアクセスを許可したいケースがあると思いますが、そのようなケースの実装方法が案外出回っていなかったので、まとめておきます。

# 検証環境
- Angular6
- @angular/fire: v5.0.2
- firebase: v5.4.1

今回はAngularを例にしていますが、他のフレームワークでも大きな違いはないと思います。

# AngularFire
AngularでFirebaseを使うには、AngularFireという公式のライブラリを使うのが一番早いです。
AngularFireのセットアップとAuthenticationの基本的な実装方法は以下を参照。

- [Installation and Setup](https://github.com/angular/angularfire2/blob/master/docs/install-and-setup.md)
- [Getting started with Firebase Authentication](https://github.com/angular/angularfire2/blob/master/docs/auth/getting-started.md)

# フロントエンドでの制御
例として、AuthServiceというclassに `googleLogin()` というメソッドを実装しました。
GoogleAuthProviderをnewした後、`setCustomParameters()` で `hd` というパラメータにドメインを指定することで、Googleログインのポップアップで、指定したドメインのメールアドレスしか入力できないようになります。

```typescript:auth.service.ts
import { Injectable } from '@angular/core';
import { auth } from 'firebase/app';
import { AngularFireAuth } from '@angular/fire/auth';
import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  constructor(
    private afAuth: AngularFireAuth,
    private afStore: AngularFirestore
  ) {}

  googleLogin() {
    const provider = new auth.GoogleAuthProvider();
    provider.setCustomParameters({
      hd: 'yourdomain.com'
    });

    return this.afAuth.auth.signInWithPopup(provider)
      .then(credential => {
        // Update user document in Firestore （おまけ）
        this.updateUser(credential.user);
      });
  }

  private updateUser(user: firebase.User) {
    const userRef: AngularFirestoreDocument<any> = this.afStore.doc(`users/${user.uid}`);

    const data = {
      uid: user.uid,
      email: user.email,
      displayName: user.displayName,
      photoURL: user.photoURL
    };

    return userRef.set(data, { merge: true });
  }
}
```

`googleLogin()` がコールされると、以下のようなポップアップが表示されます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/47414/25f09b31-75c6-ee26-069d-093f5c824350.png" width="400">

ちなみに、ログイン情報はブラウザのIndexedDBに保存されます。



# サーバーサイド（Firestore）での制御
フロントエンドでの制御だけだと完璧ではないので、サーバーサイド（Firestore）でも、特定のドメインのユーザーしかアクセスできないように設定します。

### Firestore Security Rules
Firestoreのセキュリティルールで、特定のドメインのユーザーしかドキュメントにアクセスできないようにします。
`firebase init` でFirestoreを使うように設定しているプロジェクトだと、プロジェクトのディレクトリ直下に `firebase.json` と `firestore.rules` があると思います。

`firestore.rules` に以下のルールを追加することで、`yourdomain.com` ドメインのユーザー以外は全てのドキュメントにアクセスできないようになります。

```firestore.rules
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if isValidUser();
    }
  }

  function isValidUser() {
    return request.auth.uid != null && request.auth.token.email.matches('.*@yourdomain.com');
  }
}

```

### シミュレータでテスト
設定したルールが意図した動きをするかどうか、Firebase Consoleのシミュレータでテストできるので、ルールをデプロイする前に一度テストすることをオススメします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/47414/ccf933c6-5573-9afb-02ed-5def96e26311.png)

### ルールをデプロイ

```shell-session
$ firebase deploy --only firestore:rules
```


# 参考記事
- [Restrict Firestore gmail sign in to specific domain](https://stackoverflow.com/questions/49985612/restrict-firestore-gmail-sign-in-to-specific-domain)
- [Cloud Firestore セキュリティルールを使ってみる](https://firebase.google.com/docs/firestore/security/get-started)
- [Firebase OAuth Login With Custom Firestore User Data](https://angularfirebase.com/lessons/google-user-auth-with-firestore-custom-data/)
- [Firestore Security Rules Guide](https://angularfirebase.com/lessons/firestore-security-rules-guide/)