はじめに

スタートアップ企業と一緒にサービスを開発し、ユーザー登録や認証を実装しました。 サービス開発にあたり、スタートアップ(PO)からは以下のような要望がありました。

  • Facebookで集客し、ターゲットはFacebookユーザー
  • ネイティブアプリではなく、WebアプリでPWAで提供したい
  • 技術者を確保しやすいため、backendはRailsを利用してほしい(ただし、開発チームに経験者はいない)

上記の前提があったため、React + APIモードのRailsという構成で開発しました。

認証方式は「Facebookで集客」という前提があったので、FacebookのOAuthを利用しました。 Railsには認証を行う Devise というライブラリと、OAuthをサポートしている OmniAuth というライブラリがあり、今回はこれらを使用することにしました。

DeviseとOmniAuthでの認証機能を実装することに関しては多くの資料が公開されていますが 多くがRailsでのWebアプリケーションであったため、APIモードのRailsアプリケーションでの実装方法を記載します。

今回触れないこと

  • OAuthとはなにか
  • Facebookのアプリの登録や設定
  • Deviseの設定

認証プロセス

React + API での認証を実装する場合のポイントを紹介する前にフローを整理します。

  1. 認証が必要なページに遷移
  2. 未認証の場合、backendのサインイン用のURLに遷移
  3. backendからFacebook認可サーバーのURLへのリダイレクトレスポンスが返却
  4. Facebookで認証
  5. backendの認証後のCallBack用のURLへのリダイレクトレスポンスが返却
  6. CallBack用のURLでサービスが管理しているユーザーとFacebookの認証ユーザー情報を紐づける
  7. frontendのURLへのリダイレクトレスポンスを返却

ポイント

React+APIで実装する場合いくつかのポイントがあります。 それぞれをReactAPサーバに分けて記載します。

React

Reactの実装のポイントはソースコードおよびコメントをベースに記載します。

下記のように未認証の場合の振る舞いを一つのコンポーネントで実装することで、それぞれのページで実装する必要がなくなります。

class RequireAuth extends React.Component {

  static contextType = AppContext;

  async componentDidMount() {
    const { user } = this.context;
    // user情報が取得できていない場合は未認証
    // hrefをbackendのurlに変更し、サインインへのGETリクエストを飛ばす。
    if (!user.id) {
      window.location.href = "/users/sign_in";
    }
  }

  render() {
    const { user } = this.context;
    if (!user.id) {
      return (<Loading/>);
    }
    const { children } = this.props;
    return (
      <React.Fragment>
        {children()}
      </React.Fragment>
    );
  }
}

Rails

OAuth 2.0の認可プロセスでは一度外部サイト(Facebook)を経由する必要があるため、認証後のリダイレクト先(ユーザーが認証前に表示しようとしていた画面)がわからなくなります。 そのため、Facebookへリダイレクトする前にリファラーをセッションへ格納します。

# ログインの前処理を行うコントローラーです。
#
# あ、自己紹介が遅れましたがTIS 西日本テクノロジー&イノベーション室の谷です。
# ずっとSIでアプリや基盤のコードを書いていましたが、2019年~サービス開発のお手伝いもするようになりました。
class Users::SignInController < ApplicationController

  def index
    # ログイン後のリダイレクト先にするため、リファラーをセッションへ格納します
    login_referer = request.headers[:referer]
    if current_user then
      # 既にログイン済みの場合は元の画面かホーム画面へリダイレクト
      if login_referer then
        redirect_to login_referer
      else
        redirect_to home
      end
    else
      # 未ログインならリファラーをセッションへ格納してログイン画面へリダイレクト
      session[:login_referer] = login_referer
      redirect_to "/users/auth/facebook"
    end
  end
  ## 中略
end

認証時のCallBackでは request.env["omniauth.auth"] から provideruidを取得しユーザー登録が完了(※)しているかを検証します。 ユーザーが登録されていれば、Sessionに保存した「ユーザーが表示したかった画面」にリダイレクトします。 ユーザーが登録されていない場合は、frontendのユーザー登録画面のURLにリダイレクトします。

# 認証後のcallback時に処理を追加するためを拡張する
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    do_callback request.env["omniauth.auth"]
  end

  private

  def do_callback(omniauth)
    uid = omniauth.uid
    provider = omniauth.provider
    user = UserAuth.where(uid: uid, provider: provider).first
    if user then
      sign_in user
      # リファラーがあればそこへリダイレクト、なければホームへリダイレクト
      login_referer = session[:login_referer]
      if login_referer then
        session[:login_referer] = nil
        redirect_to login_referer
      else
        redirect_to home
      end
    else
      # ユーザー登録の場合はセッションにOAuthログイン情報をセットして登録画面へリダイレクト
      redirect_to registration
    end
  end

まとめ

当初はRailsでOAuthログインを実装する難易度が分からず不安もありましたが、DeviseとOmniAuthを使うことで比較的簡単に実装することができました。

なお、今回のサービス開発ではFacebookのOAuthを利用しましたが、ユーザーの投稿やメッセージを利用しませんので他のプロバイダでも同じように実装できます。


本コンテンツはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。