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

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

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

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

---

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

https://qiita.com/pon0204/items/ea6b68043ca11c2b5156

https://qiita.com/pon0204/items/4de8c4b55b348a4c0781

【画像手順解説】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とは?
https://qiita.com/furuth/items/68c3caa3127cbf4f6b77

## どのような機構
https://dev.classmethod.jp/articles/auth0-architecture/

## 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: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/900110/01bc4ab2-21c5-87ff-e5b1-a8fa195ecf6c.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を作成する方法について解説させていただきました。

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