React-Adminで認証を行う｜React-Admin | authProvider,Firebase

## 前提

こちらのページでReact-Adminの全体像や各機能の解説をしていますので、ぜひそちらもご覧ください（https://qiita.com/shino_gono/items/22397f340bfb2038587d）
- この記事ではReact-Adminの導入は済んでいる前提で話を進めます。
- 認証にはfirebaseを用います。
- ドキュメントの認証周りが書かれているページ
- https://marmelab.com/react-admin/Authentication.html

## React-Adminでの認証について

- React-Adminでの認証は基本的にauthProviderというプロバイダーを作成して実装を行います。
- こちらはそのほかのデータ操作で使うdataProviderとは切り分けられて扱われるので、その点はご注意ください。
- デフォルトではReact-Adminは認証を必要としていません。
- つまり、特定の設定を加えてやる必要があります。
- まずはApp.jsでAdminコンポーネントに追加してあげましょう。

```jsx
// in src/App.ts
import authProvider from './authProvider';

export default function App() {
  return (
    <Admin authProvider={authProvider}>
        ...
    </Admin>
  );
}
```

- これで認証機能の実装が可能になります。

### サポートされているバックエンド

React-Aminでは下記のバックエンドが公式にサポートされているので、こちらのバックエンドを使用している場合は非常に簡単に認証が実装できます。

- [**Okta の Auth0**](https://auth0.com/) [marmelab/ra-auth-auth0](https://github.com/marmelab/ra-auth-auth0/blob/main/packages/ra-auth-auth0/Readme.md)
- [**AWS Amplify**](https://docs.amplify.aws/) [MrHertal/react-admin-amplify](https://github.com/MrHertal/react-admin-amplify)
- [**AWS Cognito**](https://docs.aws.amazon.com/cognito/latest/developerguide/setting-up-the-javascript-sdk.html) [marmelab/ra-auth-cognito](https://github.com/marmelab/ra-auth-cognito/blob/main/packages/ra-auth-cognito/Readme.md)
- [**Azure Active Directory (MSAL を使用)**](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser) [marmelab/ra-auth-msal](https://github.com/marmelab/ra-auth-msal/blob/main/packages/ra-auth-msal/Readme.md)[チュートリアル](https://marmelab.com/blog/2023/09/13/active-directory-integration-tutorial.html)
- [**Casdoor**](https://casdoor.com/) [NMB-Lab/reactadmin-casdoor-authprovider](https://github.com/NMB-Lab/reactadmin-casdoor-authprovider)
- [**ディレクタス**](https://directus.io/) [marmelab/ra-directus](https://github.com/marmelab/ra-directus/blob/main/packages/ra-directus/Readme.md)
- [**Firebase Auth (Google、Facebook、GitHub など)**](https://firebase.google.com/docs/auth/web/firebaseui) [benwinding/react-admin-firebase](https://github.com/benwinding/react-admin-firebase#auth-provider)
    
    !https://marmelab.com/react-admin/img/backend-logos/firebase.png
    
- [**Google Identity と Google Workspace**](https://developers.google.com/identity/gsi/web/guides/overview) [marmelab/ra-auth-google](https://github.com/marmelab/ra-auth-google/blob/main/packages/ra-auth-google/Readme.md)
- [**キークローク**](https://www.keycloak.org/) [marmelab/ra-keycloak](https://github.com/marmelab/ra-keycloak/blob/main/packages/ra-keycloak/Readme.md)
- [**Supabase**](https://supabase.io/) [marmelab/ra-supabase](https://github.com/marmelab/ra-supabase/blob/main/packages/ra-supabase/README.md)
- [**SurrealDB**](https://surrealdb.com/) [djedi23/ra-surrealdb](https://github.com/djedi23/ra-surrealdb)

## authProviderを実装する

- 個人的にauthProviderは下記のようにディレクトリにすることを推奨します。

```jsx
 📂authProvider
 ┗ 📜authProvider.ts
 ┗ 📜index.ts
```

- というのも、認証にfirebaseの設定を組み込む際などに、その部分が切り分けられていると見やすいなと思うからです。
- React-Adminには
- なので、`index.ts`はこんな感じになります。

```jsx
import { FirebaseAuthProvider } from 'react-admin-firebase';
import createAuthProvider from './authProvider';
import { firebaseConfig } from '../firebaseConfig';

const firebaseAuthProvider = FirebaseAuthProvider(firebaseConfig, {});

const authProvider = createAuthProvider();

export default authProvider;
```

## authProvider.ts

- `authProvider.ts`では`createAuthProvider`を定義する必要があります。
- `createAuthProvider` では下記のメソッドが必須で必要になり、定義しないと型エラーなどがうるいさいので、使わないものも定義します。

```jsx
import { AuthProvider } from "react-admin";

const createAuthProvider = (): AuthProvider => ({
  login: () => {},
  logout: () => {},
  checkError: () => {},
  checkAuth: () => {},
  getPermissions: () => {},
  getIdentity: () => {},
  setIdentity: () => {},
});

export default createAuthProvider;
```

- これらのメソッドはPromiseを返すので使わないものはrejectでも返しておけばいいと思います。

```jsx
// 例：
checkError: () => Promise.resolve(),
```

### loginメソッド

- ログインの処理を記述します。

```jsx
 login: async ({ username, password }) => {
    try {
      await signInWithEmailAndPassword(auth, username, password);
      const user = auth.currentUser;
      if (user) {
        const result = await fetchWithAuth("/user.getBySelfToken");
        localStorage.setItem("user", JSON.stringify(result));
        return true;
      }
    } catch {}
    return Promise.reject();
  },
```

- ヘッダーにトークンを載せないといけない場合を想定してfetchWithAuth関数とか作っておくといいですね

```jsx
async function fetchWithAuth(endpoint: string): Promise<User> {
  try {
    const user = auth.currentUser;
    if (!user) {
      return await Promise.reject();
    }

    const idToken = await user.getIdToken();

    const response = await fetch(`適切なURL`, {
      method: "GET",
      headers: {
       // 適当な設定
      },
    });

    if (!response.ok) {
      return await Promise.reject();
    }

    const data = await response.json();

    return data;
  } catch (error) {
    return Promise.reject();
  }
}

```

### logoutメソッド

- ログアウトの処理を記述します。
- ログイン状態の確認はchackAuthメソッドで管理しており、今回はローカルストレージに情報があるか否かを見ているので、このようにローカルストレージからユーザー情報を削除してあげればよいです

```jsx
 logout: () => {
    localStorage.removeItem("user");
    return Promise.resolve();
  },
```

### checkAuthメソッド

- 現在のユーザーが存在するかを確認します

```jsx
checkAuth: () =>
    localStorage.getItem("user") ? Promise.resolve() : Promise.reject(),
```

### getIdentityメソッド

- ユーザー情報を取得する処理
- これを設定しておかないと、ユーザー情報を表示させるページなどでユーザー情報が取得しにくくなるので、必ず設定します
- 今回は単にローカルに保存されたものをそのまま使います

```jsx
getIdentity: () => {
    const identity = localStorage.getItem("user");

    return Promise.resolve(identity);
  },
```

### setIdentityメソッド

- ユーザー情報を更新するメソッド

```jsx
setIdentity: async () => {
    try {
      const storedUser = localStorage.getItem("user");
      const user = storedUser ? JSON.parse(storedUser) : {};

      localStorage.setItem("user", JSON.stringify(user));
    } catch (error) {
      return Promise.reject();
    }

    return Promise.resolve();
  },
```

## エラーハンドリングなど

- 上記のコードでrejectにエラーメッセージなどを渡していません。
- React-Adminでは特に設定しなくてもAPIからのエラーを解釈してエラーメッセージが表示されるようになっています。
- そのため、このようにrejectに引数を渡さなくても問題がありません。
- 逆に言うと、この仕様のためエラーメッセージを上書きするのがまぁまぁ面倒なので、これは特にいじらないのが無難なきがします。