> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Ionic React と Capacitor アプリケーションにログインを追加する

> このガイドでは、Auth0 React SDK を使用して、Auth0 を Ionic React と Capacitor のアプリケーションに統合する方法を説明します。

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

export const CreateInteractiveApp = ({placeholderText = "Auth0", appType = "regular_web", allowedCallbackUrls = ["localhost:3000"], allowedLogoutUrls = ["localhost:3000"], allowedOriginUrls = ["localhost:3000"]}) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [storeReady, setStoreReady] = useState(false);
  const [displayForm, setDisplayForm] = useState(true);
  useEffect(() => {
    const init = () => setStoreReady(true);
    if (window.rootStore) {
      window.rootStore.clientStore.setSelectedClient(null);
      window.rootStore.clientStore.setSelectedClientSecret(undefined);
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
    };
  }, []);
  useEffect(() => {
    if (!storeReady) return;
    const disposer = autorun(() => {
      const rootStore = window.rootStore;
      setIsAuthenticated(rootStore.sessionStore.isAuthenticated);
    });
    return () => {
      disposer();
    };
  }, [storeReady]);
  if (!storeReady || typeof window === "undefined" || !displayForm) {
    return <></>;
  }
  const login = () => {
    const baseUrl = window.rootStore.config.apiBaseUrl;
    const returnTo = encodeURIComponent(window.location.href);
    window.location.href = `${baseUrl}/auth/user/login?returnTo=${returnTo}`;
  };
  const Card = ({className = "", children}) => {
    return <div className={`
          flex border rounded-2xl
          border-gray-950/10 dark:border-white/10
          py-3.5 px-4 gap-2
          text-sm text-gray-900 dark:text-gray-200
          ${className}
        `}>
        {children}
      </div>;
  };
  const Button = ({children, ...props}) => {
    return <button className="bg-[--button-primary] text-[--foreground-inverse] px-[1.125rem] py-1.5 rounded-lg font-medium" {...props}>
        {children}
      </button>;
  };
  const CreateApplicationForm = () => {
    const [name, setName] = useState("");
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState("");
    const handleSubmit = async () => {
      if (!name.trim()) {
        setError("アプリケーション名は必須です");
        return;
      }
      setIsLoading(true);
      setError(null);
      try {
        await window.rootStore.clientStore.createClient({
          name: name.trim(),
          app_type: appType,
          callbacks: allowedCallbackUrls,
          allowed_logout_urls: allowedLogoutUrls,
          web_origins: allowedOriginUrls,
          client_metadata: {
            created_by: "quickstart-docs-app-creation-component"
          }
        });
        setDisplayForm(false);
      } catch (err) {
        console.error("クライアントの作成中にエラーが発生しました:", err);
        const errorMessage = err instanceof Error ? err.message : "アプリケーションの作成に失敗しました";
        setError(errorMessage);
      } finally {
        setIsLoading(false);
      }
    };
    return <Card className="flex-col items-start p-4 gap-3.75">
        <span className="font-medium text-gray-900 dark:text-gray-200">Auth0 アプリの作成</span>
        <div className="w-full flex gap-2">
          <input id="app-name" name={name} className="
              w-full max-w-[448px] h-11 py-2 px-4 
              border rounded-lg border-gray-950/10 dark:border-white/10 
              text-gray-900 dark:text-gray-200
              focus:outline-none dark:focus:outline-none
            " placeholder={`My ${placeholderText} App`} value={name} onChange={e => setName(e.target.value)} />
          <Button onClick={handleSubmit}>{isLoading ? "作成中..." : "作成"}</Button>
        </div>
        {error && <p className="text-red-500">{error}</p>}
      </Card>;
  };
  const SignInForm = () => {
    return <Card className="items-center">
        <Button onClick={login}>ログイン</Button> <span>してアプリを作成してください</span>
      </Card>;
  };
  return isAuthenticated ? <CreateApplicationForm /> : <SignInForm />;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****マスク済み*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

<HowToSchema />

<Accordion title="AI を使って Auth0 を統合する" icon="microchip-ai" iconType="solid" defaultOpen>
  Claude Code、Cursor、GitHub Copilot などの AI コーディングアシスタントを使用している場合は、[agent skills](https://agentskills.io/home) を使って数分で Auth0 認証を自動的に追加できます。

  まず、Auth0 agent skills をインストールします。

  ```bash theme={null}
  npx skills add auth0/agent-skills --skill auth0-quickstart --skill auth0-ionic-react
  ```

  次に、AI アシスタントに次のように依頼します。

  ```text theme={null}
  Add Auth0 authentication to my Ionic React app
  ```

  AI アシスタントは、Auth0 アプリケーションの作成、認証情報の取得、Auth0 React SDK と Capacitor プラグインのインストール、ディープリンクの設定、ネイティブブラウザー統合によるログイン/ログアウト フローの実装を自動的に行います。詳しくは、[Auth0 agent skills](/ja/docs/quickstart/agent-skills) を参照してください。
</Accordion>

<div id="get-started">
  ## はじめに
</div>

このクイックスタートでは、Capacitor を使用した Ionic React アプリケーションに Auth0 の認証を追加する方法を説明します。Auth0 React SDK と Capacitor のネイティブブラウザー統合を使用して、ログイン、ログアウト、ユーザープロフィール機能を備えたモバイル対応アプリを構築します。

export const localEnvSnippet = `VITE_AUTH0_DOMAIN={yourDomain}
VITE_AUTH0_CLIENT_ID={yourClientId}`;

<Steps>
  <Step title="新しいプロジェクトを作成" stepNumber={1}>
    Capacitor を使用して新しい Ionic React アプリを作成する

    ```shellscript theme={null}
    npx ionic start auth0-ionic-react tabs --type=react --capacitor
    ```

    プロジェクトを開く

    ```shellscript theme={null}
    cd auth0-ionic-react
    ```

    <Warning>
      `@ionic/cli` パッケージを使用していることを確認してください (非推奨の `ionic` パッケージではありません) 。プロジェクトの作成時に `--npm-client` に関するエラーが表示される場合は、CLI を更新してください。

      ```shellscript theme={null}
      npm uninstall -g ionic
      npm i -g @ionic/cli
      ```
    </Warning>

    <Info>
      すでに Ionic React アプリがある場合は、Capacitor が有効になっていることを確認してください。`ionic integrations enable capacitor` を実行し、続けて `npx cap init` を実行すると追加できます。
    </Info>
  </Step>

  <Step title="Auth0 React SDK と Capacitor プラグインをインストールする" stepNumber={2}>
    Ionic のスターターtemplateでは `react@19.0.0` が生成されることがありますが、これは Auth0 React SDK と互換性がありません。まず、サポート対象の React バージョンを使用していることを確認してください。

    ```shellscript theme={null}
    npm install react@^19.0.1 react-dom@^19.0.1
    ```

    次に、Auth0 SDK と Capacitor プラグインをインストールします。

    ```shellscript theme={null}
    npm install @auth0/auth0-react @capacitor/browser @capacitor/app && npx cap sync
    ```

    * [`@auth0/auth0-react`](https://github.com/auth0/auth0-react): Auth0 認証用の React フックとコンポーネントを提供します
    * [`@capacitor/browser`](https://capacitorjs.com/docs/apis/browser): デバイスのシステムブラウザーでログインページを開きます (iOS では [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller)、Android では [Chrome Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs))
    * [`@capacitor/app`](https://capacitorjs.com/docs/apis/app): Auth0 がアプリにリダイレクトした際のディープリンク callback を処理します

    <Info>
      iOS の Capacitor Browser プラグインは `SFSafariViewController` を使用しますが、iOS 11 以降ではデバイス上の Safari と cookie を共有しません。そのため、これらのデバイスでは [SSO](https://auth0.com/docs/sso) は機能しません。SSO が必要な場合は、[ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) を使用する互換性のあるプラグインを使用してください。
    </Info>
  </Step>

  <Step title="Auth0アプリを設定する" stepNumber={3}>
    次に、Auth0テナントで新しいアプリを作成し、プロジェクトに環境変数を追加します。

    Auth0アプリのセットアップ方法は3つあります。Quick Setupツールを使用する (推奨) 、CLIコマンドを実行する、またはDashboardから手動で設定する方法から選択してください。

    <Info>
      この quickstart 全体では、`YOUR_PACKAGE_ID` はアプリケーションのパッケージ ID を表します。これは `capacitor.config.ts` ファイル内の `appId` フィールドです (例: `io.ionic.starter`) 。詳しくは、[Capacitor の Config スキーマ](https://capacitorjs.com/docs/config#schema) を参照してください。
    </Info>

    <Tabs>
      <Tab title="クイックセットアップ（推奨）">
        Auth0 アプリを作成し、適切な設定値があらかじめ入力された `.env` ファイルをコピーします。

        <CreateInteractiveApp placeholderText="Ionic React" appType="native" allowedCallbackUrls={["YOUR_PACKAGE_ID://YOUR_AUTH0_DOMAIN/capacitor/YOUR_PACKAGE_ID/callback"]} allowedLogoutUrls={["YOUR_PACKAGE_ID://YOUR_AUTH0_DOMAIN/capacitor/YOUR_PACKAGE_ID/callback"]} allowedOriginUrls={["capacitor://localhost", "http://localhost"]} />

        <AuthCodeBlock children={localEnvSnippet} language="shellscript" filename=".env" />

        アプリを作成したら、[Auth0 Dashboard](https://manage.auth0.com/dashboard/) の **Settings** に移動し、**Allowed Callback URLs** と **Allowed Logout URLs** を更新して、`YOUR_PACKAGE_ID` を実際のパッケージ ID に、`YOUR_AUTH0_DOMAIN` を Auth0 ドメインに置き換えます。

        ```text theme={null}
        YOUR_PACKAGE_ID://YOUR_AUTH0_DOMAIN/capacitor/YOUR_PACKAGE_ID/callback
        ```

        また、**Application Type** が **Native** に、**Token Endpoint Authentication Method** が **None** に設定されていることを確認してください。
      </Tab>

      <Tab title="CLI">
        Auth0 アプリを作成し、`.env` ファイルを生成するには、プロジェクトのルートディレクトリで次のコマンドを実行します。

        <CodeGroup>
          ```shellscript Mac theme={null}
          # Auth0 CLI をインストールします（まだインストールしていない場合）
          brew tap auth0/auth0-cli && brew install auth0

          # Auth0 Native アプリケーションを作成します
          auth0 apps create \
            --name "Ionic React App" \
            --type native \
            --callbacks "YOUR_PACKAGE_ID://YOUR_AUTH0_DOMAIN/capacitor/YOUR_PACKAGE_ID/callback" \
            --logout-urls "YOUR_PACKAGE_ID://YOUR_AUTH0_DOMAIN/capacitor/YOUR_PACKAGE_ID/callback" \
            --origins "capacitor://localhost,http://localhost"
          ```

          ```powershell Windows theme={null}
          # Auth0 CLI をインストールします（まだインストールしていない場合）
          scoop bucket add auth0 https://github.com/auth0/scoop-auth0-cli.git
          scoop install auth0

          # Auth0 Native アプリケーションを作成します
          auth0 apps create `
            --name "Ionic React App" `
            --type native `
            --callbacks "YOUR_PACKAGE_ID://YOUR_AUTH0_DOMAIN/capacitor/YOUR_PACKAGE_ID/callback" `
            --logout-urls "YOUR_PACKAGE_ID://YOUR_AUTH0_DOMAIN/capacitor/YOUR_PACKAGE_ID/callback" `
            --origins "capacitor://localhost,http://localhost"
          ```
        </CodeGroup>

        <Note>
          このコマンドでは、次のことが行われます。

          1. 認証済みかどうかを確認し、必要に応じてログインを求めます
          2. Auth0 Native アプリケーションを作成します
          3. **ドメイン** と **クライアントID** を表示します。これらをプロジェクトルートの `.env` ファイルにコピーしてください
        </Note>

        CLI の出力に含まれる値を使って `.env` ファイルを作成します。

        <AuthCodeBlock children={localEnvSnippet} language="shellscript" filename=".env" />
      </Tab>

      <Tab title="Dashboard">
        始める前に、プロジェクトのルートディレクトリに `.env` ファイルを作成してください

        ```shellscript .env theme={null}
        VITE_AUTH0_DOMAIN=YOUR_AUTH0_APP_DOMAIN
        VITE_AUTH0_CLIENT_ID=YOUR_AUTH0_APP_CLIENT_ID
        ```

        1. [Auth0 Dashboard](https://manage.auth0.com/dashboard/)にアクセスします
        2. **Applications** > **Applications** > **Create Application** をクリックします
        3. ポップアップでアプリの名前を入力し、アプリの種類として `Native` を選択して **Create** をクリックします
        4. Application Details ページで **Settings** タブに切り替えます
        5. `.env` ファイル内の `YOUR_AUTH0_APP_DOMAIN` と `YOUR_AUTH0_APP_CLIENT_ID` を、Dashboard の **ドメイン** と **クライアントID** の値に置き換えます
        6. **Token Endpoint Authentication Method** を `None` に設定します

        最後に、Application Details ページの **Settings** タブで、以下の URL を設定します (`YOUR_PACKAGE_ID` は `capacitor.config.ts` の `appId` に置き換えてください) 。

        **Allowed Callback URLs:**

        ```text theme={null}
        YOUR_PACKAGE_ID://YOUR_AUTH0_DOMAIN/capacitor/YOUR_PACKAGE_ID/callback
        ```

        **Allowed Logout URLs:**

        ```text theme={null}
        YOUR_PACKAGE_ID://YOUR_AUTH0_DOMAIN/capacitor/YOUR_PACKAGE_ID/callback
        ```

        **許可済みのオリジン:**

        ```text theme={null}
        capacitor://localhost, http://localhost
        ```

        <Info>
          **Allowed Callback URLs** は、認証後にユーザーを安全にアプリケーションへ戻すための重要なセキュリティ対策です。一致する URL がないとログイン処理は失敗し、ユーザーはアプリにアクセスできず、代わりに Auth0 のエラーページが表示されます。

          **Allowed Logout URLs** は、サインアウト時にシームレスなユーザー体験を提供するために不可欠です。一致する URL がないと、ユーザーはログアウト後にアプリケーションへリダイレクトされず、代わりに汎用的な Auth0 ページに移動します。

          **Allowed Origins** (iOS では `capacitor://localhost`、Android では `http://localhost`) は、ネイティブアプリが Auth0 API と通信するために必要です。
        </Info>
      </Tab>
    </Tabs>

    <Tip>
      `.env` ファイルが存在することを確認してください: `cat .env` (Mac/Linux) または `type .env` (Windows)
    </Tip>
  </Step>

  <Step title="Auth0Provider を設定する" stepNumber={4}>
    `src/main.tsx` を開き、`App` コンポーネントを `Auth0Provider` でラップします。`useRefreshTokens` と `useRefreshTokensFallback` はモバイル向けの設定であり、iOS および Android 上の Ionic アプリでは必須です。

    ```javascript src/main.tsx {5,6,12,13,14,15,16,17,18,19} lines theme={null}
    import React from 'react';
    import { createRoot } from 'react-dom/client';
    import App from './App';
    import { Auth0Provider } from '@auth0/auth0-react';

    // capacitor.config.ts の appId と一致させる必要があります
    const appId = 'io.ionic.starter';

    createRoot(document.getElementById('root')!).render(
      <React.StrictMode>
        <Auth0Provider
          domain={import.meta.env.VITE_AUTH0_DOMAIN}
          clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
          useRefreshTokens={true}
          useRefreshTokensFallback={false}
          authorizationParams={{
            redirect_uri: `${appId}://${import.meta.env.VITE_AUTH0_DOMAIN}/capacitor/${appId}/callback`
          }}
        >
          <App />
        </Auth0Provider>
      </React.StrictMode>
    );
    ```

    * `useRefreshTokens`: Ionic を Android または iOS で使用する場合は必須です。モバイルブラウザーではサードパーティ Cookie がブロックされるため、SDK は iframe ベースのサイレント認証ではなくリフレッシュトークンを使用します。
    * `useRefreshTokensFallback`: モバイルでは利用できない iframe ベースのサイレント認証を SDK が試行しないよう、`false` に設定する必要があります。
    * `authorizationParams.redirect_uri`: OS が Auth0 コールバックをアプリに戻せるように、パッケージ ID をカスタム URL スキームとして使用します。

    <Warning>
      アプリケーションを閉じて再度開いたあとも認証状態を維持するには、`cacheLocation` を `localstorage` に設定することを検討できますが、[localstorage にトークンを保存するリスク](https://auth0.com/docs/libraries/auth0-single-page-app-sdk#change-storage-options)に注意してください。Capacitor では、localstorage は**一時的**なものとして扱う必要があります。OS によって予期せず消去される可能性があります。[Capacitor のストレージに関するガイダンス](https://capacitorjs.com/docs/guides/storage#why-cant-i-just-use-localstorage-or-indexeddb)を参照してください。

      トークンの保存に [Capacitor の Preferences plugin](https://capacitorjs.com/docs/apis/preferences) を使用することは**推奨しません**。これは `UserDefaults` (iOS) および `SharedPreferences` (Android) を基盤としており、暗号化されておらず、クラウドに同期される可能性があるためです。より安全で永続的なストレージメカニズムが必要な場合、SDK は[カスタムキャッシュ実装](https://github.com/auth0/auth0-spa-js/blob/master/EXAMPLES.md#creating-a-custom-cache)をサポートしています。
    </Warning>
  </Step>

  <Step title="ログイン、ログアウト、ユーザープロファイル、コールバックハンドラーを作成する" stepNumber={5}>
    ファイルを作成する

    <CodeGroup>
      ```shellscript Mac/Linux theme={null}
      touch src/LoginButton.tsx && touch src/LogoutButton.tsx && touch src/Profile.tsx
      ```

      ```powershell Windows theme={null}
      New-Item -ItemType File -Path src/LoginButton.tsx
      New-Item -ItemType File -Path src/LogoutButton.tsx
      New-Item -ItemType File -Path src/Profile.tsx
      ```
    </CodeGroup>

    次のコードスニペットを追加してください

    <AuthCodeGroup>
      ```javascript src/LoginButton.tsx lines theme={null}
      import { useAuth0 } from '@auth0/auth0-react';
      import { Browser } from '@capacitor/browser';
      import { IonButton } from '@ionic/react';

      const LoginButton: React.FC = () => {
        const { loginWithRedirect } = useAuth0();

        const login = async () => {
          await loginWithRedirect({
            async openUrl(url) {
              await Browser.open({
                url,
                windowName: "_self"
              });
            }
          });
        };

        return <IonButton onClick={login}>Log in</IonButton>;
      };

      export default LoginButton;
      ```

      ```javascript src/LogoutButton.tsx lines theme={null}
      import { useAuth0 } from '@auth0/auth0-react';
      import { Browser } from '@capacitor/browser';
      import { IonButton } from '@ionic/react';

      const appId = 'io.ionic.starter';

      const LogoutButton: React.FC = () => {
        const { logout } = useAuth0();

        const logoutUri = `${appId}://${import.meta.env.VITE_AUTH0_DOMAIN}/capacitor/${appId}/callback`;

        const doLogout = async () => {
          await logout({
            logoutParams: {
              returnTo: logoutUri,
            },
            async openUrl(url) {
              await Browser.open({
                url,
                windowName: "_self"
              });
            }
          });
        };

        return <IonButton onClick={doLogout} color="danger">Log out</IonButton>;
      };

      export default LogoutButton;
      ```

      ```javascript src/Profile.tsx lines theme={null}
      import { useAuth0 } from '@auth0/auth0-react';
      import { IonAvatar, IonItem, IonLabel } from '@ionic/react';

      const Profile: React.FC = () => {
        const { user, isAuthenticated, isLoading } = useAuth0();

        if (isLoading) {
          return <IonItem><IonLabel>Loading profile...</IonLabel></IonItem>;
        }

        return (
          isAuthenticated && user ? (
            <IonItem lines="none">
              <IonAvatar slot="start">
                <img
                  src={user.picture || `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='50' fill='%2363b3ed'/%3E%3Cpath d='M50 45c7.5 0 13.64-6.14 13.64-13.64S57.5 17.72 50 17.72s-13.64 6.14-13.64 13.64S42.5 45 50 45zm0 6.82c-9.09 0-27.28 4.56-27.28 13.64v3.41c0 1.88 1.53 3.41 3.41 3.41h47.74c1.88 0 3.41-1.53 3.41-3.41v-3.41c0-9.08-18.19-13.64-27.28-13.64z' fill='%23fff'/%3E%3C/svg%3E`}
                  alt={user.name || 'User'}
                  onError={(e) => {
                    const target = e.target as HTMLImageElement;
                    target.src = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='50' fill='%2363b3ed'/%3E%3Cpath d='M50 45c7.5 0 13.64-6.14 13.64-13.64S57.5 17.72 50 17.72s-13.64 6.14-13.64 13.64S42.5 45 50 45zm0 6.82c-9.09 0-27.28 4.56-27.28 13.64v3.41c0 1.88 1.53 3.41 3.41 3.41h47.74c1.88 0 3.41-1.53 3.41-3.41v-3.41c0-9.08-18.19-13.64-27.28-13.64z' fill='%23fff'/%3E%3C/svg%3E`;
                  }}
                />
              </IonAvatar>
              <IonLabel>
                <h2>{user.name}</h2>
                <p>{user.email}</p>
              </IonLabel>
            </IonItem>
          ) : null
        );
      };

      export default Profile;
      ```

      ```javascript src/App.tsx expandable lines theme={null}
      import { useEffect } from 'react';
      import { Redirect, Route } from 'react-router-dom';
      import {
        IonApp,
        IonIcon,
        IonLabel,
        IonRouterOutlet,
        IonTabBar,
        IonTabButton,
        IonTabs,
        setupIonicReact,
      } from '@ionic/react';
      import { IonReactRouter } from '@ionic/react-router';
      import { personCircle, logIn } from 'ionicons/icons';
      import { App as CapApp } from '@capacitor/app';
      import { Browser } from '@capacitor/browser';
      import { useAuth0 } from '@auth0/auth0-react';
      import HomePage from './pages/Home';

      /* Ionic CSS */
      import '@ionic/react/css/core.css';
      import '@ionic/react/css/normalize.css';
      import '@ionic/react/css/structure.css';
      import '@ionic/react/css/typography.css';
      import '@ionic/react/css/padding.css';
      import '@ionic/react/css/float-elements.css';
      import '@ionic/react/css/text-alignment.css';
      import '@ionic/react/css/text-transformation.css';
      import '@ionic/react/css/flex-utils.css';
      import '@ionic/react/css/display.css';

      setupIonicReact();

      const App: React.FC = () => {
        const { handleRedirectCallback } = useAuth0();

        useEffect(() => {
          CapApp.addListener('appUrlOpen', async ({ url }) => {
            if (url.includes('state') && (url.includes('code') || url.includes('error'))) {
              await handleRedirectCallback(url);
            }
            await Browser.close();
          });
        }, [handleRedirectCallback]);

        return (
          <IonApp>
            <IonReactRouter>
              <IonTabs>
                <IonRouterOutlet>
                  <Route exact path="/home">
                    <HomePage />
                  </Route>
                  <Route exact path="/">
                    <Redirect to="/home" />
                  </Route>
                </IonRouterOutlet>
                <IonTabBar slot="bottom">
                  <IonTabButton tab="home" href="/home">
                    <IonIcon aria-hidden="true" icon={personCircle} />
                    <IonLabel>Home</IonLabel>
                  </IonTabButton>
                </IonTabBar>
              </IonTabs>
            </IonReactRouter>
          </IonApp>
        );
      };

      export default App;
      ```

      ```javascript src/pages/Home.tsx expandable lines theme={null}
      import {
        IonContent,
        IonHeader,
        IonPage,
        IonTitle,
        IonToolbar,
      } from '@ionic/react';
      import { useAuth0 } from '@auth0/auth0-react';
      import LoginButton from '../LoginButton';
      import LogoutButton from '../LogoutButton';
      import Profile from '../Profile';

      const HomePage: React.FC = () => {
        const { isAuthenticated, isLoading, error } = useAuth0();

        return (
          <IonPage>
            <IonHeader>
              <IonToolbar>
                <IonTitle>Auth0 + Ionic React</IonTitle>
              </IonToolbar>
            </IonHeader>
            <IonContent className="ion-padding" fullscreen>
              {isLoading && <p>Loading...</p>}

              {error && <p>Error: {error.message}</p>}

              {!isLoading && !isAuthenticated && (
                <div className="ion-text-center ion-padding-top">
                  <h2>Welcome</h2>
                  <p>Sign in to get started</p>
                  <LoginButton />
                </div>
              )}

              {!isLoading && isAuthenticated && (
                <div className="ion-padding-top">
                  <Profile />
                  <div className="ion-text-center ion-padding-top">
                    <LogoutButton />
                  </div>
                </div>
              )}
            </IonContent>
          </IonPage>
        );
      };

      export default HomePage;
      ```
    </AuthCodeGroup>

    `LoginButton` および `LogoutButton` の `openUrl` コールバックは、Capacitor の Browser プラグインを使用して、アプリを完全に離れることなく、デバイスのシステムブラウザコンポーネントで Auth0 のログインページおよびログアウトページを開きます。

    `App` コンポーネントは `appUrlOpen` イベントをリッスンします。このイベントは、Auth0 がカスタム URL スキームを使用してアプリにリダイレクトバックする際に発火します。`handleRedirectCallback` を呼び出して認可コードをトークンと交換し、その後ブラウザを閉じます。

    <Info>
      デフォルトでは、SDK の `loginWithRedirect` メソッドは `window.location.href` を使用してログインページに移動するため、デバイスのデフォルトのブラウザーアプリが開きます。`openUrl` を `Browser.open` を使用するように設定すると、認証フローをアプリ内のコンテキストに維持できるため、ユーザーエクスペリエンスが向上します。
    </Info>
  </Step>

  <Step title="アプリを実行する" stepNumber={6}>
    まずはブラウザーでテストする

    ```shellscript theme={null}
    ionic serve
    ```

    <Warning>
      ブラウザーで `ionic serve` を実行する場合、ブラウザーはカスタム URL スキームを処理できないため、カスタム URL スキームのリダイレクト (`io.ionic.starter://...`) は機能しません。ブラウザーでテストするには、`src/main.tsx` の `redirect_uri` を一時的に `http://localhost:8100` に変更し、Dashboard の Auth0 アプリの **Allowed Callback URLs** と **Allowed Logout URLs** に `http://localhost:8100` を追加してください。ネイティブ向けにビルドする前に、この変更を元に戻すことを忘れないでください。
    </Warning>

    デバイスまたはシミュレーターで実行するには、まずネイティブプラットフォームを追加します。

    ```shellscript theme={null}
    npx cap add ios
    npx cap add android
    ```

    次に、ビルド、同期、実行を行います。

    <CodeGroup>
      ```shellscript iOS theme={null}
      ionic build && npx cap sync && npx cap run ios
      ```

      ```shellscript Android theme={null}
      ionic build && npx cap sync && npx cap run android
      ```
    </CodeGroup>

    <Info>
      実行する前に、`npx cap add` を使用してネイティブプラットフォームを追加する必要があります。これはプラットフォームごとに一度だけ実行すれば十分です。その後、`npx cap sync` によって、ビルドした Web アセットがコピーされ、ネイティブプラグインが更新されます。
    </Info>
  </Step>
</Steps>

<Check>
  **チェックポイント**

  これで、Ionic アプリで Auth0 ログインが完全に動作するようになっているはずです。デバイス上で実行している場合は、「ログイン」をタップするとシステムブラウザーで Auth0 の Universal Login ページが開き、認証後にアプリへリダイレクトされて、ユーザープロファイルが表示されます。
</Check>

***

<div id="advanced-usage">
  ## 高度な使い方
</div>

<Accordion title="カスタム URL スキームの設定（iOS と Android）">
  デバイス上で Auth0 の callback を機能させるには、各プラットフォームでパッケージ ID をカスタム URL スキームとして登録します。

  **iOS** — `ios/App/App/Info.plist` に追加します。

  ```xml ios/App/App/Info.plist theme={null}
  <key>CFBundleURLTypes</key>
  <array>
    <dict>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>io.ionic.starter</string>
      </array>
    </dict>
  </array>
  ```

  **Android** — `android/app/src/main/AndroidManifest.xml` のメイン `<activity>` 内に intent filter を追加します。

  ```xml android/app/src/main/AndroidManifest.xml theme={null}
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="io.ionic.starter" />
  </intent-filter>
  ```

  `io.ionic.starter` は、`capacitor.config.ts` の実際の `appId` に置き換えてください。

  <Info>
    詳細については、iOS の場合は [Defining a Custom URL Scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app)、Android の場合は [Create Deep Links to App Content](https://developer.android.com/training/app-links/deep-linking) を参照してください。
  </Info>
</Accordion>

<Accordion title="Ionic Router を使用したルートの保護">
  Auth0 の認証状態を使用して、Ionic アプリケーション内の特定のルートを保護します。

  ```jsx src/App.tsx theme={null}
  import { useAuth0, withAuthenticationRequired } from '@auth0/auth0-react';
  import { IonReactRouter } from '@ionic/react-router';
  import { IonRouterOutlet } from '@ionic/react';
  import { Route, Redirect } from 'react-router-dom';
  import HomePage from './pages/Home';
  import DashboardPage from './pages/Dashboard';

  const ProtectedRoute = ({ component, ...args }: any) => {
    const Component = withAuthenticationRequired(component, {
      onRedirecting: () => <div className="ion-padding">Loading...</div>,
    });
    return <Route {...args} render={() => <Component />} />;
  };

  const App: React.FC = () => {
    // ... 前の手順の callback handler

    return (
      <IonApp>
        <IonReactRouter>
          <IonRouterOutlet>
            <Route exact path="/home" component={HomePage} />
            <ProtectedRoute exact path="/dashboard" component={DashboardPage} />
            <Route exact path="/">
              <Redirect to="/home" />
            </Route>
          </IonRouterOutlet>
        </IonReactRouter>
      </IonApp>
    );
  };
  ```

  `withAuthenticationRequired` HOC は、未認証のユーザーが保護されたルートにアクセスしようとすると、自動的に Auth0 のログインページへリダイレクトします。
</Accordion>

<Accordion title="保護されたAPIの呼び出し">
  Auth0Provider を設定して API のオーディエンスを指定し、`getAccessTokenSilently` メソッドを使用してバックエンド用のアクセストークンを取得します。

  ```jsx src/main.tsx theme={null}
  <Auth0Provider
    domain={import.meta.env.VITE_AUTH0_DOMAIN}
    clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
    useRefreshTokens={true}
    useRefreshTokensFallback={false}
    authorizationParams={{
      redirect_uri: `${appId}://${import.meta.env.VITE_AUTH0_DOMAIN}/capacitor/${appId}/callback`,
      audience: "YOUR_API_IDENTIFIER"
    }}
  >
    <App />
  </Auth0Provider>
  ```

  次に、コンポーネントから認証済みの API 呼び出しを実行します。

  ```jsx src/ApiCall.tsx theme={null}
  import { useState } from 'react';
  import { useAuth0 } from '@auth0/auth0-react';
  import { IonButton, IonText } from '@ionic/react';

  const ApiCall: React.FC = () => {
    const { getAccessTokenSilently } = useAuth0();
    const [result, setResult] = useState<string | null>(null);

    const callApi = async () => {
      try {
        const token = await getAccessTokenSilently();

        const response = await fetch('https://your-api.example.com/protected', {
          headers: {
            Authorization: `Bearer ${token}`
          }
        });

        const data = await response.json();
        setResult(JSON.stringify(data, null, 2));
      } catch (error) {
        console.error('API call failed:', error);
      }
    };

    return (
      <div className="ion-padding">
        <IonButton onClick={callApi}>Call Protected API</IonButton>
        {result && (
          <IonText>
            <pre>{result}</pre>
          </IonText>
        )}
      </div>
    );
  };

  export default ApiCall;
  ```
</Accordion>
