an odd fellow

仕事のメモ

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のインスタンス化のタイミングは同時のタイミングのほうが都合が良い。よってディレクトリの構成は以下のようにした。

公式のサンプルだと 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>
)

ログイン処理はこうなる

  1. handleAuthentication() の処理の完了を待たず "ログイン中" がレンダリングされる
  2. handleAuthentication() でログイン処理が終了すると push('/') がstoreにdispatchされ、react-router-reduxのRouterReducerがページ遷移を実行する

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)