こんにちは。
今回は、RailsでLIFFアプリを作る方法をご紹介します。
LIFFとは
LIFFは、LINEの公式アプリ内で動作するWebアプリケーションです。
LINE Front-end Framework(LIFF)は、LINEが提供するウェブアプリのプラットフォームです。このプラットフォームで動作するウェブアプリを、LIFFアプリと呼びます。
LIFFアプリを使うと、LINEのユーザーIDなどをLINEプラットフォームから取得できます。LIFFアプリではこれらを利用して、ユーザー情報を活用した機能を提供したり、ユーザーの代わりにメッセージを送信したりできます。
LIFFアプリのメリットとして、LINEアプリ内で動作するため、ユーザーのアプリを開く手間が省け、ログイン情報をLINEの情報を元に共有することができることが挙げられます。
webに詳しくないユーザーでも、LINEアプリを開くだけでアプリを利用できるため、ユーザーの利便性が向上します。
例えば、LINEギフトなどは、LINEアプリ内で動作するLIFFアプリです。
LIFFアプリを作る
Line DevelopersのLIFFページからLIFFアプリを作成します。
以下の公式ドキュメントを参考にしてください。
プロバイダーを選択し、適当なチャンネルを作成します。
作成できたら、LIFFアプリを作成します。
この時、Endpoint URLが必要になります。
一旦ざつなURLを入力して、LIFFアプリを作成します。
RailsのcredentialsにLIFF IDを登録する
RailsでLIFFアプリを作るためには、LIFF IDを取得する必要があります。
LIFF IDは、LIFFアプリの作成時に作成されます。
EDITOR="code --wait" bin/rails credentials:edit
line:
channel_id: XXXXXXXXXXX
上記の設定で、以下のようにAPIキーを取得することができます。
channel_id = Rails.application.credentials.dig(:line, :channel_id)
ngrokの設定
LIFFアプリを作成する際に、Endpoint URLを一旦雑に作成したと思いますが、ローカルで開発する場合は、ngrokを使って、ローカルのURLを外部に公開する必要があります。
今回は、dockerを使って、ngrokを起動します。
version: '3.8'
services:
ngrok:
image: wernight/ngrok:latest
ports:
- 4040:4040
environment:
NGROK_PROTOCOL: https
NGROK_PORT: app:3000
NGROK_AUTH: ${NGROK_AUTH}
depends_on:
- app
networks:
- default
NGROK_AUTH=xxxxxxxx
これで、ngrokが起動します。
http://localhost:4040/ にアクセスすると、ngrokのダッシュボードが表示されるのでそこから、ngrokのURLを確認します。
https://xxxxxxxxxxxx.ngrok.io が、ngrokのURLになります。
LIFFアプリのEndpoint URLを設定する
LIFFアプリのEndpoint URLを、ngrokのURLに設定します。
先ほどのLIFFアプリの作成画面に戻り、Endpoint URLを設定します。
これで、LIFF URLにアクセスすると、ngrokのURL(= ローカルのroot_path)にアクセスすることができます。
ログインする
これで、LIFF URLにアクセスするとLIFFアプリとして起動するようになりました。
ここからは、LIFFアプリの中でログインする方法を紹介します。
LIFFアプリでは、フロントエンドでIDトークンを取得し、バックエンドでそれを検証することで、ログインを実現します。
Railsアプリで実装する流れとしては
- フロントでIDトークンを取得する
- 取得したIDトークンをバックエンドに送信する
- バックエンドでIDトークンを検証し、IDから、DBのユーザーと紐付ける
という流れになります。
フロントでIDトークンを取得する
LIFFアプリの中で、IDトークンを取得するには、LIFF SDKを使います。
LIFF SDKは、LIFFアプリの中で、LINEの機能を利用するためのライブラリです。
今回はStimulusを使って、LIFF SDKのイベント発火を監視しています
!!!
%html
%head
%script{charset: "utf-8", src: "https://static.line-scdn.net/liff/edge/2/sdk.js"}
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["liffId", "idToken"]
connect() {
liff.init({
liffId: this.liffIdTarget.value // LIFF IDを取得
}).then(() => {
if (!liff.isLoggedIn()) {
liff.login();
} else { // すでにログインしている場合は、期限切れかどうかを確認
if (liff.getDecodedIDToken().exp < Date.now() / 1000) {
liff.logout()
liff.login()
}
}
})
.then(() => {
this.idTokenTarget.value = liff.getIDToken()
})
.then(() => {
setTimeout(() =>{
this.dispatch("login")
}, 5000)
})
.catch((err) => {
console.log(err.code, err.message);
});
}
}
取得したIDトークンをバックエンドに送信する
.flex.justify-center.pt-20
.animate-ping.h-6.w-6.bg-blue-600.rounded-full
%p.text-ld-gray.text-center.pt-4
ログインしています...
.hidden{'data-controller' => 'submitable liff', 'data-action' => "liff:login@window->submitable#submit"}
= hidden_field_tag :liff_id, Rails.application.credentials.dig(:line, :liff_id), {'data-liff-target' => "liffId"}
= form_with url: sessions_path, method: :post, local: true, data: {'submitable-target' => 'form'} do |f|
= f.hidden_field :id_token, value: nil, data: {'liff-target' => "idToken"}
= f.hidden_field :path, value: params[:path]
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "form" ]
submit() {
this.formTarget.submit()
}
}
LIFFログインが完了したらsubmitable_controllerのsubmitメソッドが呼ばれ、formがsubmitされます。
バックエンドでIDトークンを検証する
続いて、先ほどのフォームで送信されたIDトークンを検証します。
class SessionsController < ApplicationController
def new; end
def create
user = User.find_by_id_token!(id_token: params[:id_token])
session[:user_id] = user.id
flash[:success] = 'ログインしました'
redirect_to XXX_path
end
end
class User < ApplicationRecord
validates :line_id, presence: true, uniqueness: true
def self.find_by_id_token!(id_token:)
channel_id = Rails.application.credentials.dig(:line, :channel_id)
uri = URI.parse('https://api.line.me/oauth2/v2.1/verify')
params = { 'id_token' => id_token, 'client_id' => channel_id }
res = Net::HTTP.post_form(uri, params)
find_by!(line_id: JSON.parse(res.body)['sub'])
end
end
line_idというレコードと、IDトークンのsubを紐付けています。
これで、sessionにuser_idが入るようになり、ログインが完了します。
ApplicationControllerにcurrent_userメソッドを追加して、ログインしているユーザーを取得できるようにします。
class ApplicationController < ApplicationController
private
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
helper_method :current_user
end
まとめ
今回は、LIFFアプリを使って、LINEログインを実装しました。
LIFFアプリは、LINEの機能を使ったアプリを作るためのフレームワークです。
LIFFアプリを使うことで、LINEの機能を使ったアプリを簡単に作ることができるので、是非試してみてください。