概要
Rails + Slack の認証機能で簡単なログイン画面を作る。
今回はあえて slack-omniauth など専用の gem などは使わずに、一から作ってみましょう。
お急ぎ便
すぐにぱっと試したい人は下記のコードをcloneしてきて、
環境変数をセットし、サーバを起動したら試せます。
Slack 側の用意
Slackにログインする
- 連携したいSlackのチームにログインをしておいてください
連携用の鍵を作成する
- 以下にアクセスして Create Nes App を押下し、認証用の鍵を作成します
- 作成が出来ると作成した Your Apps に作成した App の情報が表示されるようになります
リダイレクト先URLを設定する
-
Your Apps から先ほど作った App の名前のリンクを押してください
-
OAuth & Permissions を押下します
- ユーザの認証後にリダイレクトするURLを設定します
今回は開発のローカルにリダイレクトするために下記で設定します。
http://localhost:3000/sessions/acs
※host名やポートは適宜設定してください。
設定ができたら Save Changes を押して設定を保存してください。
またこの値は後程出てくる redirect_uri
の値として使うのでメモしておいてください。
必要なパラメータをメモしておく
-
client_id
,secret_key
を Rails 使用するのでコピペしてメモ帳などに張り付けておいてください
※show ボタンを押すと secret_key
が表示されます
以上で Slack 側での用意は終了です。
続いて Rails のセッティングをしていきます。
Rails 側の用意
アプリの作成
- 作業ディレクトリを作成して移動しましょう
mkdir slack_login
cd $_
- Rails アプリを作成します
bundle init
source "https://rubygems.org"
gem "rails", '~> 5.0.0'
bundle
bundle exec rails new . -N --skip-active-record --skip-bundle
連携に必要なパラメータの用意
- 連携に必要なパラメータは下記になります
パラメータ | 説明 |
---|---|
client_id | Slack連携を行う識別用ID |
secret_key | Slack連携を行う認証用key |
scope | アプリに許可する権限(とりあえず『identify』と設定しておけば良い ) |
team_name | 認証先のチーム名 |
state | リダイレクト元識別key(自分で作成) |
redirect_uri | 認証後のリダイレクト先 |
-
client_id
,secret_key
,redirect_uri
については Slack側の用意 の項目で準備したものを使います。 -
scope
はとりあえずidentify
と設定しておきます。 -
team_name
は連携をする Slack のチーム名を設定します。 -
state
は 認証時に Rails からポストする値です。これは Slack からそのまま返却されます。- よって
state
は自分のアプリが Slack にリクエストを送り、そのときに返ってきたレスポンスであることを証明するのに使います。
- よって
シークレットな値になるので環境変数に値をセットし、 Rails からは定数として使うようにしましょう。
- 環境変数のセット
環境変数 | セットする値 |
---|---|
SLACK_CLIENT_ID | client_id |
SLACK_SECRET_KEY | secret_key |
SLACK_TEAM_NAME | team_name |
SLACK_SCOPE | state |
SLACK_REDIRECT_URL | redirect_uri |
SLACK_STATE_CODE | state code |
- 環境変数を定数に入れる
お好きな gem を使うなりしてください。
今回は easy_settings
という gem を使って定数管理をするようにします。
Gemfile に 以下の記載を追加して bundle install してください。
gem 'easy_settings'
bundle
続いて config/settings.yml を作成します。
default: &default
slack:
client_id: "<%= ENV['SLACK_CLIENT_ID'] %>"
secret_key: <%= ENV['SLACK_SECRET_KEY'] %>
team_name: <%= ENV['SLACK_TEAM_NAME'] %>
scope: <%= ENV['SLACK_SCOPE'] %>
redirect_url: <%= ENV['SLACK_REDIRECT_URL'] %>
state: "<%= ENV['SLACK_STATE_CODE'] %>"
url:
auth: https://slack.com/oauth/authorize?
info: https://slack.com/api/oauth.access?
development:
<<: *default
test:
<<: *default
production:
<<: *default
これで例えば client_id
を取得したかったら下記のように書くことで取得できます。
EasySettings.slack.client_id
# => xxxxxxxxxx
Slackとの連携用Classを作成
- Slackにリクエストを送信する準備
Slackにリクエストを送る準備をします。ここも自分の好きなgemなどを使ってください。
今回は httpclient
を使います。Gemfileに以下を記載して bundle install
してください。
gem 'httpclient'
bundle
- 連携用Class(SlackConnector)を作成
以下のような Class を lib/ 配下に作成します。
class SlackConnector
def initialize
@client_id = EasySettings.slack.client_id.to_s
@client_secret = EasySettings.slack.secret_key
@scope = EasySettings.slack.scope
@team_name = EasySettings.slack.team_name
@redirect_uri = EasySettings.slack.redirect_url
@state = EasySettings.slack.state
end
def auth_path
"#{auth_host}client_id=#{@client_id}&state=#{@state}&scope=#{@scope}&team=#{@team_name}&redirect_uri=#{@redirect_uri}"
end
def get_info(code)
path = "#{acs_host}client_id=#{@client_id}&client_secret=#{@client_secret}&code=#{code}&redirect_uri=#{@redirect_uri}"
hc = HTTPClient.new
response = hc.get(path)
res_json = JSON.parse(response.body)
HashWithIndifferentAccess.new(res_json)
end
private
def auth_host
EasySettings.slack.url.auth
end
def acs_host
EasySettings.slack.url.info
end
end
説明
やってることは簡単で以下の2つの処理をさせています。
- Slack認証用のURLを作成する
- 認証完了後の値を元にユーザの情報を取得する
- Slack認証用のURLを作成する
Slack認証用のURLにはパスパラメータで
- client_id
- state
- scope
- redirect_uri
の情報が必要です。
※各パラメータを空だとしたらこんなURLにアクセスさせる
https://slack.com/oauth/authorize?client_id=&state=&scope=&team=&redirect_uri=
これらの各パラメータを initialize
のタイミングでインスタンス変数に格納しています。
- 認証完了後の値を元にユーザの情報を取得する
こちらもやっていることは Slack認証用のURLを作成する
と同じです。
各パラメータをセットして、 Slack の Web API を叩き、返り値をセット返すようにしています。
ポイント二つです。
まず引数で受け取る code
は Slack認証用URL
からリダイレクトした後に含まれる値で、
ログイン認証を終えたユーザを識別する値
にもなります。
次に返り値は json
で返ってくるので hash
へと変換をさせています。
その際文字列の hash
に対して symbol
でアクセスができるように
HashWithIndifferentAccess
を使用しています。
- lib 配下を自動で読み込む設定を追加
config/application.rb
に以下を追記
Rails.application.configure do
config.autoload_paths += %W(#{config.root}/lib)
end
Controller を作成する
ユーザに対してSlack認証させ、その結果を受け取り、セッションに格納する Controller
を作成します。
-
Controller
とView
を作成する
以下のコマンドで一気に作ってしまいます。
bundle exec rails g controller sessions index new acs
作成された app/controllers/sessios_controller.rb を以下のように修正します。
class SessionsController < ApplicationController
def index
end
def new
client = SlackConnector.new
redirect_to client.auth_path
end
def acs
state = params[:state]
raise "invalid" if state != EasySettings.slack.state || params[:error].present?
client = SlackConnector.new
code = params[:code]
# ユーザの情報を取得
res = client.get_info(code)
if res[:ok]
raise "invalid team" if res[:team_name] != EasySettings.slack.team_name
session[:user] = {}.tap do |u|
u[:id] = res[:user_id]
u[:access_token] = res[:access_token]
u[:time] = Time.zone.now
end
# ログイン成功後のリダイレクト先。今回はrootへ戻します。
redirect_to root_path, notice: "ログインテスト成功"
else
redirect_to root_path, notice: "ログイン失敗"
end
rescue
redirect_to root_path, notice: "ログイン失敗"
end
end
- ログイン画面を作成
以下のように app/view/sessions/index.html.erb を修正
<h1>Slackログイン</h1>
<%= link_to 'Slackログイン', sessions_new_path %>
<% if flash[:notice] %>
<h4><%= flash[:notice] %></h4>
<% end %>
- ルーティングの設定
rootにアクセスしたときにログイン画面に遷移するよう以下のパスを追加
root controller: :sessions, action: :index
試してみる
さっそく試してみましょう。
- サーバを起動する
bundle
bundle exec rails s
- ログイン画面へ遷移する
以下のように表示されていればOKです。
Slackログインリンク を押下しましょう。
すると Slack の認証画面へ遷移すると思います。
Authrize を押したらログイン画面に「ログイン成功」の文字が出てきます。
これでとりあえず Slack を利用したログイン画面は完成です。
まとめ
今回は Rails と Slack の認証を使ったログイン画面を一から丁寧に作成していきました。
上記のコードをまとめたものを以下の Github にアップしています。
手元で実際に動かして試してみたい方はご利用ください。
参考
Slack の認証後と ユーザ情報取得後のパラメータの値は以下のようになっています。
- Slack認証画面のリダイレクト後
[1] pry(#<SessionsController>)> params
=> <ActionController::Parameters {"code"=>"xxxxx", "state"=>"xxxxx", "controller"=>"sessions", "action"=>"acs"} permitted: false>
- ユーザ情報取得後
[1] pry(#<SessionsController>)> res
=> {"ok"=>true, "access_token"=>"xoxp-xxxx", "scope"=>"identify", "user_id"=>"Uxxxx", "team_name"=>"xxxx", "team_id"=>"Txxxx"}
``