Slackからユーザー情報を取得する

タイトルの通りSlackからユーザー情報を取得してみました。
取得する方法はQiitaにも書かれてる方が多いですが、３年前とか割と時が経っていたりもしたので。

今回はnode.jsを使っての取得ですが、リクエスト先は固定なので参考にしてみてください。
※Slackアプリケーションは登録済でOAuth情報がわかっていること前提で紹介しています

1. Slackから認証情報を取得する
2. Slackからユーザー情報を取得する
3. [おまけ] curlでサクッとリクエストしてみる

# 実行環境
- Node.js: v18.15.0
- npm:  9.5.0
- React: ^18.2.0

# 1. Slackから認証情報を取得する

ユーザー情報を取得する時に必要なAccessTokenとUserIdを取得します。
このAccessTokenとUserIdを取得するには、前提としてSlackのアプリケーション認証を許可した後にリダイレクトURLに含まれる```code```が必要です。
その辺の認証まわりはこちらの記事を参考にすると良いかなと思います。
https://bagelee.com/programming/Slack-oath-with-react/

```code```を取得するリダイレクトURLは以下の通りです。
```code```にはOAuth認証で許可される```scope```を含めています。
リダイレクト先で「あなたはscopeA,B,Cから許可された〇〇という情報をアプリに譲渡してもOK?」という画面が表示されるので、それを許可すると、```scope```で許可されたデータを取得できる```accecc_token```が与えられます。今回はユーザー情報を取得したいので、そのために必要な```scope```を```const scopes```で定義します。

```ts
export const slackLoginURI = () => {
    const SLACK_CLIENT_ID = import.meta.env.VITE_SLACK_CLIENT_ID
    const SLACK_REDIRECT_URI = import.meta.env.VITE_SLACK_REDIRECT_URI
    const scopes = "identify,users.profile:read,users:read"
    return `https://slack.com/oauth/authorize?client_id=${SLACK_CLIENT_ID}&scope=${scopes}&redirect_uri=${SLACK_REDIRECT_URI}`
}
```
```import.meta.env```のところはvite仕様なので、envの読み取りは適宜変えてください。


urlから```code```の値を取得した後はfetchを使ってAccessTokenとUserIdを取得します。

```tsx:index.tsx
  useEffect(() => {
    (async () => {
      const params = new URLSearchParams(window.location.search);
      const code = params.get("code");

      if (code) {
        const { access_token, user_id } = await getAuthFromSlack(code);
      }
    })();
  }, []);
```

```ts:api.ts
export const getAuthFromSlack = async (code: string) => {
    const SLACK_CLIENT_ID = import.meta.env.VITE_SLACK_CLIENT_ID 
    const SLACK_CLIENT_SECRET = import.meta.env.VITE_SLACK_CLIENT_SECRET
    const SLACK_REDIRECT_URI = import.meta.env.VITE_SLACK_REDIRECT_URI

    const params = new URLSearchParams({
        code,
        client_id: SLACK_CLIENT_ID,
        client_secret: SLACK_CLIENT_SECRET,
        redirect_uri: SLACK_REDIRECT_URI
    })

    const result = await fetch('https://slack.com/api/oauth.access?' + params)
        .then(response => response.json())

    return {
        user_id: result.user_id,
        access_token: result.access_token
    } as SlackAuthData
}
```

これで```user_id```と```access_token```を取得できたので、この値を使ってユーザー情報を取得します。

# 2. Slackからユーザー情報を取得する

1で取得した ```user_id```と```access_token```を使ってユーザー情報を取得します。
前提としてアプリケーションのScopesを以下の通りに変更します。

- Bot Token Scopes
  - users.profile:read
  - users:read
- User Token Scopes
  - users.profile:read
  - users:read

Scopeを変更後、アプリケーションのreinstallが必要なので忘れずしましょう。
（多分Slackからwarningポップアップが出ると思いますが）

fetchのままリクエストを送るとcorsブロックのpreflight response ~ と格闘する羽目になるので、こちらはnode.jsでリクエストします。expressを使ってバックエンド側にリクエストを流すことで、node.jsからユーザー情報を取得します。

```index.tsx
const { access_token, user_id } = await getAuthFromSlack(code);
// ↓続き
if (!access_token || !user_id) return;
const data = await getUserProfileFromSlack(access_token, user_id);
```

```ts: api.ts
export const getUserProfileFromSlack = async (token: string, userId: string) => {
    return await mutation(
        '/auth/slack/user/profile',
        RequestType.POST,
        { userId, token }
    ) as SlackUserProfile
}
```

```ts:hook.ts
/**
 * post / put / delete request
 * @param {string} prefix 
 * @param {keyof typeof RequestType} method 
 * @param {any} body 
 * @returns 
 */
export const mutation = async (prefix: string, method: keyof typeof RequestType, body: any) => {
  const BACKEND_URL = import.meta.env.VITE_BACKEND_URL;
  const url = `${BACKEND_URL}${prefix}`;
  return await fetch(url, {
    method,
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
    credentials: 'include',
    body: JSON.stringify(body)
  })
    .then(response => {
      return response.json()
    })
}
```
```mutation()```でリクエストをラップしてますが、要はfetchでバックエンドに流しているだけです。nodeだけの検証であれば上で紹介したapi.tsとhook.tsは無視してください。

以下ルーティングを解決した後にSlackからユーザー情報を取得する方法です。
```ts: backend.ts
import request from 'request'

/**
 * get slack user profile 
 * @param {SlackProfileRequest} data 
 * @returns {SlackProfileResponse | null}
 */
export const getSlackUserProfile = async (data: SlackProfileRequest) => {
    const { userId, token } = data
    if (!userId || !token) return null
    const options = {
        url: `https://slack.com/api/users.profile.get?user=${userId}&pretty=1`,
        method: 'GET',
        headers: {
            'Authorization': `Bearer ${token}`
        },
        json: true,
    }

    const result: SlackProfileResponse = await new Promise((resolve, reject) => {
        request(options, (error, response, body) => {
            if (!error && response.statusCode === 200) {
                resolve(body);
            } else {
                reject(error);
            }
        })
    })

    return (result.ok) ? result.profile : null
}
```
```userId```は1で取得した```user_id```、```token```は```access_token```にあたります。
リクエストURLに```&token=xxxxxxx```を含めてリクエストする方法も別の記事でありましたが、こちらの方法は現在使えないようです。代わりに```'Authorization': `Bearer ${token}` ```とすることでリクエストから欲しい情報を取得できます。
今回のケースだと```result.profile```に欲しい情報があるのでそれを取得します。

結果は以下のようになります。
```ts
{
    ok: boolean,
    profile: {
      title: string,
      phone: string,
      skype: string,
      real_name: string,
      real_name_normalized: string,
      display_name: string,
      display_name_normalized: string,
      .
      .
      image_original: string,
      email: string,
      .
      .
    }
  }
```
```display_name```に表示アカウント名、```image_original```にはアカウント使用の画像URLがあります。```email```も取得できるので、Slackアカウントを通して認証機能を作ってみるのもありですね。
値の詳細は以下のURLから確認できます。
https://api.slack.com/methods/users.profile.get



# [おまけ] curlでサクッとリクエストしてみる
curlでリクエストしたい方はこちら。
```
curl -H 'Authorization: Bearer xxxxxx' -X GET 'https://slack.com/api/users.profile.get?user=xxxxxx&pretty=1'
```
xxxxxxにaccess_token,user_idを置き換えて投げてください。

# まとめ
今回は簡単にfetch等でリクエストしましたが、sdkもあるようなのでそちらを使って取得してみても良いかもしれません。