【Firebase】Sign in with Slackの実装方法(設計)
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
Firebaseを用いたsign in with slackができるまでの記録第二弾として、今日は昨日1日調べてわかったことをこの記事に残しておきたいと思います。記事内容としてはあくまで設計段階ですので、実際に試してみてからまた更新します。
Firebase Authentication
Firebaseの認証部分だけを使用するために「Firebase Authentication」を使用します。この機能を使うことで認証機能をかなり簡単に実装できるようです。例えば、自分が試した以下の記事は30分程度でTwitter認証を行うことができました。
Vue、FirebaseでツイッターのOAuthを使ったログイン機能を追加する - Qiita
【該当ソースコード】
<template> <div class="signin"> <h2>Sign in</h2> <button @click="signin">Signin</button> </div> </template> <script> import firebase from 'firebase' export default { name: 'Signin', methods: { signin: function () { const provider = new firebase.auth.TwitterAuthProvider() firebase.auth().signInWithPopup(provider) .then( result => { if (user) { console.log(result.user) } else { alert('有効なアカウントではありません') } }) } } } </script>
Firebaseを通して認証を行うソースコードは以下の部分です。
const provider = new firebase.auth.TwitterAuthProvider() firebase.auth().signInWithPopup(provider)
Twitter認証のためのメソッドとしてTwitterAuthProvider
というものが用意されています。よって、slack認証のためにはSlackAuthProviderを使えばいいのだなと思ったのですが、そううまくいきませんでした。slack認証はFirebaseのプロバイダとしては登録されていないからです。
【Authenticationの画面キャプチャ】
ではslack認証はFirebaseに非対応なのかというと実はそうではないようです。以下の公式の記事の例でもある通り、カスタム認証システムを使用してslack認証を行うようです。
Android でカスタム認証システムを使用して Firebase 認証を行う
Slack認証フロー
カスタム認証システムを使用する上で重要なのは各APIの認証フローに沿って自作で認証フローを作ることです。slack認証フローでは公式では以下のように記載されています。
Token negotiation flow
- User arrives at your site and clicks Sign in with Slack button
- User arrives at
slack.com/oauth/v2/authorize?client_id=CLIENT_ID&user_scope=identity.basic
and briefly approves sign in - User arrives at your specified redirect URL with a
code
parameter - Your server exchanges
code
for an access token usingslack.com/api/oauth.v2.access
- Your server uses the resultant access token to request user & workspace details with
slack.com/api/users.identity
, passing the awarded token as a HTTP authorization header or POST parameter
ユーザーがアプリケーション(自分のアプリ)に対して認証を行い、その後slack APIにAccess Tokenを発行してもらい、それを利用してリソースにアクセスするというフローです。これは至ってシンプルな認証フローだと思います。不明点がある方は以下の記事を参考にしていただければと思います。
上記のフローをアプリケーション上ではなくFirebase上で行う実装に置き換える必要があります。参考になる記事がありましたのでそれをそのまま使用して説明しようかと思います。
Sign in with Slack x Firebase Authenticationやってみた話 - Qiita
【Firebase版slack認証フロー】
- クライアントからSlackの認証ページへ飛ぶ
- ユーザーはSlackの画面でアクセス許可を行う
- Slackは認証用codeを載せて指定しておいたリダイレクト先に飛ぶ(今回は
Cloud Functions
を利用) - codeを使ってSlackユーザーのアクセストークンを取得、必要なら永続化などを行う
- FirebaseのAdmin SDKを使ってトークンを発行する
- トークンを載せてクライアントにリダイレクト
- クライアントは
signInWithCustomToken
メソッドを叩く
上の図と公式の図は同じことをしています。
=(イコール)
みたいですね。
参考にしてFirebase認証をまとめてみました。
1. クライアントからSlackの認証ページへ飛ぶ
(User arrives at your site and clicks Sign in with Slack button)
これは自分のアプリケーションやサイト画面にslack認証ページに飛ぶためのボタンやリンクを設けてあげるだけです。Slack Appを作成後、Sign in with SlackのページにあるBotton Generatorを使用すれば簡単に認証ページに飛ぶことができるボタンを作成できます。
ボタンのコードの例
<a href="https://slack.com/oauth/v2/authorize?user_scope=identity.basic&client_id=<アプリのクライアントID>"><img alt=""Sign in with Slack"" height="40" width="172" src="https://platform.slack-edge.com/img/sign_in_with_slack.png" srcset="https://platform.slack-edge.com/img/sign_in_with_slack.png 1x, https://platform.slack-edge.com/img/sign_in_with_slack@2x.png 2x" /></a>
ボタン生成ページ
2. ユーザーはSlackの画面でアクセス許可を行う
(User arrives at slack.com/oauth/v2/authorize?client_id=CLIENT_ID&user_scope=identity.basic
and briefly approves sign in)
slack上の認証画面でアクセス許可を行うフローです。上記のボタンを生成していれば問題なく認証画面に遷移すると思います。
【認証画面】
3. Slackは認証用codeを載せて指定しておいたリダイレクト先に飛ぶ(今回はCloud Functions
を利用)
(User arrives at your specified redirect URL with a code
parameter)
ここはFirebaseの初学者の自分には理解するのに苦労しました。参考記事に書いてあることとしては以下になります。
- ユーザーが許可を行うと、指定しておいたリダイレクト先に飛ぶフロー
- ここだけ唯一サーバーサイドが必要となるので
Firebase Cloud Functions
を利用 - その際に
code
というクエリパラメータで認証用の一時的なトークンを渡してくる
認証画面では認証後コールバック先にリダイレクトするようになっています。本来であれば自分のアプリケーションにリダイレクトするフローですが、それをFirebaseにリダイレクトするようにしています。Firebaseにリダイレクトさせるために使用するのが、Firebase Cloud Functions
というサーバーサイドの機能です。Javascript、またはTypeScriptをFirebase上にデプロイしておくことで、リダイレクトした時の処理、ロジックをFirebase上で行えるようにします。また、リダイレクト先のパラメータにcode
というクエリパラメータで認証用の一時的なトークンを渡してくれます。
デプロイ方法
Getting Started with Cloud Functions for Firebase using TypeScript - Firecasts
4. codeを使ってSlackユーザーのアクセストークンを取得、必要なら永続化などを行う
(Your server exchanges code
for an access token using slack.com/api/oauth.v2.access
)
先ほどのcode
を使ってoauth.v2.access
というエンドポイントへリクエストを投げ、アクセストークンを取得します。そのロジックをFirebase Cloud Functions
をデプロイして実装します。参考記事でTypeScriptのファイルが公開されていたのでこれをありがたく使わせてもらおうと思います。
※こちらの公開ファイルの最終更新日は2年前ですので更新する必要があります。例えば以下の部分。
const res = await slackClient.post<oauthAccessResponseType>('oauth.v2.access', requestArgs); // const res = await slackClient.post<oauthAccessResponseType>('oauth.access', requestArgs);
⇒ Firebaseからslackにアクセストークンのリクエストを送るフェーズ(仮実装)
import * as functions from 'firebase-functions'; import axios from 'axios'; import * as qs from 'querystring'; // 全体的に例外処理が果てしなくてきとう。 // というか、SlackAPIはok: falseがエラーの際には返ってくるのでこれでは例外処理になってないような気がする。 // 細かいことは気にしない。 const slackClient = axios.create({ baseURL: 'https://slack.com/api', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, transformRequest: [ data => qs.stringify(data) ] }) export type oauthAccessResponseType = { user_id: string; access_token: string; scope: string; team_name: string; team_id: string; } export const oauthAccess = async (code: string): Promise<oauthAccessResponseType> => { const requestArgs = { client_id: functions.config().slack.client_id, client_secret: functions.config().slack.client_secret, code }; try { const res = await slackClient.post<oauthAccessResponseType>('oauth.v2.access', requestArgs); // const res = await slackClient.post<oauthAccessResponseType>('oauth.access', requestArgs); return res.data; } catch(e) { console.warn('Slack oauth was failed.', e); throw new Error(); } } // 面倒なので使いそうなやつだけ export type SlackUserType = { id: string; team_id: string; name: string; real_name: string; is_admin: boolean; is_owner: boolean; is_primary_owner: boolean; is_restricted: boolean; is_ultra_restricted: boolean; }; export const usersInfo = async (token: string, userId: string) => { const requestArgs = { token, user_id: userId }; try { const res = await slackClient.post<{user: SlackUserType}>('users.info', requestArgs); return res.data.user; } catch (e) { console.warn('Slack oauth was failed.', e); throw new Error(); } };
これによりアクセストークンをSlack APIから取得することができます。
5. FirebaseのAdmin SDKを使ってトークンを発行する
アクセストークン取得後Firebaseに再度リダイレクトされるため、クライアント側にカスタムトークンを発行するフローが必要になります。これもFirebase Cloud Functions
を使用して実装します。つまり、Firebase認証フローでは合計で2つのファイルをデプロイします。
トークンの発行方法としては公式の通りにcreateCustomToken
メソッドを使用します。
const customToken = await admin.auth().createCustomToken(userCredential.user_id);
6. トークンを載せてクライアントにリダイレクト
customToken
をユーザーに返すフローです。ようやくここでユーザー側に戻ってきました。
if (redirectUri) { const url = new URL(redirectUri); url.search = `t=${customToken}`; res.redirect(303, url.toString()); } else { res.json({ custom_token: customToken }).end(); } return; } catch (e) { console.error('Failed to create custom token:', e) } });
7. クライアントはsignInWithCustomToken
メソッドを叩く
(Your server uses the resultant access token to request user & workspace details with slack.com/api/users.identity
, passing the awarded token as a HTTP authorization header or POST parameter)
取得したカスタムトークンを使用してsignInWithCustomToken
を叩くことでAPIリソースにアクセスすることができるようになります。
firebase.auth().signInWithCustomToken(token)
【カスタム認証の該当ソースコード】
終わりに
まだ上記フローを直接試していないので近日中に試してみます。