【Rails】Slack認証時のアクセストークンの保存
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
今日はslack認証時に得られるアクセストークンをDBに保存する方法です。(立て続けにslackについての記事を書いておりますが、最近ようやく実装できたことなのでたくさんアウトプットの題材が残っているのでアウトプットしておきます。)アクセストークンを保存することで 、下記メソッドを使用することで認証時以外でもslack APIのメソッドを使用することができます。
access_token = OmniAuth::Slack.build_access_token(ENV['SLACK_CLIENT_ID'], ENV['SLACK_CLIENT_SECRET'], hash_token)
それでは記事内容を見ていきましょう。
JSON型で保存
外部認証時の返却値request.env['omniauth.strategy']
からアクセストークンを生成するメソッドにつなげることができます。
bot_token = request.env['omniauth.strategy'].access_token hash_token = bot_token.to_hash access_token = OmniAuth::Slack.build_access_token(ENV['SLACK_CLIENT_ID'], ENV['SLACK_CLIENT_SECRET'], hash_token)
繰り返しになりますが、保存すべき値はhash_token
の値になります。
このhash_token
の値をDBに保存するのですがhash_tokenはJSONです。
hash_token => {"ok"=>true, "app_id"=>"xxxxxxxxxx", "authed_user"=> {"id"=>"xxxxxxxxxx", "scope"=>"identity.basic,identity.email,identity.avatar,identity.team", "access_token"=>"xoxp-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx", "token_type"=>"user"}, "scope"=>"chat:write,groups:write,im:write,channels:manage,channels:read,groups:read", "token_type"=>"bot", "bot_user_id"=>"xxxxxxxxxx", "team"=>{"id"=>"xxxxxxxxxx", "name"=>"prof"}, "enterprise"=>nil, "is_enterprise_install"=>false, :access_token=>"xoxb-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx", :refresh_token=>nil, :expires_at=>nil}
よってDBに保存する型はJSON型を使用します。(はじめてJSON型というものを使用しました、、、。)基本的にhashは全てJSON型の方が良さそうだと思いました。
ActiveModelのバリデータでJSON型のカラムを検証する
したがってUserカラムに追加するaccess_tokenはjson型で作成しましょう。
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[6.0] def change create_table(:users) do |t| ## User Info ・ ・ ・ t.json :access_token end end end
あとは保存時にhash_tokenを引数として指定して、保存をしてあげるだけです。
@user = User.from_omniauth(request.env['omniauth.auth'], user_info, hash_token, channel)
# app/models/user.rb class User < ApplicationRecord def self.from_omniauth(auth, user_info, hash_token, channel) user = find_or_initialize_by(provider: auth.provider, uid: auth.uid) user.password = Devise.friendly_token[0, 20] # ランダムなパスワードを作成 user.name = user_info.dig('user', 'name') user.email = user_info.dig('user', 'email') user.access_token = hash_token user.remote_image_url = user_info.dig('user', 'image_192') user.check_team_existence(user_info.dig('team'), channel) user.save! user end end
暗号化
slack APIについては今回生成したアクセストークンの値などが記されたhash_tokenを直接盗み見られてもそこまで問題ではありません。繰り返しアクセストークンを生成するメソッドを例にしますが、slackアプリのIDとなるSLACK_CLIENT_IDとSLACK_CLIENT_SECRETが知られない限り、アクセストークンを知られたとしても基本的に何もできません。
access_token = OmniAuth::Slack.build_access_token(ENV['SLACK_CLIENT_ID'], ENV['SLACK_CLIENT_SECRET'], hash_token)
SLACK_CLIENT_IDとSLACK_CLIENT_SECRETがあることでsecureな状態を保ってくれています。
しかし、アクセストークンを直接保存することはあまり良くないので、暗号化してから保存しましょう。
# app/models/user.rb class User < ApplicationRecord # before before_save :encrypt_access_token, :if => Proc.new {|u| u.access_token.present? } def encrypt_access_token key_len = ActiveSupport::MessageEncryptor.key_len secret = Rails.application.key_generator.generate_key('salt', key_len) crypt = ActiveSupport::MessageEncryptor.new(secret) self.access_token = crypt.encrypt_and_sign(access_token) end def self.from_omniauth(auth, user_info, hash_token, channel) user = find_or_initialize_by(provider: auth.provider, uid: auth.uid) user.password = Devise.friendly_token[0, 20] # ランダムなパスワードを作成 user.name = user_info.dig('user', 'name') user.email = user_info.dig('user', 'email') user.access_token = hash_token user.remote_image_url = user_info.dig('user', 'image_192') user.check_team_existence(user_info.dig('team'), channel) user.save! user end end
before_save :encrypt_access_token
で保存する前にencrypt_access_token
を実行するようにします。そのUserがアクセストークンを持っていない場合は実行しないように:if
オプションをつけています。
before_save :encrypt_access_token, :if => Proc.new {|u| u.access_token.present? }
暗号化方法にはいくつかの方法がありますが、上述したようにアクセストークンを暗号化する重要性がそこまで高くはないため、少し簡易的な方法をとっています。
def encrypt_access_token key_len = ActiveSupport::MessageEncryptor.key_len secret = Rails.application.key_generator.generate_key('salt', key_len) crypt = ActiveSupport::MessageEncryptor.new(secret) self.access_token = crypt.encrypt_and_sign(access_token) end
これを復号するときは以下のようにするば復号できます。
def set_access_token encrypted_access_token = current_user.access_token key_len = ActiveSupport::MessageEncryptor.key_len secret = Rails.application.key_generator.generate_key('salt', key_len) crypt = ActiveSupport::MessageEncryptor.new(secret) decrypted_access_token = crypt.decrypt_and_verify(encrypted_access_token) access_token = OmniAuth::Slack.build_access_token(ENV['SLACK_CLIENT_ID'], ENV['SLACK_CLIENT_SECRET'], decrypted_access_token) return access_token end
Slack認証時に保存するべき値
以上までの説明でhash_tokenを保存することで再度slack用のアクセストークンを作成することができます。しかし、アプリによってはアクセストークン以外にも認証時にカラムに保存しておくべき値があります。例えば、DMをuserに送信するアプリの場合、ユーザのIDがわかっていないと送ることができません。
chat.postMessage Slack API Method
chat.postMessage
メソッドはアクセストークンの値に加えて、channelのIDを必要とします。(DMを送る場合はuserのIDを必要とする)
よって認証時にuserのIDは控えておけば、のちのちDMを認証時以外で送る場合により簡単にDMを送ることができるでしょう。
アプリの用途によって必要なAPIメソッドが変わるのでそれに合わせて必要な値を認証時に保存しておきましょう。
今回は以上です。