Reduxを使ってるときのAuth0の使い方
背景
ログイン処理にAuth0を使っている。Auth0はサンプルが豊富でReactを使ったサンプルも在るが、そのサンプルではReduxは使わずPure React構成のため、Reduxを使っている場合の使い方がイマイチ分からなかったので、考えて実装した結果をまとめておく。
どこで悩むか
基本的には Auth0 React SDK Quickstarts: Login を見てまず実装した上でRedux化していくのが良い。その上で悩みポイントは2つ。
- Authオブジェクトで直接historyAPIを叩いているが、react-router-reduxを使っている場合それはできない
- Authオブジェクトのインスタンス化のタイミング
AuthオブジェクトからhistoryAPIを叩く方法
react-router-reduxを使う場合historyAPIもActionを発行しReducerに処理させなければならない。そうなると store.dispatch
をAuthオブジェクトは使わざるを得ない。
つまり公式のサンプルでは hitsoty.replace('/home')
とやっているところを、 dispatch(push('/home'))
とすれば良い。コードは以下のようになる。
Auth.js
import auth0 from 'auth0-js' import { push } from 'react-router-redux' export default class Auth { auth0 = new auth0.WebAuth({ domain: AUTH0_DOMAIN clientID: AUTH0_CLIENT_ID, redirectUri: AUTH0_REDIRECT_URI responseType: 'token id_token', scope: 'openid' }) constructor (dispatch) { this.login = this.login.bind(this) this.handleAuthentication = this.handleAuthentication.bind(this) this.isAuthenticated = this.isAuthenticated.bind(this) this.logout = this.logout.bind(this) this.dispatch = dispatch // constructorの引数でdispatchをもらってthisに束縛する } login () { this.auth0.authorize() } handleAuthentication () { this.auth0.parseHash((err, authResult) => { if (authResult && authResult.accessToken && authResult.idToken) { this.setSession(authResult) this.dispatch(push('/home')) // ここが公式のサンプルでは history.replace('/home') になってる } else if (err) { console.log(err) this.dispatch(push('/hoe')) } }) } setSession (authResult) { let expiresAt = JSON.stringify( authResult.expiresIn * 1000 + new Date().getTime() ) localStorage.setItem('access_token', authResult.accessToken) localStorage.setItem('id_token', authResult.idToken) localStorage.setItem('expires_at', expiresAt) this.dispatch(push('/home')) } isAuthenticated () { let expiresAt = JSON.parse(localStorage.getItem('expires_at')) return new Date().getTime() < expiresAt } logout () { // Clear Access Token and ID Token from local storage localStorage.removeItem('access_token') localStorage.removeItem('id_token') localStorage.removeItem('expires_at') this.dispatch(push('/home')) } }
ディレクトリ構成
Authにstore.dispatchを渡さなければならないため、Authオブジェクトのインスタンス化とStoreのインスタンス化のタイミングは同時のタイミングのほうが都合が良い。よってディレクトリの構成は以下のようにした。
- App.js
- ページ全体のテンプレート 且つ ルーティング設定
- index.js
公式のサンプルだと routes.js
というのがあって、ルーティングの設定を切り出しているが、 App.js
で全てやってしまった。コードは以下のようになる。
index.js
import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' import { ConnectedRouter } from 'react-router-redux' import createBrowserHistory from 'history/createBrowserHistory' import App from './App' import Auth from './auth' import { createStore } from './store' const history = createBrowserHistory() const store = createStore(history) const auth = new Auth(store.dispatch) // authのdispatchを渡す ReactDOM.render( <Provider store={store}> <ConnectedRouter history={history}> <App auth={auth} /> </ConnectedRouter> </Provider>, document.getElementById('root') )
App.js
handleAuthenticationの引数にauthを追加しているのに注意。公式のサンプルだとauthがスコープにいたが、この構成だと明示的に渡してやる必要がある。
import React from 'react' import { Route, Switch, Redirect } from 'react-router-dom' const handleAuthentication = (nextState, replace, auth) => { if (/access_token|id_token|error/.test(nextState.location.hash)) { auth.handleAuthentication() } } export default ({ auth }) => ( <Switch> <Route exact path='/login' render={() => <LoginPage auth={auth} />} /> <Route exact path='/callback' render={props => { handleAuthentication(props, null, auth) return <p>ログイン中</p> }} /> </Switch> )
ログイン処理はこうなる
handleAuthentication()
の処理の完了を待たず "ログイン中" がレンダリングされるhandleAuthentication()
でログイン処理が終了すると push('/') がstoreにdispatchされ、react-router-reduxのRouterReducerがページ遷移を実行する
改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで
- 作者: 山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2016/09/30
- メディア: 大型本
- この商品を含むブログを見る
React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)
- 作者: 穴井宏幸,石井直矢,柴田和祈,三宮肇
- 出版社/メーカー: 翔泳社
- 発売日: 2018/02/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
「Auth0」で作る!認証付きシングルページアプリケーション (技術書典シリーズ(NextPublishing))
- 作者: 土屋貴裕
- 出版社/メーカー: インプレスR&D
- 発売日: 2018/08/31
- メディア: Kindle版
- この商品を含むブログを見る