OIDCでのLINEログインをomniauthに乗っかる形で実装する[Rails]
業務にて、WebにLINEログイン機能を実装したので、その方法について書いておきます。
omniauthに乗っかる形でlib/以下にstrategyを自作することで対応しました。
理由としては、Gemとしてはこちらのomniauth-lineがありますが、
OpenID Connectを使用していないので、OpenID Connectに対応するためです。
全体
LINE公式に全体の流れが書かれているのでこれに沿う形でstrategyを実装していきます。
自作のストラテジーをomniauthが使えるように設定。
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
require 'omniauth/strategies/line'
provider :line, ENV['channel_id'], ENV['channel_secret']
end
今回はこのファイルを作成します。
# lib/omniauth/strategies/line.rb
require 'omniauth-oauth2'
module OmniAuth
module Strategies
class Line < OmniAuth::Strategies::OAuth2
# IDトークンからemailを取得するために'email'が必要
# IDトークンからプロフィール画像を取得するのに'profile'が必要
option :scope, 'openid email profile'
option :client_options, {
site: 'https://api.line.me',
authorize_url: 'https://access.line.me/oauth2/v2.1/authorize',
token_url: '/oauth2/v2.1/token'
}
uid do
raw_info['userId']
end
info do
{
'user_id' => raw_info['sub'],
'email' => raw_info['email'],
'picture_url' => raw_info['picture'],
}
end
def raw_info
@raw_info ||= verify_id_token
end
private
# nonceをリクエストパラメータに追加するためoverride
def authorize_params
super.tap do |params|
params[:nonce] = SecureRandom.uuid
session["omniauth.nonce"] = params[:nonce]
end
end
# デフォルトだとクエリパラメータがついてエラーになるのでoverride
def callback_url
full_host + script_name + callback_path
end
def verify_id_token
@id_token_payload ||= client.request(:post, 'https://api.line.me/oauth2/v2.1/verify',
{
body: {
id_token: access_token['id_token'],
client_id: options.client_id,
nonce: session.delete("omniauth.nonce")
}
}
).parsed
@id_token_payload
end
end
end
end
処理の流れを追っていきます。
リクエストフェーズ
/auth/lineにPOSTしたあとに、リダイレクト先を決めているのはここです。
# OmniAuth::Strategies::OAuth2
def request_phase
redirect client.auth_code.authorize_url({:redirect_uri => callback_url}.merge(authorize_params))
end
ここでリクエストパラメーターにnonce
をセットする必要があります。
そのためauthorize_params
をoverrideします。
# lib/omniauth/strategies/line.rb
# nonceをリクエストパラメータに追加するためoverride
def authorize_params
super.tap do |params|
params[:nonce] = SecureRandom.uuid
session["omniauth.nonce"] = params[:nonce]
end
end
また、今回はline上でのuser_id, email, プロフィール画像を取得したいのでscopeパラメータは'openid email profile'とする必要があります。
# lib/omniauth/strategies/line.rb
# IDトークンからemailを取得するために'email'が必要
# IDトークンからプロフィール画像を取得するのに'profile'必要
option :scope, 'openid email profile'
コールバックフェーズ
LINE側で認証が完了して、
/auth/line/callback
に戻ってきたときの処理です。
def callback_urlをoverrideしないと以下のエラーが発生します。
OAuth2::Error (invalid_grant: redirect_uri does not match web_1 | {"error":"invalid_grant","error_description":"redirect_uri does not match"}):
/auth/line/callback
に戻ってきたときに、得られた認可コードからアクセストークンを取得しにいきます。
そのときにアクセストークン取得リクエストに付与するredirect_urlパラメーターはここで実装されていますが、
# OmniAuth::Strategies::OAuth2
def build_access_token
verifier = request.params["code"]
client.auth_code.get_token(verifier, {:redirect_uri => callback_url}.merge(token_params.to_hash(:symbolize_keys => true)), deep_symbolize(options.auth_token_params))
end
# Omniauth::Strategy
def callback_url
full_host + callback_path + query_string
end
このままだとhttps://example.com/auth/line/callback?code=Fk19LszLSk7xFzAulJHn&state=af671fe52e40609f9127b8c691322f7c71ce180ee5624b02
となりcodeとstateパラメータが付いてしまい、LINE側に登録しているコールバックURLと異なる形になってしまうためにエラーが発生します。
以下のようにoverrideすることで
# lib/omniauth/strategies/line.rb
def callback_url
full_host + script_name + callback_path
end
https://example.com/auth/line/callback
となるのでLINE側に登録しているコールバックURLと一致し正常に処理をすすめることができます。
つづいて、IDトークン検証です。
def verify_id_tokenで、access_tokenからIDトークンを取得しIDトークンを検証してユーザー情報を取得します。
# lib/omniauth/strategies/line.rb
info do
{
'user_id' => raw_info['userId'],
'email' => raw_info['email'],
'picture_url' => raw_info['pictureUrl'],
}
end
def raw_info
@raw_info ||= verify_id_token
end
private
def verify_id_token
@id_token_payload ||= client.request(:post, 'https://api.line.me/oauth2/v2.1/verify',
{
body: {
id_token: access_token['id_token'],
client_id: options.client_id,
nonce: session.delete("omniauth.nonce")
}
}
).parsed
@id_token_payload
end
コントローラー
# config/routes.rb
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
get '/auth/line/callback', to: 'sessions#create'
end
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
# If you're using a strategy that POSTs during callback, you'll need to skip the authenticity token check for the callback action only.
skip_before_action :verify_authenticity_token, only: :create
def create
p "auth_hash", auth_hash
p "info", auth_hash.info
end
private
def auth_hash
request.env['omniauth.auth']
end
end
request.env['omniauth.auth']経由で、得られたユーザーID, email, プロフィール画像を取得できます。
ちなみに、認可の段階でエンドユーザーがemailの取得を許可しなかったり、そもそもLINEの登録はemailが無くても可能なのでemailが存在しないこともあります。
emailは必ずしも取得できるものではないということを念頭に置く必要があります。(アプリケーションでユーザーのemailを必須にしている設計にしている場合など)
Discussion