LoginSignup
7

More than 1 year has passed since last update.

【画像手順解説】Auth0 Rails(api) + React SPAでユーザー認証機能を実装しよう 3 (Rails(api) + React編)

Last updated at Posted at 2021-07-29

はじめに

こんにちは、つよしと申します。転職のために、railsとreactでユーザー認証付きのSPAのポートフォリオを作成しました。

この記事では、Auth0 Rails(api) + React SPAでユーザー認証を実装する方法を解説します

初学者のため、間違っている情報があるかもしれません。
その場合は、ご指摘,もしくは適宜読み替えて勧めていただけたらと思います。


この記事は3部構成になっています。

【画像手順解説】Auth0 Rails(api) + React SPAでユーザー認証機能を実装しよう 3 (Rails(api) + React編)
→ 本記事

この記事で最終的に出来ること

  • Rails(API) + React SPAでユーザー認証機能を実装
  • current_user機能
  • Authenticate_user機能
  • 新しいユーザーがサインアップした場合は自動でユーザーをクリエイト

個別での使用方法も学べるので、どちらか一方の実装する方も参考になると思います。

本記事で解説すること

  • Rails(api) + Reactでユーザー認証を実装する
  • 簡単なRails + ReactのCRUD機能

注:part1とpart2を読んでから本記事に取り組んでください。

この記事でしないこと

機能を詳細に説明はせず、手順を丁寧に書いた記事にします。
参考となる記事を随所で貼るので、気になる方はそちらを御覧ください。

目次

  • Auth0とは?
  • どのような機構?
  • Rails アプリ設定
  • React アプリ設定

Auth0とは?

どのような機構

Rails アプリ設定

  • cors.rbに以下を記述します
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:3001'

    resource '*',
            headers: :any,
            methods: %i[get post put patch delete options head]
  end
end

React アプリ設定

  • .envファイルにAUDIENCEを追加します

AUDIENCEの値はpart2で控えたIdentifierです。

本記事の場合は
https://test-auth-apiとなります。

/.env
PORT=3001
REACT_APP_AUTH0_DOMAIN=[Domain]
REACT_APP_AUTH0_CLIENT_ID=[Client ID]
REACT_APP_AUTH0_AUDIENCE=[Identifier] // 追加
例: https://test-auth-api
REACT_APP_REST_URL="http://localhost:3000/api/v1"

※ []はいりません。文字列のみ入力してください。


  • index.tsxにaudienceを追加します。
src/index.tsx

...

const domain: string = process.env.REACT_APP_AUTH0_DOMAIN || ''
const clientId: string = process.env.REACT_APP_AUTH0_CLIENT_ID || ''
const audience: string = process.env.REACT_APP_AUTH0_AUDIENCE || '' // 追加

ReactDOM.render(
  <Auth0Provider
    domain={domain}
    clientId={clientId}
    audience={audience} // 追加
    redirectUri={window.location.origin}
  >
    <Provider store={store}>
      <App />
    </Provider>
  </Auth0Provider>,
  document.getElementById('root')

...

以上でAuth0の設定は終わりです。
次からはrailsとreactの通信に入っていきます。


通信の流れ

1.React側でtokenを取得
2.Rails(api)にtoken付きでリクエストを投げる
3.Rails(api)でcreate

以上のような流れで行います。


  • 通信をするために、axiosをインストールします。
console
npm install axios --save

React側でtokenを取得

ここからはログインした状態で進めてください。

/App.tsx
import React, { useEffect, useState } from 'react';
import './App.css';
import { useAuth0 } from "@auth0/auth0-react";

function App() {
// getAccessTokenSilentlyを追加
  const { isAuthenticated,loginWithRedirect,logout,getAccessTokenSilently } = useAuth0();
//追加
  const [token,setToken] = useState<string>('')

// 追加  
  useEffect(() => {
    const getToken = async () => {
      try {
        const accessToken = await getAccessTokenSilently({})
        setToken(accessToken)
      } catch (e) {
        console.log(e.message)
      }
    }
    getToken()
  }, [])

■ getAccessTokenSilently
Auth0の関数で、ログインしている場合にtokenを取得することが出来ます。
取得したtokenはuseStateのtokenに格納しています。


index

railsとreactの通信がちゃんと出来ているか確認するために、postsの投稿を取得してみます。
indexアクションはskip_before_actionで指定しているので、tokenは必要ありません。

今回は簡単に、ボタンで取得する形にします。

/App.tsx

import React, { useEffect, useState } from 'react';
import './App.css';
import { useAuth0 } from "@auth0/auth0-react";
import axios from 'axios';

// 追加
interface Post {
  title: string,
  caption: string
}

function App() {
  const { isAuthenticated,loginWithRedirect,logout,getAccessTokenSilently } = useAuth0();
  const [token,setToken] = useState<string>('')
// 追加
  const [posts,setPosts] = useState<Post[]>()

// 追加
  const fetchPosts = () => {
    axios.get('http://localhost:3000/api/v1/posts')
    .then((res) => {
      setPosts(res.data)
    })
  }
  ...

  return (
    <div className="App">
   ...

         //追加
        <h2>投稿一覧</h2>
          <button onClick={fetchPosts}>投稿取得</button>
        {posts?.map((post :Post,index:number) => 
        <div key={index}>
          <p>{post.title}</p>
          <p>{post.caption}</p>
        </div>
        )}
      </div>
    </div>
  );
}

export default App;

ボタンをクリックして、タイトル1,説明1が表示されれば成功です。
全画面_2021_07_29_22_03.png

表示されない場合は検証ツールでconsole.logやNewWork,railsのログをみて解消してください。

create

それでは最後にtoken付きのリクエストを送って投稿を作成したいと思います。

/App.tsx

...
// 追加
  const createPosts = () => {
    const headers = {
      headers: {
        Authorization: token,
        'Content-Type': 'application/json',
      }
    }
    const data = {
      title: 'タイトル2',
      caption: '説明2'
    }
    axios.post('http://localhost:3000/api/v1/posts',data,headers)
  }

...

  return (
... 
      //追加
        <h2>投稿作成</h2>
          <button onClick={createPosts}>投稿作成</button>
        <h2>投稿一覧</h2>
          <button onClick={fetchPosts}>投稿取得</button>
        {posts?.map((post :Post,index:number) => 
        <div key={index}>
          <p>{post.title}</p>
          <p>{post.caption}</p>
        </div>
        )}
      </div>
    </div>
  );
}

export default App;

axiosに投稿データとtoken付きheaderを載せてリクエストしています。

rails.log
  User Create (1.7ms)  INSERT INTO "users" ("sub", "created_at", "updated_at") VALUES (?, ?, ?)  [["sub", "auth0|60c833837c1b260072d36e1a"], ["created_at", "2021-07-29 13:11:44.946926"], ["updated_at", "2021-07-29 13:11:44.946926"]]
  ↳ app/models/user.rb:4:in `from_token_payload'
   (1.4ms)  commit transaction
  ↳ app/models/user.rb:4:in `from_token_payload'
   (0.1ms)  begin transaction
  ↳ app/controllers/api/v1/posts_controller.rb:17:in `create'
  Post Create (1.1ms)  INSERT INTO "posts" ("user_id", "title", "caption", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["user_id", 5], ["title", "タイトル2"], ["caption", "説明2"], ["created_at", "2021-07-29 13:11:44.968824"], ["updated_at", "2021-07-29 13:11:44.968824"]]
  ↳ app/controllers/api/v1/posts_controller.rb:17:in `create'

新規のユーザーなのでCreateされていますね!!

もう一度投稿取得ボタンを押して、タイトル2,説明2が追加されていたら成功です!!

ログアウトした状態で投稿作成するとエラーになることも確認してください。


App.tsxの全体です

App.tsx
import React, { useEffect, useState } from 'react';
import './App.css';
import { useAuth0 } from "@auth0/auth0-react";
import axios from 'axios';

interface Post {
  title: string,
  caption: string
}

function App() {
  const { isAuthenticated,loginWithRedirect,logout,getAccessTokenSilently } = useAuth0();
  const [token,setToken] = useState<string>('')
  const [posts,setPosts] = useState<Post[]>()

  console.log(posts)

  const fetchPosts = () => {
    axios.get('http://localhost:3000/api/v1/posts')
    .then((res) => {
      setPosts(res.data)
    })
  }

  const createPosts = () => {
    const headers = {
      headers: {
        Authorization: token,
        'Content-Type': 'application/json',
      }
    }
    const data = {
      title: 'タイトル3',
      caption: '説明3'
    }
    axios.post('http://localhost:3000/api/v1/posts',data,headers)
  }

  useEffect(() => {
    const getToken = async () => {
      try {
        const accessToken = await getAccessTokenSilently({})
        setToken(accessToken)
      } catch (e) {
        console.log(e.message)
      }
    }
    getToken()
  }, [])

  console.log(posts)
  return (
    <div className="App">
      <div style={{padding:'20px'}}>
        <h2>ログインボタン</h2>
          <button onClick={() => loginWithRedirect()}>ログイン</button>
        <h2>ログアウトボタン</h2>
          <button onClick={() => logout()}>ログアウト</button>
        <h2>ログイン状態</h2>
        {
          isAuthenticated ?
          <p>ログイン</p>
          :
          <p>ログアウト</p>
        }
        <h2>投稿作成</h2>
          <button onClick={createPosts}>投稿作成</button>
        <h2>投稿一覧</h2>
          <button onClick={fetchPosts}>投稿取得</button>
        {posts?.map((post :Post,index:number) => 
        <div key={index}>
          <p>{post.title}</p>
          <p>{post.caption}</p>
        </div>
        )}
      </div>
    </div>
  );
}

export default App;

すべてApp.tsxに記述していますが、実際にアプリにするときは
- token付きheaderをreduxで管理
- 通信処理をreactQueryで管理

以上の様にするとすっきりしたコードになります。


ここまでお読み頂きありがとうございました!!

Auth0を使用し、ユーザー認証付きでSPAを作成する方法について解説させていただきました。

挙動や詳細な機能については、ぜひ自分で調べてみて、色々試してみてください~

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7