Ruby on RailsのWebアプリで、「Mastodonでログイン」を作ってみた

はじめに

Mastodonインスタンスの一つ、Creatodonを普段は運営しています。
メインのユーザー層としては創作活動をされている方々で、絵を描かれる人や物書きが使われていますねー(かくいう私もフリーゲーム作ってます)

去年の五月に、さくらのクラウドを使ってインスタンスを建てました。

が、メディアストレージを結構使ってて辛かったり(Pawooとかと繋がっている為)、
一回ストレージ関連でインスタンスが正常に動作しなかったこともあり(cronでのキャッシュの削除が上手くいっていなかった)

そういうこともありCreatodon Folioという画像投稿サービスを作りました。

基本的には、インスタンスのユーザーが絵や漫画を気軽に投稿できる場所として公開していますねー。

まあ、Creatodon Folio 作った後に AWS S3 へメディアストレージを移したので作った意味があったかといわれると……。

まあ、いろいろとRailsの勉強になったのでOKだと思ってます。

今回の記事では、実際に「Mastodonでログインする機能」を実装するところを解説していきます。

実際に作ったもの

で、実際に作ったものはこちらになります。

Creatodon Folio

ちなみに、ソースコードはこちら

Railsの教科書を参考にさせていただきながら制作させて頂きました。

環境としては Heroku + AWS S3 を使って構築しています。
そのため、Heroku dyno の再起動の影響もなく画像を表示できていますねー。
おまけに AWS を使っているのでコストもはるかに安く済んでいます。

あと、今回の記事用に「Mastoonでのログインする機能」のみを実装したサンプルを作っておきました。

本記事のみで、分かりにくいようでしたらそちらを参照されると良いかと思います。

作る手順

制作環境

使用環境
OS:Windows 10 Pro
Ruby: 2.3.3
Rails : 5.1.4

上記の環境にてサンプルを制作しました。
ですので、対象はWindowsユーザーになります。ご了承くださいませ。

また既にHerokuアカウントを持っているものとして話を進めていきます。
アカウントを持っていない方はこちらの記事を参考にされると良いと思います。

なお、このほかHeoku CLI を使う場面もありますのでこちらより実行ファイルをダウンロードしてインストールしておいてください。

Herokuでの下準備

まず、コマンドプロンプト又はPowerShellを起動し、以下のコマンドを実行して Heroku 上にアプリケーションを作成します。

heroku apps:create -a <アプリ名>

これで、Heroku での下準備は終了

Mastodonでの下準備

自分のアカウントがあるMastoonインスタンスにて
「ユーザー設定」をクリックし、「開発」という項目をクリックします。
「新規アプリ」をクリックし、「アプリ名」を入力の上「送信」をクリックします。

その後、作成したアプリをクリックすると「クライアントキー」と「シークレットキー」を取得できます。
この二つは後々で使いますのでわかるようにメモしておきます(ただし、第三者とは絶対に共有しないでください!)

Mastoonでの下準備はこれで終了です。

Railsでの実装

Railsアプリケーションのひな型作成

それでは、いよいよRailsでの実装といきましょう。

まず、コマンドプロンプト又はPowerShellを起動し、以下のコマンドを実行して アプリケーションを作成します。

rails new <アプリ名>`

その後、下記のコマンドでディレクトリを移動します。

cd <アプリ名>

それから、以下のコマンドを実行。

rails g controller MastodonLogin index

今回はログインする機能とログインできたかが確認できればいいので、view に関してはindex のみを作ります。

index.html.erb が作成された後、config/routes.rbroot_path を設定します

root 'mastodon_login#index'

次に、Gemfile を編集します。

まず、以下のように必要なgemを追記します

gem 'coffee-script-source', '1.8.0'

gem 'devise'

gem 'mastodon-api', require: 'mastodon'
gem 'omniauth-mastodon'
gem 'omniauth'

その後、sqlite3 を下記のように開発環境でしか利用しないように設定します。

group :development do
  gem 'sqlite3'
end

また本番環境用に以下のように記述を追記しておきます。

group :production do
  gem 'pg'
end

Gemfile の編集が終わったら、bundle update bundle install の順にコマンドを実行してください。

Devise のインストール

今回のログイン機能を実装するにあたってDevise とよばれる gem を使用します。
詳しいことはこちらに書いてあるので参照してください。

以下のコマンドを実行してDviseをインストールします。

rails g devise:install
rails g devise:views
rails g devise user

上記のコマンドをサラッと説明すると

rails g devise:installは Devise のインストール。
rails g devise:views は Devise 用の view の作成
rails g devise user は Devise で使用するmodelの作成

となっています。

その後、rails db:migrate を実行し、DBに適用します。

Mastodonアカウントでのログイン機能の実装

まず、今のままではUserモデルの中にMastodonでのidなどを記憶できないので

rails g migration AddColumnsToUsers uid:string provider:string

上記のコマンドを実行してカラムを追加します。

次に、Mastodonのドメイン情報などを記憶するモデル:MastodonClient を以下のコマンドで作成します。

rails generate model MastodonClient domain:string client_id:string client_secret:string 

次に、config/initializers/devise.rbに先ほど取得したアクセスキーなどを記述していきます。

実際の記述例としては以下のようになります。

Devise.setup do |config|

  #(中略)

  # ==> OmniAuth
  # Add a new OmniAuth provider. Check the wiki for more information on setting
  # up on your models and hooks.
  # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'

  config.omniauth :mastodon, 'ACCESS_KEY', 'SECRET_KEY'

  config.omniauth :mastodon, scope: 'read follow', credentials: lambda { |domain, callback_url|
  client = MastodonClient.where(domain: domain).first_or_initialize(domain: domain)

  return [client.client_id, client.client_secret] unless client.new_record?

  new_client = Mastodon::REST::Client.new(base_url: "https://#{domain}").create_app('MastodonLoginSample', callback_url, 'read follow')

  client.client_id = new_client.client_id
  client.client_secret = new_client.client_secret
  client.save

  [client.client_id, client.client_secret]
}

  #(中略)

end

で、'ACCESS_KEY', 'SECRET_KEY' に先ほど取得したクライアントキーなどを記述します(本当はdotenvあたりを使って実際のソースコードに記述しない方が良い)

お次は、app/controllers/users/omniauth_callbacks_controller.rbを作成して以下のように記述します。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def mastodon
    callback_from :mastodon
  end

  private

  def callback_from(provider)
    provider = provider.to_s

    @user = User.find_for_oauth(request.env['omniauth.auth'])

    if @user.persisted?
      flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
      sign_in_and_redirect @user, event: :authentication
    else
      session["devise.#{provider}_data"] = request.env['omniauth.auth']
      redirect_to new_user_registration_url
    end
  end
end

このコードがないと、Mastodonで認証した後にWebアプリまで戻ってくることができませんので注意。あと、Userデータなども保存されません。

そして、models/user.rbに以下のように:omniauthable を追記します。

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :omniauthable

そして、ログイン時に既にアカウントデータが作成されているかを判断するfindメソッドを記述します。
その為、最終的には以下のようになっています。

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :omniauthable

  def self.find_for_oauth(auth)
    user = User.where(uid: auth.uid, provider: auth.provider).first

    unless user
      user = User.create(
        uid:      auth.uid,
        provider: auth.provider,
        email:    User.dummy_email(auth),
        password: Devise.friendly_token[0, 20]
      )
      user.save!
    end
    current_user = user
  end

  private

  def self.dummy_email(auth)
    "#{auth.uid}"
  end
end

そして、config/routes.rb

devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }

を設定します。

下記のようになっていればOKです。

Rails.application.routes.draw do
  devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
  root 'mastodon_login#index'
end

あとは、index.html.erbを以下のように記述してログイン用のリンクとログインした後に表示される文字を設定します。

<h1>MastodonLogin#index</h1>
<p>Find me in app/views/mastodon_login/index.html.erb</p>

<%= link_to "Log In for Mastodon Account", user_mastodon_omniauth_authorize_path %>
<br>
<% if user_signed_in? %>
 Your Welcome !
<% end %>

ログインされていると、「Your Welcome !」 という文章が表示されるようになっています。
これで、とりあえずは「Mastodonでログインする機能」は実装されました。

Herokuへのデプロイ

それでは、最後に実際の本番環境で試してみましょう。

まず、Heroku 上のアプリケーションのリポジトリとローカルにあるRailsのリポジトリを以下のコマンドで繋げます。

heroku git:remote -a <アプリ名>

で、次に以下のコマンドでHeroku上のリポジトリにpushします。

git add .
git commit -am "mastodon login test"
git push heroku master

そして、最後に以下のコマンドを実行してやれば、「Mastodonでログイン」できるようになります。

heroku run rails db:migrate -a <アプリ名>

あとは、以下のコマンドで実際のWebサプリを開いて

heroku run app:open -a <アプリ名>

「Log In for Mastodon Account」をクリックして、実際に使っているMastodonアカウントを入力するだけ

さいごに

とりあえず、今までの手順でRailsのWebアプリケーションで「Masotodonでログインする機能」を使えるようになります。

正月休み中ずっと実装のために格闘していたので、ようやくのんびりできそうです。

参考文献

Heroku初心者がHello, Herokuをしてみる

【Rails備忘録】deviseまとめ

RailsなサービスでMastodonとのOauth連携を実装する

Devise+OmniAuthでユーザ認証を実装する手順

mastodon-bridge

またMastodonでのログイン機能実装にあたっては kenchiki 氏からアドバイスなどをいただきました。

この場を借りて感謝申し上げます。