SlackのOAuth認証を使ってユーザ情報を取得してみた


SlackのOAuth2を利用することになり、作業前は細かい動作がピンとこなかったので手順をまとめておきます。
Slackのユーザ情報を取得する実際のコードも載せています。


SlackのOAuth2.0については以下を参考にしました。
[SlackでOAuth2を利用したときのメモ](https://qiita.com/subarunari/items/3e4c6060fcefd4c65257)
[Using OAuth 2.0 | Slack](https://api.slack.com/docs/oauth)

# 手順概要

ざっくり以下の流れが必要になります。

1. Slackアプリに登録する
1. Slackアプリの権限設定などを行う
1. 認可コードを受け取るコールバックサーバを立てる
1. Webブラウザで認可リクエスト投げる
1. コールバックサーバで認可コードを受け取り、そのコードを含めたリクエストを投げる

# Slackアプリの登録

まず、Slackアプリを登録します

[Slack API: Applications | Slack](https://api.slack.com/apps) を開いて、`Create New App` を選択して、任意のアプリ名とチームを選択してください。

## ClientIDの取得

`Basic Information`のページの画面中央の`App Credentials`にIDが書いてあります

`Client ID`と`Client Secret`が必要になるので控えておいてください。(**漏洩しないよう注意**)

ただ、いつでも見えるので後でも良いです。

<img width="1026" alt="oauth_client_id.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/48217efe-f168-5a27-a7ba-f1bdbe8e1e0f.png">


## Recirect URLsの設定

左メニューより、`OAuth & Permissions`を選択します。

`Redirect URLs`の項目からコールバックを受けるサーバのURLを追加します。

今回はローカルサーバに立てるので、`http://localhost:3000`を登録します。

<img width="1026" alt="oauth_redirecturl.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/7400ed77-7908-6943-9fdc-9816d5678ca5.png">


## Scopeの設定

画面下部の`Scopes`中の`Select Permission Scopes`から`users:read`を選択します

<img width="1029" alt="oauth_users_read.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/506a3a4d-efcb-952d-62c6-3c32723c2993.png">


> 認可のみであれば`identity.basic`でも良いのですが、サンプルコードではユーザ情報まで取得しているため、これを選択します

ここまででSlack側の設定は完了です


OAuth認証を行うためにはコールバックを受け取るためのサーバが必要になります。  
実際のアプリの場合はグローバルなサーバとなりますが、今回は確認なのでローカルホストにサーバを起動します。

サンプルコードは [dbgso/slack-oauth2.0-client](https://github.com/dbgso/slack-oauth2.0-client) にあげています。
内容は[コードの中身](#コードの中身)をみてください

#コードの取得

```bash
git clone https://github.com/dbgso/slack-oauth2.0-client.git
cd slack-oauth2.0-client
yarn # or npm install
```

# クライアントIDの設定


任意のエディタで`app.js`を編集して、SlackのクライアントIDを設定してください。
IDは[Clientの取得](#Clientの取得)で控えておいたものです。

```diff
// Slack App の Client ID
- const slack_client_id = '';
+ const slack_client_id = '実際のclient_id';
// Slack App の Client Secret
- const slack_client_secret = ''
+ const slack_client_secret = '実際のclient_secret'
```

# 起動

```bash
node app.js
```

これで`http://localhost:3000`にSlackからのコールバックを受け取るサーバが立ち上がります。

# Webブラウザから認可リクエスト


Webブラウザで以下のURLを開きます

`SLACK_CLIENT_ID`の部分は実際の`client_id`に合わせてください。それ以外は変更なしで大丈夫です

```
https://slack.com/oauth/authorize?client_id=<SLACK_CLIENT_ID>&scope=identify&redirect_uri=http://localhost:3000/
```

以下のような画面が表示されるはずです。  
ここで`Authorize`ボタンを押すと`redirect_uri`で指定した`http://localhost:3000`に対してリクエストが送られます。

![slack-auth](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/06eaabfe-0278-175c-021c-028d21ea89c7.png)


# 実行結果

Slackのユーザ情報を取得するまでの流れは以下のようになっています。  
認可するのみであれば6.のユーザ情報取得までは必要ありません。

1. Authorizedボタンを押下する
1. `http://localhost:3000`にHTTPリクエストが飛ぶ
1. HTTPリクエストのクエリ中に`認可コード`が含まれているので取得
1. `認可コード`を使って`https://slack.com/api/oauth.access`にリクエストして`アクセストークン`を取得
1. `アクセストークン`を使って`https://slack.com/api/auth.test`にリクエストして`user_id`を取得
1. `user_id`を使って`https://slack.com/api/users.info`にリクエストして、Slack上のユーザ情報を取得する


うまく行けば以下のようなログがコンソール上に流れるはずです。


**アクセストークン取得**

`https://slack.com/api/oauth.access`からのレスポンス

```javascript
{ ok: true,
  access_token:
   '****-**********-************-************-********************************',
  scope: 'read,identify',
  user_id: '*********',
  team_name: '******',
  team_id: '*********' }
```

**user_id取得**

`https://slack.com/api/auth.test`からのレスポンス

```javascript
{ ok: true,
  url: 'https://******.slack.com/',
  team: '******',
  user: '*****',
  team_id: '*********',
  user_id: '*********' }
```

**Slackユーザ情報取得**

`https://slack.com/api/users.info`からのレスポンス

自身のSlackユーザ情報が表示されます。この情報は別にいらない、ということであれば`https://slack.com/api/users.info`へのリクエスト処理は不要です。

```json
{
    "ok": true,
    "user": {
        "id": "**********",
        "team_id": "**********",
        "name": "**********",
        "deleted": false,
        "color": "d58247",
        "real_name": "**********",
        "tz": "Asia/Tokyo",
        "tz_label": "Japan Standard Time",
        "tz_offset": 32400,
        "profile": {
            "title": "",
            "phone": "",
            "skype": "",
            "real_name": "**********",
            "real_name_normalized": "**********",
            "display_name": "**********",
            "display_name_normalized": "**********",
            "status_text": "",
            "status_emoji": "",
            "status_expiration": 0,
            "avatar_hash": "**********",
            "image_original": "https://avatars.slack-edge.com/xxxx-xx-xx/****************_original.jpg",
            "email": "**********",
            "first_name": "***",
            "last_name": "***",
            "image_24": "https://avatars.slack-edge.com/xxxx-xx-xx/****************_24.jpg",
            "image_32": "https://avatars.slack-edge.com/xxxx-xx-xx/****************_32.jpg",
            "image_48": "https://avatars.slack-edge.com/xxxx-xx-xx/****************_48.jpg",
            "image_72": "https://avatars.slack-edge.com/xxxx-xx-xx/****************_72.jpg",
            "image_192": "https://avatars.slack-edge.com/xxxx-xx-xx/****************_192.jpg",
            "image_512": "https://avatars.slack-edge.com/xxxx-xx-xx/****************_512.jpg",
            "image_1024": "https://avatars.slack-edge.com/xxxx-xx-xx/****************_1024.jpg",
            "status_text_canonical": "",
            "team": "**********",
            "is_custom_image": true
        },
        "is_admin": false,
        "is_owner": false,
        "is_primary_owner": false,
        "is_restricted": false,
        "is_ultra_restricted": false,
        "is_bot": false,
        "is_app_user": false,
        "updated": 1557212637,
        "has_2fa": false
    }
}
```

##  失敗時ログ

こんなログが流れている場合は認可が失敗しています。

`client_id`が正しいか、`Redirect URLs`や`Permissions`の設定を見直してみてください。

```bash
{ ok: false, error: 'invalid_code' }
{ ok: false, error: 'not_authed' }
```

> 認可のみであれば`identity.basic`でも良いのですが、サンプルコードではユーザ情報まで取得しているため、これを選択します

ちなみに、こう書きましたが、`identity.basic`を選んだ状態で、ユーザ情報を取得しようとすると以下のようなエラーがでます。

```
{"ok":false,"error":"missing_scope","needed":"users:read","provided":"identify"}
```

# コードの中身

* Node.js v10.15.3
* express 4.17
* request 2.88

でコールバックサーバを立てました。
リクエスト処理の度にネストが深くなってしまってみづらいですが、50行程度なので逆に小細工するよりこっちの方がみやすいかなと思ってます。

requestをPromiseにしてくれ...
と思ったらあるんですね。試しておきます
[request-promiseを使ったHTTPクライアントを作る - Qiita](https://qiita.com/you21979@github/items/37d62a7d190505bc089a)

```app.js
//@ts-check

const request = require('request')
const express = require('express');
const app = express();

// SSL関連でエラーが出るので、応急処置 SlackのAPIにリクエストするときに死んでいる模様。おそらく社内ネットワークのせい
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";

// Slack App の Client ID
const slack_client_id = '';
// Slack App の Client Secret
const slack_client_secret = ''

app.get('/', (req, res) => {
    // 認可コードの取得
    const code = req.query["code"];

    // 認可コードを使って、アクセストークンをリクエストする
    request({
        url: "https://slack.com/api/oauth.access",
        method: "POST",
        form: {
            client_id: slack_client_id,
            client_secret: slack_client_secret,
            code: code,
            redirect_uri: "http://localhost:3000/"
        }
    }, (error, response, body) => {
        // レスポンスからアクセストークンを取得する
        const param = JSON.parse(body);
        console.log(param);
        const access_token = param['access_token']; // アクセストークン

        // ユーザIDを取得するためのリクエスト
        request("https://slack.com/api/auth.test",{
            method: "POST",
            form: {
                token: access_token
            }
        },(error, response, body) => {
            console.log(JSON.parse(body));
            // アクセストークンを使ってユーザ情報をリクエスト
            // 認可のみが目的の場合はここはなくても良い
            request("https://slack.com/api/users.info ", {
                method: 'POST',
                form: {
                    token: access_token,
                    user: param['user_id']
                }
            }, (error, response, body) => {
                res.send(JSON.parse(body));
            })
        })
    })
})

app.listen(3000, () =>{
        console.log('HTTP Server(3000) is running.');
});
```

# おわりに

OAuth認証はほぼ初めてだったので、実際にコード書くと流れがわかってよかったです。

あと、認証と認可って言葉難しいですよね。使い方間違ってたら教えてください。。。

# 参考

[AWS CognitoにGoogleとYahooとLINEアカウントを連携させる - Qiita](https://qiita.com/poruruba/items/189945dc64edfe1f2464)

[SlackでOAuthを利用して BOT投稿 するための アクセストークン を取得する方法 - UTALI](https://www.utali.io/entry/2016/10/03/004421)

[SlackでOAuth2を利用したときのメモ - Qiita](https://qiita.com/subarunari/items/3e4c6060fcefd4c65257)

[オウム返し slack bot をぱっとつくる - Qiita](https://qiita.com/takose/items/bc2b63426b9921604599)

[Ignore invalid self-signed ssl certificate in node.js with https.request? - Stack Overflow](https://stackoverflow.com/questions/10888610/ignore-invalid-self-signed-ssl-certificate-in-node-js-with-https-request)