React TypeScript Auth0 でログイン/ログアウト とユーザー情報の表示を実装する
Tags: typescript, auth0, react
Published: 2022-05-09 19:06:40


## why

https://www.becomebetterprogrammer.com/jwt-authentication-middleware-nodejs-typescript/

認証機能のあるブログサービスを作ろうと思い、調べてみたら
Express で作るのがかなり難しそうに見えた

そんな時、Auth0 の外部サービス使えば？と聞き、調べると

https://dev.classmethod.jp/articles/add-authentication-with-auth0-to-react-typescript-app/

Auth0 側の管理画面でボタン押して作って
React コンポーネントに組み込んで
提供されるログインボタン押せば Auth0 側のモーダルに行くので、簡単そうに見えた。

## Auth0 のアプリ作成

アカウントを GitHub 連携で手早く作成し

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nugvznx3d8cqwget7omw.png)

authTest という名前で SPA で Auth0 のアプリ作成

Settings をみると

今作った name の他に

* Domain
* Client ID
* Client Secret (ID) 

が見れる。これをログイン/ログアウトボタンを React で使うときに必要とするらしい。

また Settings の下部の

Allowed
* Callback URLs
* Logout URLs
* Web Origins

という Allowed 系の URL を全て

http://localhost:3000/

にする。これがないとそこでログイン/ログアウトボタンが使えないと推測する。

これで Save Changes ボタンを押せば React で使えるようになるらしい。

---

## CRA と auth0-react の導入

```ts
npx create-react-app auth0 --template typescript
```

CRA --temp ts で auth0 という名前で React アプリを作成

```
npm install @auth0/auth0-react
```

auth0-react のライブラリをインストールする。

--- 

## .env ファイルに REACT_APP_ の環境変数を作る

ログイン機能の開発なので、開発用の認証、環境変数が必要。

Auth0 で process.env で .env ファイルから読み取る必要がある。

https://qiita.com/yuta-ushijima/items/a653e9ca4847276f19cd#%E6%94%B9%E4%BF%AE%E5%BE%8C%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89

proccess.env については CRA で React App　を作った時点で
dotenv ライブラリが組み込まれるので

https://stackoverflow.com/questions/53237293/react-evironment-variables-env-return-undefined

`REACT_APP_` から変数名を始めれば .env ファイルから読み取れた。
普通に TEST=testString にしても読み取れなかった。

.env ファイルにこう書いて

```php
TEST=TEST_STRING
REACT_APP_WEBSITE_NAME=hello
```

App.tsx のファイルで出力すると

```ts
function App() {
console.log(process.env.TEST);
console.log(process.env.REACT_APP_WEBSITE_NAME);
}
```

> undefined
> hello

ちゃんと `REACT_APP_` がついている変数のみ読み取れた。

---

## index で AuthProvider で App を囲い、.env の ドメインとクライアントID を紐づける。

Redux でも Provider で App を囲うのは共通だった。

https://qiita.com/G-awa/items/fa34fd7065ef1e14d3d9#auth0provider

```ts
import { Auth0Provider } from "@auth0/auth0-react";
```

これを使って

```ts
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```

単に StrictNode で囲われている App を

```ts
  <Auth0Provider
    domain={process.env.REACT_APP_AUTH0_DOMAIN!}
    clientId={process.env.REACT_APP_AUTH0_CLIENT_ID!}
    redirectUri={window.location.origin}
  >
    <App />
  </Auth0Provider>,
```

Auth0Provider で囲い

先ほど取得した Auth0 のドメインID と クライアントID をセットして
リダイレクトにはルート？をセットする。

最後に! をつけているので、モノがなくてもエラーにはならないと推測
だが、外すと String に String|Undef は入らないとエラーになる
なので name?:string のような文法なのかもしれない

それはともかく Auth0 の ID の変数には ! を末尾につける

.env ファイルには

```php
REACT_APP_AUTH0_DOMAIN=dev-123abc.us.auth0.com
REACT_APP_AUTH0_CLIENT_ID=123aBcD
```

このように記載する。

Redux ではこの段階で console にストアのメッセージが出ていたが
Auth0 では Provider を紐づけただけでは何のメッセージもない。

---

## ログインボタンを isAuthenticated, loginWithRedirect で作って　App から呼び出す

auth0-react のライブラリから isAuthenticated, loginWithRedirect の モジュールを使ってログインボタンのコンポーネントを作る。

https://qiita.com/G-awa/items/fa34fd7065ef1e14d3d9#loginbutton

```ts
import { useAuth0 } from "@auth0/auth0-react";

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

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

export default LoginButton;
```

useAuth0 を import
Auth0 で認証が済んでいるかの isAuthenticated
Auth0 のログインモーダルを開く関数の loginWithRedirect

これらを useAuth から作成

isAuthenticated でない、つまりログインされていないときには
クリックしたときに loginWithRedirect を発火させるボタンを描画

isAuthenticated である、つまりログインされているときは
null を描画

するボタンを作り

```ts
import LoginButton from './LoginButton';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <LoginButton/>
      </header>
    </div>
  );
}
```

App で呼び出す。

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ixqwwyq15dse0gqgdtqi.png)
 
するとログインボタンが表示された。

---

## ログインボタンをクリックしてみる

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdyim69s8njucsj3hu5a.png)

すると、Auth0 の auth0.com ドメインのログイン画面が開く。

今回は最初なのでユーザー登録をしてログインすると

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hehezmo55ebhl45jfwqy.png)
 

localhost 3000 の画面に戻り、ログインボタンが消える。

---

## ログイン中はログアウトボタンが表示されるようにする

ログアウトボタンは少し複雑で、returnTo でログアウト後にユーザーが送られる場所を指定するオブジェクトを渡す必要がある。

このログアウトボタンを、ログイン中のみ表示するようにしてみる。

```ts
function LoginButton() {
  const { isAuthenticated, loginWithRedirect, logout } = useAuth0();
  if ( !isAuthenticated ) {
    return (
      <button onClick={loginWithRedirect}>LOG IN</button>
    )
  } else {
    return (
      <button
        onClick={() => {
          logout({ returnTo: window.location.origin });
        }}
      >LOG OUT</button>
    )
  }
}
```

useAuth0 から logut モジュールも持ってきて
ログイン中でなければ早期リターンで ログインボタンを返すようにする。
そしてログイン中であれば、ログアウトボタンを返すようにする

ログアウトは引数にオブジェクトが必要なので

```ts
onclick={() => {logout({ key:value });} }
```

の形にする必要があった。

ここに returnTo: window.location.origin を入れた。

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f7yxr32hh2wi2xxzwj5j.png)

これで、ログイン時にはログアウトボタンが表示されるようになった。便利。

---

## ユーザー情報を表示する

Auth 0 の ユーザーしか見れないページの

Show User Profile Information　の項目にある。

```ts
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";

const Profile = () => {
  const { user, isAuthenticated, isLoading } = useAuth0();

  if (isLoading) {
    return <div>Loading ...</div>;
  }

  return (
    isAuthenticated && (
      <div>
        <img src={user?.picture} alt={user?.name} />
        <h2>{user.name}</h2>
        <p>{user.email}</p>
      </div>
    )
  );
};

export default Profile;
``` 

サンプルの通り書くとログインしているけどユーザー情報が取れない処理がないので

```ts
const user: User | undefined
Object is possibly 'undefined'.ts(2532)
```

user の オブジェクトが undef かもしれないエラーが出てしまう
なので、user がある時だけ描画される条件にしたら 型エラーが消えた。
TS でのエラーは type だけでない、例外処理がされてない場合も出るようだ。

ないと動かなくなるものでは例外処理大事！

また、ローディング中でなく、ログイン済みでもない場合の処理もないと
undefined の場合があるからとエラーが出てしまう。

なのでその時は　null　を返す処理も入れる。

```ts
  if (isLoading) {
    return <div>Loading ...</div>;
  } else if (isAuthenticated && user) {
    return (
      <div>
        <img src={user.picture} alt={user.name} />
        <h2>{user.name}</h2>
        <p>{user.email}</p>
      </div>
    );
  }
  else {
    return null
  }
```

そしてこの三段階になった。

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8949i84nm9j8hf04ybcc.png)

これでログイン中はユーザーの画像と名前とメアドが表示されて

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yj715vhptkjarw02fhek.png)

ログアウトするとログインボタンのみが表示される仕組みが作れた。