React アプリに Auth0 でシュッと認証を組み込んで Vercel に爆速デプロイする

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/7c3d5c09-3bfe-356e-7331-bfed2882301b.png" width="100%"/>

Auth0 は認証・認可サービスをクラウドで提供している SaaS ベンダーです。

「認証」という機能はどのアプリケーションにも求められる重要な要件ですが、プロダクトの本質的なビジネス価値を持たない場合が多いでしょう。Auth0 を使用することで、この「認証」機能という Undifferentiated Heavy Lifting な作業を排除できます。

本記事では、簡単な React アプリケーションを作成して Auth0 を使用した認証機能を実装します。また、作成したアプリケーションを Vercel（旧 Zeit now）にデプロイする方法を解説します。ユーザのサインアップ後の確認メールなどは SendGrid から送信されます。

以下は、本記事で紹介するアプリケーションの簡単な構成図です。また、本記事で実装されたアプリケーションは Vercel にデプロイしています。[こちら](https://auth0-todo-app.vercel.app/)からアクセスできます。ソースコードは以下の GitHub リポジトリにホストしています。

https://github.com/daisuke-awaji/auth0-todo-app

![auth0-vercel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/db8dfd31-ef0e-0f6e-7d3e-1367df522943.png)

## React アプリケーションの雛形を作る

まずは `create-react-app` を使用して簡単なアプリケーションを実装していきます。

```bash
$ create-react-app app --typescript
```

#### react-router

```
$ yarn add react-router react-router-dom
$ yarn add -D @types/react-router @types/react-router-dom
```

最初の react-router はシームレスなナビゲーションを可能にするメインのライブラリです。ふたつめは react-router-dom で、React ルーターの DOM 結合を提供します。`-D` オプションをつけることで開発時にのみ使用するライブラリをインストールできます。この例では TypeScript の型定義ライブラリをインストールしています。

#### react-bootstrap

最低限のデザインを整えるために `react-bootstrap` を使用します。

```
$ yarn add react-bootstrap react-bootstrap-icons bootstrap
$ yarn add -D @types/react-bootstrap
```

#### auth0-react

Auth0 の SDK である [`auth0-react`](https://auth0.com/docs/libraries/auth0-react) をインストールします。後述しますが、従来の [`auth0-spa-js`](https://auth0.com/docs/libraries/auth0-spa-js) などに比べると Hooks 化の対応が進んでおり、本当に最小限のコードで認証機能を実装できます。

```
$ yarn add @auth0/auth0-react
```

## auth0-react を使用して認証機能を実装する

[auth0-react の公式ドキュメント](https://auth0.github.io/auth0-react/index.html) でも解説していますが、本記事ではもう少し実際の開発のユースケースを想定してガイドします。

#### Auth0Provider

まずは `index.tsx` にて `<App/>` コンポーネントを `<Auth0Provider/>` でラップします。このようにしておくことで、 `<App/>` コンポーネント内で `useAuth0` フックを使用できるようになります。 `useAuth0` フックを使用することで、以下のような様々な認証に関するステートおよびメソッドを取得することができます。

- ステートの例: isLoading, isAuthenticated, user
- メソッドの例: loginWithRedirect, logout

```tsx:index.tsx
import React from "react";
import ReactDOM from "react-dom";
import { Auth0Provider } from "@auth0/auth0-react";
import "bootstrap/dist/css/bootstrap.min.css";
import { App } from "./App";

ReactDOM.render(
  <Auth0Provider
    domain={process.env.REACT_APP_AUTH0_DOMAIN!}
    clientId={process.env.REACT_APP_AUTH0_CLIENT_ID!}
    redirectUri={window.location.origin}
  >
    <App />
  </Auth0Provider>,
  document.querySelector("#root")
);
```

domain および、clientId には Auth0 のマネジメントコンソール画面で作成した Application の domain, clientId を入力してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/2ed57425-3eda-0bfa-bce1-faacf3369e19.png)

#### LoginButton

ログインボタンは `useAuth0` フックを使用すると簡単に実装できます。`loginWithRedirect` メソッドを実行すると Auth0 の SSO エンドポイントを使用してログインした後に、自身のアプリケーションにリダイレクトします。

```tsx:LoginButton.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "react-bootstrap";

function LoginButton() {
  const { isAuthenticated, loginWithRedirect } = useAuth0();

  return !isAuthenticated ? (
    <Button onClick={loginWithRedirect}>Log in</Button>
  ) : null;
}

export default LoginButton;
```

実際にログインボタンを押した振る舞いは以下のようになります。
![login.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/5f358792-0cc8-d140-b798-0014dc06c21b.gif)

ログイン処理の際、Auth0 のエンドポイントにアクセスし、自身のアプリケーションにリダイレクトします。事前に Application URIs の各種設定をしておきましょう。以下の例では開発用に `localhost:3000`、 本番デプロイ用として `https://auth0-todo-app.vercel.app/` を設定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/e5c854ef-5664-0aa9-749d-0ae668fe8c8b.png)

#### LogoutButton

ログアウト処理には `logout` メソッドが用意されています。
ログアウト後に表示するパスを `returnTo: xxxx` で指定します。

```tsx:LogoutButton.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "react-bootstrap";

function LogoutButton(props: any) {
  const { isAuthenticated, logout } = useAuth0();

  return isAuthenticated ? (
    <Button
      variant="outline-primary"
      onClick={() => {
        logout({ returnTo: window.location.origin });
      }}
      {...props}
    >
      Log out
    </Button>
  ) : null;
}

export default LogoutButton;
```

#### UserProfile

ログインユーザの情報は `user` を使用して取得します。認証プロバイダー（Google, Facebook, etc...）によって取得できる情報は異なります。以下では、ログインユーザのアバター画像を `user.picture` として取得しています。

```tsx:UserProfileAvatar.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Image } from "react-bootstrap";
export const UserProfileAvatar = (props: any) => {
  const { user } = useAuth0();
  return (
    <Image
      style={{ width: "30px" }}
      src={user.picture}
      roundedCircle
      {...props}
    />
  );
};
```

この記事のサンプルアプリケーションでは、この情報を組み合わせて、ユーザアイコンをクリックすると各種メニューが表示されるようにしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/aca041aa-13fe-2d4e-22b4-2693f4da9690.png)

## 認証済みのユーザだけ見れるページを制御する

[react-router-dom を使用した実装ではおなじみの PrivateRoute](https://reactrouter.com/web/example/auth-workflow) を組み込んでいきます。
`auth0-react` では、ログインしていないユーザが表示しようとすると、ログインページにリダイレクトする `withAuthenticationRequired()` が用意されています。

以下のように `ProtectedRoute` のようなルート制御コンポーネントを作成しておくと便利です。

```tsx:ProtectedRoute.tsx
import { withAuthenticationRequired } from "@auth0/auth0-react";
import React from "react";
import { Route } from "react-router-dom";

export function ProtectedRoute({ component, ...args }: any) {
  return (
    <Route component={withAuthenticationRequired(component, {})} {...args} />
  );
}
```

ルート制御ではこの `ProtectedRoute` コンポーネントを使用して、ログインしていないユーザがアクセスできないページを実装できます。以下の例では、`/profile` はログインしていない状態では表示できません。

```tsx:App.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Route, BrowserRouter as Router, Switch } from "react-router-dom";
import Home from "./pages/Home";
import { ProtectedRoute } from "./components/auth/ProtectedRoute";
import Profile from "./pages/Profile";
import { Layout } from "./components/layout/Layout";
import Support from "./pages/Support";
export function App() {
  const { isLoading } = useAuth0();
  if (isLoading) {
    return <p></p>;
  }

  return (
    <Router>
      <Layout>
        <Switch>
          <Route path="/" exact component={Home} />
          <ProtectedRoute path="/profile" component={Profile} />
          <Route path="/support" exact component={Support} />
        </Switch>
      </Layout>
    </Router>
  );
}
```

`/profile` にアクセスしようとするとログインページにリダイレクトされています。

![protectedroute.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/81104ece-ea0e-7ed9-3868-9b05d5c401f6.gif)

## Vercel にデプロイする

[Vercel のダッシュボード](https://vercel.com/dashboard)から `Import Project` をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/aa57ca1f-114d-7e9e-b4bf-7e4fe60a97a7.png)

今回は GitHub にホスティングしているソースコードを元にデプロイするので、Git リポジトリを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/d3906bea-c067-4d2c-b070-a13aa8046fb2.png)

GitHub のリポジトリ URL を指定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/3165eab1-696d-e4fd-c9bc-ba7d23b78e13.png)

最後にビルドコマンドと環境変数を設定します。
Auth0 の domain と clientId は環境変数に指定して React アプリに渡しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/aa930d54-246a-2cd0-d341-4adf7ca1d251.png)

あとは Deploy ボタンをクリックして完了です。これだけでデプロイができるはずです。

## サインアップ後の確認メールを SendGrid から送信する

Auth0 には Welcome メールやパスワードリセット、アカウントの検証などのためにメールを送信する仕組みが用意されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/37af0b0f-8982-1097-f42c-376110a6301e.png)

送信するためには事前にメールプロバイダの設定が必要です。今回は無料で提供されている SendGrid を使用してみましょう。

Email Provider の画面から　 SendGrid 　を選択し、送信元メールアドレス（From）と SendGrid の API キーを入力します。API キーは SendGrid の [settings](https://app.sendgrid.com/settings/api_keys) から発行できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/c65297a7-73aa-e2ad-d00d-5fe87052820f.png)

ログインに成功すると以下のようなメールが送信されるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163591/46ce564f-92b5-5713-e73c-4e6f00c9bb0d.png)

## まとめ

Auth0 + Vercel + SendGrid という構成をチュートリアル的に実装し、その振る舞いを確認してみました。`auth0-react` ライブラリは非常に使い勝手がよく、簡単に認証機能を組み込むことができます。一連の流れを実装しても 1 ~ 2 時間で実装できました。以前までは苦労して認証の仕組みを整えていましたが随分と楽になりそうです。