投稿日
React + APIでFacebookのOAuth認証を実装した
はじめに
スタートアップ企業と一緒にサービスを開発し、ユーザー登録や認証を実装しました。 サービス開発にあたり、スタートアップ(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 での認証を実装する場合のポイントを紹介する前にフローを整理します。
- 認証が必要なページに遷移
- 未認証の場合、backendのサインイン用のURLに遷移
- backendからFacebook認可サーバーのURLへのリダイレクトレスポンスが返却
- Facebookで認証
- backendの認証後のCallBack用のURLへのリダイレクトレスポンスが返却
- CallBack用のURLでサービスが管理しているユーザーとFacebookの認証ユーザー情報を紐づける
- frontendのURLへのリダイレクトレスポンスを返却
ポイント
React+APIで実装する場合いくつかのポイントがあります。 それぞれをReact、APサーバに分けて記載します。
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"]
から provider
とuid
を取得しユーザー登録が完了(※)しているかを検証します。 ユーザーが登録されていれば、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 の「表示—継承」に準拠しています。