Help us understand the problem. What is going on with this article?

Rails (with sorcery)+ React でOAuth認証を実装してみた

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);
};

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away