【Rails7】LINEログインを公式ドキュメントに沿って実装する
Googleカレンダーの予定を家族や自分のLINEに通知するアプリ「トリコミ」を個人開発しています。
リモートワークやオンライン勉強会の予定を共有できるように作ったので、「何それちょっと気になる」と思った方は、よければ使ってみてください!!!
さて、この記事では、このアプリの中でも利用している「LINEログイン」の実装方法についてまとめます!
LINEログインとは
LINEアカウントを使ったソーシャルログインサービスで、自分のアプリにLINEアカウントの情報を使ってログインすることができます。
LINE Developersコンソールで開発者登録をすれば無料で使えます。
WebアプリではGoogleログインやTwitterログインほど使われている印象はありませんが、スマホアプリだったり、LINEのユーザー情報とアプリの情報を紐付けたい場合は便利ですね。
今回は、Rails7系でサンプルアプリを作成して、LINEログインを使えるようにしてみます!
環境
以下の環境で作成しています。
- macOS 11.6.2
- Ruby 3.1.2
- Rails 7.0.3
- PostgreSQL 14.4
下準備
まずrails new
コマンドでアプリの雛形を作成します。後半の--css tailwind
は、TailwindCSSというCSSをあてるためのコマンドなので、省略しても大丈夫です。見映えをよくしたいので今回は入れました。
またデータベースにPostgreSQLを指定していますが、これも各自の環境にあわせてお好きなものを。
$ rails new line_login_app --css tailwind --database=postgresql
line_login_app
ディレクトリに移動して、User
モデルを作成します。ユーザーの認証はLINEプラットフォームに任せるので、パスワード関連のカラムは作成しません。あとで説明しますが、LINEプラットフォームでの認証・認可が成功したあと、一意となるLINEのユーザーID(line_user_id
)を取得できるので、これをUser
の識別に使うことにします。
bundle exec rails g model User line_user_id:string
line_user_id
カラムにnot null制約とunique制約をかけてデータベースを作成します。
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
# 追加
t.string :line_user_id, null: false, index: { unique: true }
t.timestamps
end
end
end
$ bundle exec rails db:create
$ bundle exec rails db:migrate
モデル側にも同じ制約をかけます。
class User < ApplicationRecord
# 追加
validates :line_user_id, presence: true, uniqueness: true
end
最後にログイン前とログイン後を識別するためのページを適当に作成しておきます。
$ bundle exec rails g controller StaticPages before_login after_login
Rails.application.routes.draw do
# 下2行を追加
root 'static_pages#before_login'
get '/after_login', to: 'static_pages#after_login'
end
<div>
<% if notice.present? %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>
<h1 class="font-bold text-4xl">ログイン前のページ</h1>
</div>
<div>
<% if notice.present? %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>
<h1 class="font-bold text-4xl">ログイン後のページ</h1>
</div>
LINE Developersコンソールでチャネルを作成する
WebアプリにLINEログインを組み込む手順は、こちらに詳しく書いてあります。
まずは、「チャネルを作成する」の項目に従って、LINEログインのチャネルを作成します。コールバックURLはあとで設定します。
コントローラを作成する
チャネルを作成したら、LINEログインを行うコントローラを作成します。名前はLineLoginApi
としましょう。
$ bundle exec rails g controller LineLoginApi
先ほどの公式ドキュメントの要件に従って、ユーザーをLINEプラットフォームにリダイレクトさせて認証・認可のプロセスを開始させるlogin
アクションと、認証・認可のプロセスが終了したあとに、その結果とともにユーザーがリダイレクトされてくるcallback
アクションを実装します。
最終的にはそのユーザーのLINEのユーザーIDが知りたいので、callback
アクションに戻ってきた認可コードからそのユーザーのアクセストークン(IDトークン)を取得するget_line_user_id_token
メソッドと、そのIDトークンからプロフィール情報(ユーザーID)を取得するget_line_user_id
メソッドも実装します。
公式ドキュメントの図を引用して説明させてもらうと、
https://developers.line.biz/ja/docs/line-login/integrate-line-login/#login-flow より
- Access the LINE Login authorization URL with 'redirect uri' and 'state' のリクエストを送るのが
login
- Access 'redirect uri' with 'state' and authorization code のリクエストを受け取るのが
callback
- Request access token のリクエストを送って Access token のレスポンスを受け取るのが
get_line_user_id_token
- Request user profile information のリクエストを送って User profile information のレスポンスを受け取るのが
get_line_user_id
という割り振りです。
それ以外の矢印は、LINEプラットフォームとユーザーが直接やりとりする部分なので、Webアプリ側で何かすることはありません。
具体的なコードは下記となります。
class LineLoginApiController < ApplicationController
require 'json'
require 'typhoeus'
require 'securerandom'
def login
# CSRF対策用の固有な英数字の文字列
# ログインセッションごとにWebアプリでランダムに生成する
session[:state] = SecureRandom.urlsafe_base64
# ユーザーに認証と認可を要求する
# https://developers.line.biz/ja/docs/line-login/integrate-line-login/#making-an-authorization-request
base_authorization_url = 'https://access.line.me/oauth2/v2.1/authorize'
response_type = 'code'
client_id = 'LINEログインチャネルのチャネルID' #本番環境では環境変数などに保管する
redirect_uri = CGI.escape(line_login_api_callback_url)
state = session[:state]
scope = 'profile%20openid' #ユーザーに付与を依頼する権限
authorization_url = "#{base_authorization_url}?response_type=#{response_type}&client_id=#{client_id}&redirect_uri=#{redirect_uri}&state=#{state}&scope=#{scope}"
redirect_to authorization_url, allow_other_host: true
end
def callback
# CSRF対策のトークンが一致する場合のみ、ログイン処理を続ける
if params[:state] == session[:state]
line_user_id = get_line_user_id(params[:code])
user = User.find_or_initialize_by(line_user_id: line_user_id)
if user.save
session[:user_id] = user.id
redirect_to after_login_path, notice: 'ログインしました'
else
redirect_to root_path, notice: 'ログインに失敗しました'
end
else
redirect_to root_path, notice: '不正なアクセスです'
end
end
private
def get_line_user_id(code)
# ユーザーのIDトークンからプロフィール情報(ユーザーID)を取得する
# https://developers.line.biz/ja/docs/line-login/verify-id-token/
line_user_id_token = get_line_user_id_token(code)
if line_user_id_token.present?
url = 'https://api.line.me/oauth2/v2.1/verify'
options = {
body: {
id_token: line_user_id_token,
client_id: 'LINEログインチャネルのチャネルID' # 本番環境では環境変数などに保管
}
}
response = Typhoeus::Request.post(url, options)
if response.code == 200
JSON.parse(response.body)['sub']
else
nil
end
else
nil
end
end
def get_line_user_id_token(code)
# ユーザーのアクセストークン(IDトークン)を取得する
# https://developers.line.biz/ja/reference/line-login/#issue-access-token
url = 'https://api.line.me/oauth2/v2.1/token'
redirect_uri = line_login_api_callback_url
options = {
headers: {
'Content-Type' => 'application/x-www-form-urlencoded'
},
body: {
grant_type: 'authorization_code',
code: code,
redirect_uri: redirect_uri,
client_id: 'LINEログインチャネルのチャネルID', # 本番環境では環境変数などに保管
client_secret: 'LINEログインチャネルのチャネルシークレット' # 本番環境では環境変数などに保管
}
}
response = Typhoeus::Request.post(url, options)
if response.code == 200
JSON.parse(response.body)['id_token'] # ユーザー情報を含むJSONウェブトークン(JWT)
else
nil
end
end
end
LINEログインチャネルのチャネルID、LINEログインチャネルのチャネルシークレットと書いてある部分は、ご自分のID、シークレットに変更してください。LINE Developersコンソールから取得できます。
ルーティングも設定します。
Rails.application.routes.draw do
# 下2行を追加
get 'line_login_api/login', to: 'line_login_api#login'
get 'line_login_api/callback', to: 'line_login_api#callback'
end
順番が前後しましたが、get_line_user_id_token
get_line_user_id
メソッド内のHTTP通信でtyphoeus
というgemを使いたいので、インストールします。
gem 'typhoeus'
$ bundle install
また、ログイン前のページに、LineLoginApi
のlogin
アクションへ遷移させるボタンを実装します。
<div>
<!-- 略 -->
<!-- 下記を追加 -->
<%= link_to "LINEでログイン", line_login_api_login_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium" %>
</div>
ついでに、ログイン後のページにも、動作確認のため、ログイン中のユーザーのIDを表示させるようにします。
<div>
<!-- 略 -->
<!-- 下記2行を追加 -->
<% current_user = User.find(session[:user_id]) %>
<%= "現在ログイン中のユーザーのID:#{current_user.id}" %>
</div>
これで、Webアプリ側の実装は終了です!
ngrokを使ってローカル環境をhttpsで公開する
最後に、LINE Developersコンソールで、LINEログインチャネルにコールバックURLを登録して終わりとしたいのですが、なんとLINEログインのコールバックURLはhttps
しか受け付けていません!
つまり、コールバックURLにhttp://localhost:3000
などのURLが使えないわけです。
そこでちょっと面倒なのですが、ngrok
というサービスを使ってlocalhost環境をhttpsで外部公開します。
まずngrokのサイトでユーザー登録を行い、バイナリファイルをダウンロードします。
サイトの手順通り、zipファイルを解凍し、PCとngrokのアカウントを紐づけます。
$ unzip ~/Downloads/ngrok-v3-stable-darwin-amd64.zip
$ ./ngrok config add-authtoken xxxxxxxxxxxxxxxxxxxxxxxx(あなたのauthtoken)
下記のコマンドで、ngrokを実行します。
$ ./ngrok http 3000
このような画面が出ればOKです。https://(乱数).jp.ngrok.io
の部分はngrokを起動するたびに変わります。
次に、development環境で接続先として許可するホスト名に.jp.ngrok.io
を追加します。(これを追加しないと、Blocked hostエラーが発生します。)
Rails.application.configure do
# 追加
config.hosts << '.jp.ngrok.io'
end
これで、http://localhost:3000
に、https://(乱数).jp.ngrok.io
でアクセスできるようになりました。
ngrokを実行したまま、./bin/dev
コマンドを実行し、ngrokの実行画面に表示されているhttps://(乱数).jp.ngrok.io
にアクセスしてみましょう。
無事、localhost環境に外部からhttpsで接続できるようになりました!
最後に、LINE DevelopersコンソールのLINEログインチャネルの設定画面で、コールバックURLをhttps://(乱数).jp.ngrok.io/line_login_api/callback
と設定します。
また、「権限設定」からLINEログインのテストに使いたいLINEアカウントを登録するか、ステータスを「開発中」から「公開」に変更して、どのLINEアカウントでもLINEログインのテストができるようにします。
Webアプリに戻って、LINEログインを試してみましょう。
LINEの認証画面が表示され、ログイン後のページにリダイレクトされたら成功です!
感想
認証系のgemを使わず公式ドキュメント通りにRailsでLINEログインを実装するとこのようになりました。
なお、本番環境で運用するにあたっては、こちらのセキュリティチェックリストが非常に参考になりました。
LINEのユーザーID以外にも、プロフィール画像や表示名も取得することができるので、用途にあわせてアレンジしてみてください。
Discussion