sorceryでRails +SPA構成のOauthサンプルがあまりなさそうなので
参考になれば。
環境
backend
- rails 6系
front
- react:16.12
- react-router: v5系
以下、sorceryのwikiに沿いつつ、SPA用でカスタムしたところを中心に記載
sorcery wiki
今回はgithub と連携してみる
開発の概要
Backend(Rails)
- oauthからのcallbackを受け取り、ユーザー作成及びログイン処理
Front(React)
- githubAPIへQueryString形式でパラメーターをもたせてアクセス。
- 認証後callBackURLにリダイレクトし、Backendに取得したパラメーターを送信
Backend側開発
external module関連のセットアップ
# external moduleのインストール
bundle exec rails g sorcery:install external --only-submodules
# migration
bundle exec rails db:migrate
# 認証用モデル作成
bundle exec rails g model Authentication --migration=false
sorceryのgithub認証関連設定変更
認証用情報の取得
github側の認証設定は以下から行う
https://github.com/settings/developers
initializers/sorcery.rb
Rails.application.config.sorcery.submodules = [:external] #:external追加
Rails.application.config.sorcery.configure do |config|
...
config.github.key = "your github key"
config.github.secret = "your github secret"
config.github.callback_url = ""
config.github.user_info_mapping = {:email => "email" }
config.github.scope = "user:email"
end
Oauth用controllerの作成
oauths_controller
class OauthsController < ApplicationController
# Frontで取得したToken情報をもとにユーザー認証をするMethod
def callback
provider = params[:provider]
# loginできた場合はここで200を返す
if @user = login_from(provider)
render json: { status: 'OK' }
else
begin
# loginできない場合は送られてきた情報をもとにユーザー作成
@user = create_from(provider)
reset_session
auto_login(@user)
render json: { status: 'OK' }
rescue
render json: { status: 'NG' }, status: 400
end
end
end
end
ルーティングの設定
- wiki記載の内容と異なり、oauth用tokenが送信されてくるAPIのみでOK
config/routes.rb
Rails.application.routes.draw do
...
post "oauth/callback" => "oauths#callback"
end
Front側開発
関連するルーティングの定義
router.jsx
const AppRouter = () => (
<Router>
<Switch>
<Route
path="/callback/:provider/"
component={ExternalAuthCallback}
/>
<Route path="/sign_in" component={SignIn} />
<Route component={NotFound} />
</Switch>
</Router>
);
export default AppRouter;
サインインページ
- サインインページの1機能としてGithub認証があるイメージ
- 通常のRailsのOauthと異なり、callbackされるURLはReactで構成されたSPAのURL(=>"/callback/:provider/")が叩かれることに注意
SignIn.jsx
// CONST.GITHUB.REDIRECT_URL = "http://localhost:3001/callback/github/"
const GITHUB_AUTH_URL = `https://github.com/login/oauth/authorize?client_id=${CONST.GITHUB.APP_ID}&redirect_url=${CONST.GITHUB.REDIRECT_URL}&scope=user:email`;
// ただ queryStringの付与したURLのリンクを踏ませるだけ
const signInForm = () => (
<div className={styles.submitBox}>
<Button href={GITHUB_AUTH_URL}>GITHUBで認証</Button>
</div>
);
export default signInForm;
CallbackURLコンポーネント
- callback時に叩かれるURLで利用するコンポーネント
- URLパラメーターでprovider名(今回は"github")を取得
- callbackURLのquesyStringに付与された認証情報(code=XXXX)を取得
- ReactからバックエンドAPIへPostする
oAuthCallback/index.jsx
import React, { useState } from "react";
import queryString from "query-string";
import { useHistory } from "react-router";
import { useParams, useLocation } from "react-router-dom";
import { api } from "../../../modules/user";
import Circular from "../../atoms/circular";
const BEFORE = "BEFORE";
const DOING = "DOING";
const ExternalAuth = () => {
const location = useLocation();
const history = useHistory();
const { code = "" } = queryString.parse(location.search);
const { provider = "" } = useParams();
const [requestStatus, setRequestStatus] = useState(BEFORE);
const request = () => {
setRequestStatus(DOING);
api.sendExternalAuthRequest({ code, provider }).then(isSuccess => {
if (isSuccess) {
history.push("/member/dashboard"); // login後ページ
} else {
history.push(PAGE_PATH.AUTH_SIGN_IN); //認証失敗した場合
}
});
};
if (requestStatus === BEFORE) {
request();
}
return (
<div className={styles.container}>
<Circular />
</div>
);
};
export default ExternalAuth;
Rails APIへのリクエスト
api.js
export const sendExternalAuthRequest = async ({
code,
provider
}) => {
const requester = requestManager.get();
return requester
.post(
"/oauth/callback",
{
code,
provider
},
)
.then(() => true)
.catch(() => false);
};