背景
ログイン処理に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
- Authオブジェクトのインスタンス化
- historyオブジェクトのインスタンス化とConnectedRouterのレンダリング
公式のサンプルだと 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がページ遷移を実行する