Sign in with Slack x Firebase Authenticationやってみた話

この資料は5分LT用に作成されたもののため、だいぶ端折って説明されています。

https://yuruhachi-it.connpass.com/event/141835/

---

# Sign in with Slackとは

- https://api.slack.com/docs/sign-in-with-slack
- 名前の通り「Slackアカウントを使ったログイン」を実現できる仕組み。
- (ログイン時に要求する権限によっては)ユーザー情報やチーム情報を取得する事が可能
- あんまり知られてないような気がした(僕が知らなかっただけかも)

---

## こんなケースに使えそう:thinking_face:

- Slackのアカウント情報と連携したWebサービス
- 社員だけに限定公開するサイト

※ちなみにいろいろサンプルを用意しようとしたら全然間に合いませんでした:sob:

---

# Firebase Authenticationとは

- DBやらを用意せずとも認証・ユーザーの永続化などを一手にやってくれるFirebaseのサービスの一つ
- Firebase内の他のサービスの認証にも用いられる
    - Cloud FirestoreやCloud Storageなどで用いられるセキュリティールールなどでも使用するFirebaseの基盤とも言えるサービス
- 標準でGoogle/Facebook/Twitter等のソーシャルログインに対応している
- Slackは残念ながら非対応
    - だが、カスタム可能な認証方法が用意されているのでこちらを利用する

---

# カスタム認証について
- Firebase Authenticationに用意されている認証プロバイダを利用する場合はフロントエンドのコードだけで認証を行う事が可能

```javascript
// 例えばGoogleログイン
const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider)
```

- この方法はカスタム認証では利用できない。

## カスタム認証のフロー

1. サーバーサイドでFirebaseのAdmin SDKを使ってトークンを発行する
2. トークンをクライアントに受け渡す
3. クライアントはFirebase SDKの`signInWithCustomToken`メソッドにトークンを渡す

以上のフローでサインインを行う。

---

# 今回のケースに当てはめる


![B3730765-4CEA-49AF-B732-399230E194AB.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20371/baac09fc-8307-68e8-5f23-cf8c7b9eea01.jpeg)

1. クライアントからSlackの認証ページへ飛ぶ
2. ユーザーはSlackの画面でアクセス許可を行う
3. Slackは認証用codeを載せて指定しておいたリダイレクト先に飛ぶ(今回はCloud Functionsを利用)
4. codeを使ってSlackユーザーのアクセストークンを取得、必要なら永続化などを行う
5. FirebaseのAdmin SDKを使ってトークンを発行する
6. トークンを載せてクライアントにリダイレクト
7. クライアントは`signInWithCustomToken`メソッドを叩く

---

コード見た方が簡単だと思うのでもしよろしければ

https://github.com/uutarou10/sign-in-with-slack

サーバサイド(Cloud Functions)とクライアント(React)でどちらもTypeScriptで実装されています。

---

# 1. クライアントからSlackの認証ページへ飛ぶ
- 事前にSlack Appを作成しておく
    - 必要な権限を付与しておく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20371/9b76e455-d934-1a42-e466-1ccae516aac3.png)

- あとは `slack.com/oauth/authorize?client_id=CLIENT_ID&scope=identity.basic` Slack指定のURLへのリンクを用意しておく

---

# 2. ユーザーはSlackの画面でアクセス許可を行う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20371/e7396811-ac1c-505e-8369-fce5ed3f3d86.png)

---

# 3. Slackは認証用codeを載せて指定しておいたリダイレクト先に飛ぶ

- ユーザーが許可を行うと、指定しておいたリダイレクト先に飛ぶ
    - ここだけ唯一サーバーサイドが必要となるのでFirebase Cloud Functionsを利用
- その際に`code`というクエリパラメータで認証用の一時的なトークンを渡してくる

---

# 4. ユーザーのアクセストークンを取得、永続化

- 先ほどのcodeを使って`oauth.access`というエンドポイントへリクエストを投げると、アクセストークンを取得できる
- 必要であれば永続化処理などを行う

---

# 5. FirebaseのAdmin SDKを使ってトークンを発行する

- FirebaseのAdmin SDKを使ってFirebase Authenticationのカスタムトークンを作成する

```javascript
const customToken = await admin.auth().createCustomToken(userCredential.user_id);
```

`createCustomToken`メソッドにuidを渡すことで作成する。

uidは一意である必要があるので、今回はSlackのuser_idをそのまま渡した。

---

# 6. トークンを載せてクライアントにリダイレクト

```javascript
const url = new URL(redirectUri);
url.search = `t=${customToken}`;
res.redirect(303, url.toString());
```

今回はクエリパラメータに入れてリダイレクト。

---

7. クライアントは`signInWithCustomToken`メソッドを叩く

```javascript
const queryPrams = new URLSearchParams(window.location.search);
const token = queryPrams.get('t');

if (token) {
  window.history.replaceState(undefined, window.document.title, window.location.href.replace(window.location.search, ''));
  await firebase.auth().signInWithCustomToken(token);
}
```

---

# ハマったところ・注意すべきところ

- Cloud FunctionsからGoogle外のAPI(Slack API)を叩くにはBlazeプラン（従量課金プラン）にする必要あり
    - もちろん無料枠はあるので滅多なことでは課金されない:heart:
- カスタムトークンを作る場合は、GCPのIAMの画面でサービスアカウントに権限をつける必要あり
    - めっちゃハマった
    - [ここ](https://firebase.google.com/docs/auth/admin/create-custom-tokens?hl=ja#troubleshooting)に思いっきり書いてた。ドキュメントをしっかり読もう。。。

---

# 所感

- 基本的にはドキュメントに書いてあるフローをそのまま実装すれば動く
- Slack APIは実に様々な操作ができるので、付ける権限によってはかなり面白いことが色々できそう
- サンプル用意全然間に合わなかった:sob: