はじめに
こんにちは、つよしと申します。転職のために、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に以下を記述します
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となります。
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を追加します。
...
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をインストールします。
npm install axios --save
React側でtokenを取得
ここからはログインした状態で進めてください。
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は必要ありません。
今回は簡単に、ボタンで取得する形にします。
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が表示されれば成功です。
表示されない場合は検証ツールでconsole.logやNewWork,railsのログをみて解消してください。
create
それでは最後にtoken付きのリクエストを送って投稿を作成したいと思います。
...
// 追加
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を載せてリクエストしています。
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の全体です
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を作成する方法について解説させていただきました。
挙動や詳細な機能については、ぜひ自分で調べてみて、色々試してみてください~