Firebase Authenticationのカスタム認証システムを使って、Slack連携サインインを実現するまでの道のり。

おはこんにちは、あさマックです。
この記事は、[謎の集団Zeneloのアドベントカレンダー](https://qiita.com/advent-calendar/2018/zenelo)、11日目の記事です。
現在は謎の集団からは少し離れてしまったのですが、気付いたら書くことになっていました。有り難い話、どうやらまだ在籍しているようです。あまりに謎の集団すぎて、私自身もよくわかっていません。

前書きが長くなってしまいました。
今回はいま携わっているサブプロジェクトで、Slackと連携したWebアプリケーションをひとつを開発したのですが、サインイン機構として、[Firebase  Authentication](https://firebase.google.com/docs/auth/?hl=ja) を用いて「Sign in with Slack（Slack認証）」を実装しました。その時の流れを自分の備忘録も兼ねて書き残します。

# はじめに
Firebase  Authenticationは、Google, Facebook, Twitter, GitHubといった主要な認証プロバイダによるログインをサポートしています。
自身のWeb/モバイルアプリケーション内で独自の認証機構を実装せずとも、フロントエンド側は簡単にGoogleやFacebookのアカウントを使った認証機構を導入できます。これが無料で使えるとはなんとも ~~恐ろしい~~ 素晴らしいですね。

ただ、今回はWebアプリとSlackを連携したWebアプリケーションを作る前提でいて、Slackのアカウント情報で一意のユーザーを管理出来た方が楽ということもあり、なんとかして「Sign in with Slack」を実現したいと考えました。

Firebase Authenticationの公式ドキュメントにはあまり目立って書かれていないですが、カスタム認証システム（カスタムトークン）も提供されています。これを用いることで「Sign in with Slack」の実現が可能です。

カスタム認証システムを利用するには、前提としてOAuth 2.0の知識が必要になるので、理解を深めるには「[一番分かりやすい OAuth の説明](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be)」がおすすめです。
また、Slackの[Using OAuth 2.0](https://api.slack.com/docs/oauth)のフローチャートもわかりやすいです。
![The OAuth Flow](https://a.slack-edge.com/bfaba/img/api/slack_oauth_flow_diagram@2x.png)

# Slack サインインを実現する
ほぼSlackの公式ドキュメント（[Sign in with Slack](https://api.slack.com/docs/sign-in-with-slack)）に沿って進めていけます。

Firebaseでカスタムトークンを用いた認証を行う部分のみFirebase JavaScript SDK, Firebase Admin SDKを用います。
SDKが2つあってややこしいので、まとめました。

### Firebase JavaScript SDK
クライアントサイド（フロントエンド）で用いるSDK。
Firebaseのサインイン／サインアウトを行ったり、FireStoreからデータを取り出したり、Storageからファイルを取り出したりできる。
**ユーザーの権限で実行** される。（サインインしていたらそのユーザー、サインアウト時はゲスト）
詳細は[Firebase を JavaScript プロジェクトに追加する](https://firebase.google.com/docs/web/setup?authuser=0) を参照のこと。

### Firebase Admin SDK
その名の通り、管理者向けAPIを集めたSDKで、バックエンドで利用するもの。
利用にあたっては、ServiceAccountと呼ばれる秘密鍵が必要。サーバサイド（バックエンド）で使うもの。
認証に必要なトークンを生成できたり、FireStoreのデータ読み書きができたり、Storageの読み書きができる。
いわゆる **管理者権限で実行される** ので
詳細は[サーバーに Firebase Admin SDK を追加する](https://firebase.google.com/docs/admin/setup?authuser=0)を参照のこと。

## 1. Slackアプリを登録する
Slack認証を利用するには、まずはじめに[Slack Appを新規登録する](https://api.slack.com/apps) ところからスタート。
Slack アプリを登録後、必要なID、キーを控えましょう。
Slack認証を行うために必要になるのは、Basic Informationのページにある「Client ID」「Client Secret」と、Installed App Settingsにある「OAuth Access Token」の3つになります。

## 2. Sign in with Slack ボタンを設置する
ご丁寧にSlackさんは「[Sign in with Slack](https://api.slack.com/docs/sign-in-with-slack)」というページがあり、そこで導入手順と画像が提供されているので、使わない手はないですね！

フロントエンド側はHTMLにSign in with Slackボタンリンクを設置。
リンク先URLは、 `https://slack.com/oauth/authorize/?client_id=${clientId}&scope=identity.basic&redirect_uri=${redirectUri}` というURLになります。 `clientId`, `redirectUri` は適宜置き換えてください。
上記URLでSlackにアクセスして承認手続きを行うと `redirectUri` で設定したURLにリダイレクトされます。
このリダイレクト先URLは後述しますが、バックエンドで動作するアプリケーションで処理できるURLにする必要があります。

## 3. Slackの承認フローを処理してFirebase Authenicationにサインインする
もっとも肝になる部分ですね。
公式ドキュメント[「Sign in with Slack」のAuthenticationセクション](https://api.slack.com/docs/sign-in-with-slack#authorization)の実装を進めつつ、Firebase Authenticationのカスタム認証システムを用いたいので、[Firebase Admin SDK](https://firebase.google.com/docs/admin/setup?hl=ja)をバックエンド側で使ってサインインする仕組みを作っていきます。

Firebase Admin SDKの利用にあたっては、サービスアカウントの秘密鍵が必要になります。
Firebase Consoleの設定→ユーザーと権限→サービスアカウントで「新しい秘密鍵の生成」を行って、サービスアカウント情報をダウンロードしておきましょう。

今回は、サーバレスでバックエンド コードを実行できるFirebase Cloud Functionsと呼ばれるサービスがあるので、今回はこれを使いました。完全に私がFirebaseにロックオンされていますね…！node.jsでバックエンドのコードが書けるため、フロントエンドが主務な方もすんなり書けて良いです。
※Firebaseのセットアップの詳説については割愛します。

バックエンド側の処理としては以下の3つを実装する流れとなります。

1. リダイレクトURLに遷移した際に `code` と `state` というクエリパラメーター付加されてくるので、これを [oauth.access API](https://api.slack.com/methods/oauth.access) にPOSTする。
2. oauth.access APIのレスポンスボディに含まれる `user_id` を用いて、Firebase Admin SDKの `createCustomToken(userId)` を実行する。
3. (2)の結果として `token` 文字列が発行されるので、このトークンの文字列を含めたクライアントサインイン処理を入れたソースをクライアント側（Webブラウザ）に返してあげる
具体的には、以下のようなHTMLを出力することで Slack サインインを実現できます。

```HTML:signin-callback.html
<html>
<head>
    <script src="https://www.gstatic.com/firebasejs/5.5.7/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/5.5.7/firebase-auth.js"></script>
    <script>
      var token = '${token}';
      // firebase のプロジェクトとして必要なキー
      var config = {
        apiKey: 'firebaseのAPIキー',
        authDomain: 'hoge.firebaseapp.com',
        projectId: 'hoge-a4f8d',
      };
      var app = firebase.initializeApp(config);
      app.auth().signInWithCustomToken(token).then(function() {
        window.location.href = 'https://hoge.firebaseapp.com/signin?success';
      });
    </script>
</head>
</html>
```

上記のソースでは `signin?success` に遷移させていますが、この後にクライアント側でFirebase JavaScript SDKを用いると、ログイン状態やユーザー情報を取得することが可能になります。

# 最後に
かなり駆け足となってしまいましたが、Slack認証とFirebase Authenticationの連携という部分をダイジェストで紹介しました。

最短攻略としては認証プラットフォームの「Auth0」を用いてSign in with Slackを導入するアプローチもありましたが、 **認証・データベース・ストレージ・サーバレス バックエンドコードもすべてFirebaseで一元管理できるのはとても楽だ** と思い、Firebaseで統一するアプローチを採りました。

「Sign in with Slack」を用いたアプリを開発したい時の一助となれば幸いです。
お読みいただき、ありがとうございました。

来年はFirebaseを活用しつつ、ネイティブアプリ（iOS/Android）を作ってみたい…！

# 参考資料
- [一番分かりやすい OAuth の説明](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be)
- [SlackでOAuth2を利用したときのメモ](https://qiita.com/subarunari/items/3e4c6060fcefd4c65257)
- [カスタム トークンを作成する](https://firebase.google.com/docs/auth/admin/create-custom-tokens?hl=ja)
- [Building a Slack app with Firebase as a backend](https://medium.com/evenbit/building-a-slack-app-with-firebase-as-a-backend-151c1c98641d)