Rails7でLIFFを用いたLINEログインのハンズオンを作ってみた



## はじめに


Railsの7系にLIFF（Line Front-end Framework）を組み込んだアプリを開発したのですが、下記の点からかなり苦戦しました。

・LIFF特有の構文への自身の不慣れさ
・Rails7ではアセット管理の方法が様々な種類がある
・RailsでLIFFを活用するための参考資料の不足


そこで今回は、Railsを使用してLINEを組み込んだアプリを作成したい方に向けて、LIFFの活用方法について解説する記事を作成しました。
この記事では、実際の手順に沿ってハンズオン形式で学習を進めていきます。
参考にしていただければ幸いです。

今回作成するのは、トップページに設置したボタンを押すだけでログインができ、ログイン前後でページの内容が切り替わるというシンプルなアプリケーションです。


記事を通じて以下のトピックに触れていきます：

・LIFF（Line Front-end Framework）
・LINEログイン
・esbuild
・Hotwire
・Session
・OAuth
・mkcert（SSL通信）

※私自身が未経験のため、説明に誤りがある可能性もございます。もし記事の内容に誤りや改善の余地があると感じられた場合は、お手数ですがご指摘いただければ幸いです。



## 作成物のイメージ

本記事で取り上げるアプリケーションの例は、「Sunkissing」という、日焼け止めの塗り直し通知アプリです。

https://www.sunkissing.net


LIFFを使ってLINEログインの実装もしております。
そのため、LINEログイン機能の実装を検討している方にもこちらのハンズオンは役に立つと思います。

本アプリのURLをLINEのトーク画面に送り、スマートフォンのLINEアプリから開いてみてください。PCよりもスマートフォンのLINEアプリから開いた方が、よりスムーズに動作するので是非お試しください。


今回のハンズオンのコードはこちらです。
https://github.com/mayuyuyuyunn/liff_sample_app



## 使用技術

Rails 7.0.4
ruby 3.1.2
Nodejs v20.3.1
LIFF(LINE Front-end Framework) v2.22.2
mkcert v1.4.4


## LIFFとは

LIFFは、LINE Front-end Frameworkの頭文字を取ったものです。

> LINE Front-end Framework（LIFF）は、LINEが提供するウェブアプリのプラットフォームです。このプラットフォームで動作するウェブアプリを、LIFFアプリと呼びます。

LIFFとはLINEアプリ上で動作するブラウザのことです。
LINEのトーク画面でURLを開くと、ChromeやSafariなどの外部ブラウザが開きますが、LIFFアプリのURLを開くと、LINEアプリ内部でブラウザが開きます。
またLIFFアプリは、他のブラウザで使用することもでき、登録しているメールアドレスやユーザーIDなど、LINEの情報を取得できるアプリとなります。

LIFFを使用する主なメリットは以下の3つです：

- LINEログインが使用できる
- LINEのトーク画面からLIFFアプリを開くと、初回を除いて自動的にログイン処理が行われる
- ユーザーID、友だち情報、アイコン写真など、LINEの情報を利用できる


## LINEログインとは

無料で利用できるLINEアカウントを使用したソーシャルログインサービスです。
初回登録時にユーザー名やパスワードを考えて入力する手間がないため、ストレスフリーです。
詳細な情報は[LINEログインの概要](https://developers.line.biz/ja/docs/line-login/overview/)をご覧ください。

## 準備編

### Railsプロジェクト作成
Railsの7系で作成しておきます。

```
$ mkdir liff_sample_app
$ cd liff_sample_app
$ rails _7.0.4_ new . --javasctipt=esbuild --skip-hotwire --css=bootstrap
```
Rails newのオプションについて説明します。（分かる方は飛ばしてください。）

- `--javascript=esbuild` 
JavaScriptのオプションで、esbuildを指定しています。

- `--skip-hotwire` 
Rails 7からデフォルトとなったフレームワーク、Hotwireに関するオプションです。Hotwireを使うと、JavaScriptを書かなくてもSPAのようなページを作成できます。今回のアプリでは利用しないのでスキップします。
今後開発をする中で、意図する挙動をしない場合は、Hotwireが原因の可能性もあります。後々開発で助かることもあるかもしれないので、頭の片隅に入れておきましょう。


- `--css=bootstrap`
 CSSのフレームワークとしてBootstrapを指定します。Bootstrapを導入することで、簡単に見た目を整えることができます。


### mkcertの導入
ローカル開発環境でSSL/TLS証明書を作成するためのツール、mkcertを導入します。

https://github.com/FiloSottile/mkcert

ローカルサーバーを起動するときのURLは通常`http://localhost:3000`となりますが、LINE DevelopersコンソールではコールバックURLの指定に`http`は受け入れられません。
したがって、LIFF開発ではURLを`https`にする必要があります。

mkcertのインストールをします。今回は、Homebrewで入れていきます。

```
$ brew install mkcert
$ mkcert -install
```
mkcertを使用して、SSL証明書を発行するための証明証を作成します。
```
$ mkcert localhost
```

`localhost-key.pem`と`localhost.pem`という2つのファイルが生成されたかと思います。
次に、`config`ディレクトリ内に`certs`ディレクトリを作成し、生成した2つのファイルを移動します。
これらのファイルはGitHubにアップロードしないように、`.gitignore`に`/config/certs/*`を追記してください。

次に、`config/puma.rb`の設定を行います。
ここでは3000番のポートで起動する記述`port ENV.fetch("PORT") { 3000 }`をコメントアウトし、 mkcertで作成したSSL証明書を指定します。


```config/puma.rb
# port ENV.fetch("PORT") { 3000 }　　←元々記載があるのでコメントアウトする
ssl_bind "0.0.0.0", "3000", {
  cert: "config/certs/localhost.pem",
  key:  "config/certs/localhost-key.pem"
}
```

これでローカルの開発環境がhttpsで起動できるようになりました。


### modelとテーブルの作成
次に、Userの情報に`line_user_id`を設定します。
通常のユーザー認証では`name`と`password`を組み合わせて使用しますが、LINEログインを使用する場合はINEが提供する一意の`user_id`を取得して利用することができます。

```sh:terminal
$ bundle exec rails g model User line_user_id:string
```


マイグレーションファイルを以下のように設定します。ここではnullと重複を許容しないように制約を設けます。
```ruby:db/migrate/XXXXXXXXX_create_user.rb
class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :line_user_id, null: false, unique: true

      t.timestamps
    end
  end
end
```

マイグレーションを行います。

```sh:terminal
$ bundle exec rails db:migrate
```
マイグレーションが完了したら、同じようにモデルにもバリデーションをかけましょう。

```ruby:model/user.rb
class User < ApplicationRecord
  validates :line_user_id, presence: true, uniqueness: true
end
```

### controllerとviewの作成
Userに関するコントローラーとviewを作成します。
今回はユーザー作成とログインに関する機能のみ作成するのでnew createアクションを作成します。

```sh:terminal
$ bin/rails g controller users new create
```

ルーティングは下記の通りです。
```ruby:config/route.rb
Rails.application.routes.draw do
  resource :user, only: %i[new create]
end
```

ApplicationControllerにログイン関係のヘルパーメソッドを作成していきます。
sessionはログイン機能などで使われるステートフルな通信を保つための仕組みですね。
突然`session[:user_id]`が出てきて少し困惑する方もいるかと思いますが、`session[:user_id]`の値を取得できれば、current_userがわかるというイメージがあればOKです。


```ruby:application_controller.rb
class ApplicationController < ActionController::Base
  helper_method :current_user
  helper_method :login_required
  helper_method :logged_in?

  private

  def current_user
    # @current_userがnilでsession[:user_id]に値が入っている場合、ユーザーを持ってくる
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end

  def login_required
    # ログインしていない（current_userが存在しない場合）root_pathに飛ばす
    redirect_to root_path unless current_user
  end

  def logged_in?
    !current_user.nil?
  end
end
```

ログイン前後で見た目を分けたいので``static_pages``コントローラーと該当するページを作成します。

```
$ bundle exec rails g controller StaticPages top before_login after_login
```

```static_pages_controller.rb
class StaticPagesController < ApplicationController
  def top
    if current_user.nil?
      render :before_login
    else
      render :after_login
    end
  end

  def before_login; end

  def after_login
    login_required
  end
end
```

```route.rb
Rails.application.routes.draw do
  root 'static_pages#top'
  get '/after_login', to: 'static_pages#after_login'
  resource :user, only: %i[new create]
end
```
before_loginとafter_loginのviewファイルを書きます。

```html:before_login.html.erb
<div class="container">
  <div class="jumbotron">
    <h1 class="p-3 mb-2 bg-primary text-white">ログイン前のページです</h1>
    <p>This is the homepage of my awesome website.</p>
    <%= link_to '（LINE）ユーザー登録・ログイン', new_user_path, class: 'btn btn-success btn-lg' %>
  </div>
</div>
```
```html:after_login.html.erb
<div class="container">
  <div class="jumbotron">
    <h1 class="p-3 mb-2 bg-danger text-white">ログイン後のページです</h1>
    <p>This is the homepage of my awesome website.</p>
  </div>
</div>
```

``$ bin/dev``でサーバーを起動して、``https://localhost:3000/``にアクセスしてみてください。
``before_login``の画面がでます。「ユーザー登録・ログイン」ボタンをクリックして``https://localhost:3000/user/new``に遷移してればOKです。
確認できたら、``ctrl + c``でサーバーを止めておいてください。


下準備編、次がラストです！
LIFFの処理はJavaScriptのファイルに書くので、JSファイルを作成していきます。

``app/javascript/new.js``を作成してください。
デバックのために、``console.log('new');``と書いておいてください。
```javascript/new.js
console.log('new');
```

`new.js`を``users/new.html.erb``で読み込ませます。

```users/new.html.erb
<p>Loading・・・</p>
<p>※時間がかかることがございます。</p>
<%= javascript_include_tag "new", "data-turbo-track": "reload", defer: true %>
```


再度``https://localhost:3000/``にアクセスして、ボタンを押すと
``https://localhost:3000/user/new``にアクセスできるか確認してください。
検証ツールでコンソールを開いて、下記のように``new``の文字が出力されていればOKです。

![liff_sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782497/eed95141-0d0b-2043-39ca-ed2895ae2db9.gif)


## LINE Developersの設定編

LINE Developersのページを開いて設定していきます。
以下のURLにアクセスし、普段ご自身が使っているアカウントでログインしてください。

https://developers.line.biz/ja/

コンソール（ホーム）からプロバイダを作成します。
名前は任意ですが、今回は「LIFFサンプル」と命名しておきます。


左側のメニューから「LINEログイン」をクリックして、チャネルを作成します。
![liff_sampleのコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782497/f87e1ab3-6457-2c17-7422-c68c73496bec.png)



チャネルの種類では「LINEログイン」、プロバイダでは先ほど作成した「LIFFサンプル」、そしてアプリタイプは「Webタイプ」 を選んでください。他の必要な項目はお好きに設定し、契約内容に同意できたら作成ボタンを押してください。

![59c35b34dfb0401ff3d1fed2d78ec1f2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782497/fa0193c1-9e47-ac88-64f0-e775409eda33.png)




チャネル基本設定の一番上にある「チャネルID」をメモしておきます。このIDは後で使用します。

![0db2bbe583dbb2a99775c61cde76df5f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782497/3c71bd76-9dc2-0d05-2643-f349f36b755c.png)



```text:memo.txt
CHANNNEL_ID="チャネルIDの値"
```




次に、チャネル基本設定の隣のタブ「LINEログイン設定」をクリックし、LINEログインを使用できるように設定します。
コールバックURLには`https://localhost:3000/user/new`を入力してください。

![12c6b1df5f16245c1675f071cc079668.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782497/62a84489-9d56-eaae-86be-19a6b5db4517.png)


さらに隣のタブで「LIFFアプリ」を追加します。

https://developers.line.biz/ja/docs/liff/registering-liff-apps/#registering-liff-app

下記の画像を参考にして入力してください。エンドポイントURLは`https://localhost:3000/user/new`と設定してください。

![d2463b3015fcf214fe00abffa60af455.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782497/476dbb3c-471c-4668-82c5-81aca514d8a6.png)


作成すると、一番上にLIFF_IDが表示されます。この値も後で使用するため控えておいてください。

```memo.txt
CHANNNEL_ID="チャネルIDの値"
LIFF_ID="LIFF IDの値"
```
:::note warn
注意
この2つの値はGitHubなどにあげないように注意してください。
.envなど環境変数を用いてください。
:::


## RailsとJavaScriptのコード編



`new.js`にLIFFを利用したコードを書いていきますが、その前にLIFFの仕組みについて簡単に説明します。
詳しい仕組みは下記の公式のシーケンス図なので、いつでも確認できるようにしておきましょう。

![1927693bfa8cf0d4158562cfadce8238.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782497/bf1e31fb-5a37-b636-5137-7194d8a7c924.png)


1.LIFFで作成されたアプリのURLを開く
2.LIFF SDKにアクセスして、LIFFの初期化とログイン処理を行う
3.LINE PlatformにアクセスしてLINEログイン処理をする
4.成功した場合、LIFFSDKにID tokenをくださいとリクエストが投げることができる
5.無事に取得できたら、ID tokenと一緒にLINE Platformにアクセスする
6.今回使用したいuser_IDだったり、ユーザー名、アイコン、登録しているメールアドレスなどの情報が取得できる
という仕組みのようです。


矢印が複数あり複雑に見えますが、赤い丸で囲ってあるゴール部分から説明していきます。

ゴールは、自分のサーバー（今回ならRails）から`ID token`を含むリクエストをLINEプラットフォームに送信し、`user_id`や`email`などの情報を取得することです。

URLに`oauth2`とあるように、ここでは`OAuth認証`を利用しています。悪意のあるリクエストを防ぐため、事前にトークンを発行し、そのトークンを添えてサーバーに情報送信の許可を得ます。
OAuthについては以下の記事を参考にしてください。

https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be

このプロセスではユーザー、作成したLIFFアプリ、LIFF SDK、アプリケーションサーバー、LINEプラットフォームの5つの要素が登場します。
SDKとはSoftware Development Kitの略称で、LIFF SDKはLIFF開発を簡単に行うために用意されたツールセットです。


詳細に知りたい方は、下記公式をみてください。

https://developers.line.biz/ja/docs/liff/using-user-profile/


まず、LIFF SDKを使用するために、scriptタグで読み込みをします。
下記のコードを記載することで、``liff.init``などLIFFで用意されているメソッドを使えるようになります。
``application.html.erb``にあるbodyタグの中にLIFF SDKを読み込むためのscriptタグ追加してください。

```application.html.erb
  <body>
    <%= yield %>
    <script charset="utf-8" src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>
  </body>
```


続いてJavaScriptでコーディングします。
ここで先ほどLINE Developers編で作成した``LIFF_ID``を利用します。

 ```new.js
// DOMが読み込まれたら処理が走る
document.addEventListener('DOMContentLoaded', () => {
  // csrf-tokenを取得
  const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
  // LIFF_ID を定数定義
  const LIFF_ID = "自身のLIFF_IDをコピペする";
  // LIFF_IDを使ってLIFFの初期化
  liff
  .init({
    liffId: LIFF_ID,
    // 他のブラウザで開いたときは初期化と一緒にログインもさせるオプション
    withLoginOnExternalBrowser: true
  })
    // 初期化後の処理の設定
  liff
   .ready.then(() => {
    //  初期化によって取得できるidtokenの定義
    const idToken = liff.getIDToken()
    // bodyにパラメーターの設定
    const body =`idToken=${idToken}`
    // リクエスト内容の定義
    const request = new Request('/user', {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
        'X-CSRF-Token': token
      },
      method: 'POST',
      body: body
    });

    // リクエストを送る
    fetch(request)
    // jsonでレスポンスからデータを取得して/after_loginに遷移する
    .then(response => response.json())
    .then(data => {
      data_id = data
    })
    .then(() => {
      window.location = '/after_login'
    })
  })
})

```

liffの構文について詳しく知りたい方は、下記リファレンスを確認してください。

https://developers.line.biz/ja/reference/liff/

続けて、Userコントローラーです。
LINE Developers編で作成した``CHANNNEL_ID``を入れるのを忘れないようにしてください。

```users_controller.rb
class UsersController < ApplicationController
  require 'net/http'
  require 'uri'

  def new
    if current_user
      redirect_to after_login_path
    end
  end

  def create
    id_token = params[:idToken]
    channel_id =　"保存したチャネルIDを入れる"
    res = Net::HTTP.post_form(URI.parse('https://api.line.me/oauth2/v2.1/verify'), { 'id_token' => id_token, 'client_id' => channel_id })
    line_user_id = JSON.parse(res.body)['sub']
    user = User.find_by(line_user_id:)
    if user.nil?
      user = User.create(line_user_id:)
    elsif (session[:user_id] = user.id)
      render json: user
    end
  end
end
```



`create`アクションは先ほどのシーケンス図で赤い印でゴール部分で説明した部分です。自分のサーバーからLINEプラットフォームに``ID token``を引っ提げてリクエストを送っています。
``id_token = params[:idToken]``でOAuth認証に必要な``ID token``を取得しているのですが、これは先ほど``new.js``に書いたコードのおかげで持ってこれています。bodyに情報を入れてリクエストをおくりました。どこの部分か分からない方は、少し戻って``new.js``のコードを見てください。
net/httpライブラリとurlライブラリを使用して、LineプラットフォームにUser情報を取得するためのリクエストを投げています。
最後に、line_user_idを取得して、初登録の方はline_user_idを保存、すでに登録している方はsession[:user_id]に取得したline_user_idを入れてログイン処理を行っています。



再度、サーバーを起動して新規登録・ログインをしてみましょう！
LINEログインの認証画面に遷移し、ソーシャルログインができるかとおもいます。


## 最後に


お疲れ様でした！！！
これにて、LIFFを用いたLINEログインのハンズオンは完了です。
LINEは現在日本でいちばん使われているコミュニケーションツールのため、
通知系のアプリを作りたい方はLINEを使用して開発するのが便利で良いのかなと思います。
今回LIFFを使用して個人開発をしてみて、Hotwire、セッション、oAuth、SSLなど様々なことを学習するきっかけになりました。
ハンズオンをしてみて、分からないところがあれば調べて復習するといい勉強になると思うのでぜひやってみてください。
この記事が、個人開発をする上での一助になれば幸いです。
最後までお付き合いいただきありがとうございました。



個人開発のアプリ、是非使ってみてください〜 :sparkles: 
https://www.sunkissing.net/