💬

goでSlackアプリ作ってトークンローテションしてみた

2025/01/24に公開

はじめに

goでSlackアプリを作る機会があったのでトークンローテーションしてイベント発火までやってみた

細かいところは省いてメインのとこだけ

アプリ追加認証

  • まず画面でアプリ追加用のボタンを作る
<a href=`https://slack.com/oauth/v2/authorize?scope=app_mentions:read,chat:write,channels:read,channels:join,users:read,users:read.email&client_id=${client_id}&redirect_uri=${リダイレクト先}`>アプリ追加</a>

アプリ追加が許可されると指定したリダイレクト先にリダイレクトされる

  • リダイレクト先の処理
func (h *SlackHandler) Install(c echo.Context) error {
	// クエリパラメータからcodeを取得
	var params InstallRequest
	err := c.Bind(&params)
	if err != nil {
		return err
	}

	// OAuth2認証
	response, err := slack.GetOAuthV2ResponseContext(
		c.Request().Context(),
		http.DefaultClient,
		h.ctx.Config.Slack.ClientID,
		h.ctx.Config.Slack.ClientSecret,
		params.Code,
		{リダイレクト先},
	)
	if err != nil {
		return err
	}

	// DBなりどこかにAccessToken保存

	// 認証成功画面とかにリダイレクト
	return c.Redirect(http.StatusOK, {リダイレクト先})
}

トークンローテーション

  • Slackアプリ管理画面でrefreshTokenをONにするとOAuth認証のレスポンスにexpiresInとrefreshTokenが含まれるのでそれでローテーションする

func (h *SlackHandler) TokenRotation(c echo.Context) (*string, error) {
	// アクセストークン、有効期限、リフレッシュトークン取得
	token := "xoxe.~"
	expiresAt := 1737771362
	refreshToken := "xoxe-~"

	if time.Now().Unix() > int64(expiresAt) {
		// 有効期限を超えていればリフレッシュトークンでOAuth認証
		response, err := slack.RefreshOAuthV2TokenContext(
			c.Request().Context(),
			http.DefaultClient,
			h.ctx.Config.Slack.ClientID,
			h.ctx.Config.Slack.ClientSecret,
			refreshToken,
		)
		if err != nil {
			return nil, err
		}
		// 新しく発行されたアクセストークン、有効期限、リフレッシュトークンをどこかに保存
		
		token = response.AccessToken
	}

	return &token, nil
}

Slackイベント用処理

  • URL検証の処理とそれ以外のイベント処理を作る
  • 今回はbotにメンションされた場合にDMに返信するようにした
func (h *SlackHandler) Events(c echo.Context) error {
	body, err := io.ReadAll(c.Request().Body)
	if err != nil {
		return err
	}

	eventsAPIEvent, err := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionNoVerifyToken())
	if err != nil {
		return err
	}

	// slackイベントURL検証用の処理
	// これがないとslackアプリ管理画面でイベント用のURLを登録できない
	if eventsAPIEvent.Type == slackevents.URLVerification {
		var r *slackevents.ChallengeResponse
		err := json.Unmarshal([]byte(body), &r)
		if err != nil {
			return err
		}
		return c.JSON(http.StatusOK, r.Challenge)
	}

	// アクセストークン取得
	token, err := h.TokenRotation(c)
	if err != nil {
		return err
	}

	if eventsAPIEvent.Type == slackevents.CallbackEvent {
		if ev, ok := eventsAPIEvent.InnerEvent.Data.(*slackevents.AppMentionEvent); ok {
			client := slack.New(*token)
			_, _, err := client.PostMessageContext(c.Request().Context(), ev.User, slack.MsgOptionText("返信だよ", false))
			if err != nil {
				return err
			}
			return c.JSON(http.StatusOK, "")
		}
	}

	return c.JSON(http.StatusOK, "")
}

おわりに

ローテーション部分の有効期限がデフォルトで12時間?らしい。
有効期限の変更はよくわからなかったのでまた調べたい。
だいぶ適当に作ってしまったのでアーキテクチャの勉強とかもしたいので設計して書き直したい。

参考

https://qiita.com/piny940/items/3a923fcdc71f4080b9c1

Discussion