Auth0 Lockをcreate-react-app / Gatsby / Next.jsで動かす

ReactでSPA作るときに使うメジャー系それぞれでAuth0 Lockを動かしました。

ドはまりした点があったので、忘備録として記録します。

動かそうとしたもの

Auth0 LockのNodeライブラリがCallbackなので、簡単なラッパークラスを作りました。

import * as Auth0Lock from 'auth0-lock';
import {Auth0UserProfile} from 'auth0-js'

export class Auth0LockClient {
  private lock: Auth0LockStatic
  constructor(cliendId: string, domain: string, lock: Auth0LockStatic = new Auth0Lock.default(cliendId, domain)) {
    this.lock = lock
  }
  public checkSession(): Promise<AuthResult | undefined> {
    return new Promise((resolve, reject) => {
      this.lock.checkSession({}, (error, authResult) => {
        if (error) {
          if (error.error === "login_required") return resolve()
          return reject(error)
        }
        return resolve(authResult)
      })
    })
  }
  public show(): void {
    this.lock.show()
  }
  public getUserInfo(accessToken: string): Promise<Auth0UserProfile> {
    return new Promise((resolve, reject) => {
      this.lock.getUserInfo(accessToken, function (error, profile) {
        if (error) return reject(error)
        return resolve(profile)
      })
    })
  }
  public logout(): void {
    this.lock.logout({
      returnTo: 'https://localhost:3000'
    })
  }
}
export default Auth0LockClient

これを以下のようなコンポーネントで実行させます。

import React, {useCallback, useEffect, useState} from 'react'
import Auth0LockClient from './client'

export default () => {
  const [user, updateUser] = useState<null | Auth0UserProfile>(null)
  const Lock = new Auth0LockClient('<AUTH0_CLIENT_ID>', '<AUTH0_DOMAIN>')

  useEffect(() => {
    // ロード時にログイン状態を確認
    // ログインしていないければログイン画面を表示する
    (async () => {
      try {
        const result = await Lock.checkSession()
        if (!result) {
          Lock.show()
          return
        }
        const user = await Lock.getUserInfo(result.accessToken)
        updateUser(user)
      } catch (e) {
        console.log(e)
      }
    })()
    return () => updateUser(null)
  }, [])
  const login = useCallback(() => {
    lock.show()
  }, [])
  const logout = useCallback(() => {
    lock.logout({
      returnTo: 'https://localhost:3000'
    })
  }, [])
  return (
    <>
      <h2>Home</h2>
      {JSON.stringify(user)}
      <button onClick={logout}>Logout</button>
      <button onClick={login}>Login</button>
    </>
  )
}

よくよく見るといろいろ横着してますので、コピペしてprodに出すのは全くおすすめしません。

無難of無難: create-react-app

まずはcreate-react-app。これは特に工夫することもなく動作しました。

Next.jsとGatsbyはそのままだと動かないので、ただ試したい用途であればcreate-react-appでやるのが無難かなと思います。

window変数でハマるNext.js

Next.jsはそのままだと以下のエラーを吐きます。

ReferenceError: window is not defined
    at extractAuthOptions (/Users/develop/next-auth0/node_modules/auth0-lock/lib/core/index.js:427:70)
    at Object.setup (/Users/develop/next-auth0/node_modules/auth0-lock/lib/core/index.js:119:11)
    at setupLock (/Users/develop/next-auth0/node_modules/auth0-lock/lib/core/actions.js:48:13)
    at Auth0Lock.Base (/Users/develop/next-auth0/node_modules/auth0-lock/lib/core.js:81:36)
    at new Auth0Lock (/Users/develop/next-auth0/node_modules/auth0-lock/lib/lock.js:27:56)
    at new Auth0LockClient (/Users/develop/next-auth0/.next/server/static/development/pages/index.js:2145:40)
    at Module../pages/index.tsx (/Users/develop/next-auth0/.next/server/static/development/pages/index.js:2185:14)

windowがundefinedのケースがある様子ですので、ignoreしてやる必要があります。

import React, {useCallback, useEffect, useState} from 'react'
import Auth0LockClient from './client'


export default () => {
  const [user, updateUser] = useState<null | Auth0UserProfile>(null)

  // ==== 追加部分ここから ===
  // windowがundefinedのときは動かさない
  if (typeof window === 'undefined') return null
  // ==== 追加部分ここまで ===

  const Lock = new Auth0LockClient('<AUTH0_CLIENT_ID>', '<AUTH0_DOMAIN>')

  useEffect(() => {
    // ロード時にログイン状態を確認
    // ログインしていないければログイン画面を表示する
    (async () => {
      try {
        const result = await Lock.checkSession()
        if (!result) {
          Lock.show()
          return
        }
        const user = await Lock.getUserInfo(result.accessToken)
        updateUser(user)
      } catch (e) {
        console.log(e)
      }
    })()
    return () => updateUser(null)
  }, [])
  const login = useCallback(() => {
    lock.show()
  }, [])
  const logout = useCallback(() => {
    lock.logout({
      returnTo: 'https://localhost:3000'
    })
  }, [])
  return (
    <>
      <h2>Home</h2>
      {JSON.stringify(user)}
      <button onClick={logout}>Logout</button>
      <button onClick={login}>Login</button>
    </>
  )
}

ちなみに公式で「@auth0/nextjs-auth0」というライブラリもあります。

「This library is currently in an experimental」なので本番に使うには少しリスクが高いかもしれません。

また、SSR前提な実装の様子ですので、Netlifyなどにホストする場合には使えない可能性があります。

react-transition-groupでハマるGatsby

Gatsbyは別のエラーが出ます。

TypeError: Cannot convert undefined or null to object at values (<anonymous>) at TransitionGroup.render (TransitionGroup.js:127), as well as a react-dom.development.js:422 Uncaught Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development.

対策としては、package.jsonに以下を追加する方法が提案されていました。

{
  ...,
  "resolutions": {
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-transition-group": "^4.1.1"
  }
}

参考: https://github.com/braden-m/gatsby-auth0-lock/pull/1

おわりに

Next.jsやGatsbyの場合、Server Side Renderingや内部で使用しているライブラリとの関係でトラブルが起きることもある様子でした。が、そこまで大きくない手数で対応できる様子でしたのでAuth0との連携はどれでも問題なくやれそうかなと思います。

余談ですが、Next.jsはAuth0のサイトで使われているみたいですね。

Comment