> ## 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 Angular と Capacitor アプリケーションにログイン機能を追加

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

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-angular
  ```

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

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

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

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

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

export const localEnvSnippet = `export const environment = {
  production: false,
  auth0: {
    domain: {yourDomain},
    clientId: {yourClientId}
  }
};`;

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

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

    プロジェクトを開く

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

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

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

  <Step title="Auth0 Angular SDK と Capacitor プラグインをインストール" stepNumber={2}>
    Capacitor's Browser プラグインおよび App プラグインと併せて、Auth0 Angular SDK をインストールします:

    ```shellscript theme={null}
    npm install @auth0/auth0-angular @capacitor/browser @capacitor/app
    ```

    * [`@capacitor/browser`](https://capacitorjs.com/docs/apis/browser) - デバイスのシステムブラウザーで Auth0 のログインページを開きます (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 から返されるディープリンクコールバックを処理します

    <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から手動で設定する方法です。

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

        <CreateInteractiveApp placeholderText="Ionic Angular" appType="native" allowedOriginUrls={["capacitor://localhost", "http://localhost"]} />

        <AuthCodeBlock children={localEnvSnippet} language="typescript" filename="src/environments/environment.ts" />

        アプリを作成したら、[Auth0 Dashboard](https://manage.auth0.com/dashboard/) の **Settings** タブで **Allowed Callback URLs** と **Allowed Logout URLs** を更新してください。`YOUR_PACKAGE_ID` は、`capacitor.config.ts` の `appId` (デフォルト: `io.ionic.starter`) に置き換えます。

        **Allowed Callback URLs** と **Allowed Logout URLs**:

        ```text theme={null}
        YOUR_PACKAGE_ID://{yourDomain}/capacitor/YOUR_PACKAGE_ID/callback
        ```
      </Tab>

      <Tab title="CLI">
        プロジェクトのルートディレクトリで次のコマンドを実行し、ネイティブの Auth0 アプリを作成して環境ファイルを生成します。`capacitor.config.ts` で別の `appId` を使用している場合は、`PACKAGE_ID` を更新してください。

        <AuthCodeGroup>
          ```shellscript Mac theme={null}
          AUTH0_APP_NAME="My Ionic Angular App" && PACKAGE_ID="io.ionic.starter" && brew tap auth0/auth0-cli && brew install auth0 && auth0 login --no-input && DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') && CALLBACK_URL="${PACKAGE_ID}://${DOMAIN}/capacitor/${PACKAGE_ID}/callback" && auth0 apps create -n "${AUTH0_APP_NAME}" -t native -c "${CALLBACK_URL}" -l "${CALLBACK_URL}" -o "capacitor://localhost,http://localhost" --json --metadata created_by="quickstart-docs-cli" > auth0-app-details.json && CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) && mkdir -p src/environments && echo "export const environment = { production: false, auth0: { domain: '${DOMAIN}', clientId: '${CLIENT_ID}' } };" > src/environments/environment.ts && rm auth0-app-details.json && echo "Environment file created at src/environments/environment.ts with your Auth0 details:" && cat src/environments/environment.ts
          ```

          ```shellscript Windows theme={null}
          $AppName = "My Ionic Angular App"; $PackageId = "io.ionic.starter"; winget install Auth0.CLI; auth0 login --no-input; $Domain = (auth0 tenants list --json | ConvertFrom-Json | Where-Object { $_.active -eq $true }).name; $CallbackUrl = "${PackageId}://${Domain}/capacitor/${PackageId}/callback"; auth0 apps create -n "$AppName" -t native -c "$CallbackUrl" -l "$CallbackUrl" -o "capacitor://localhost,http://localhost" --json --metadata created_by="quickstart-docs-cli" | Set-Content -Path auth0-app-details.json; $ClientId = (Get-Content -Raw auth0-app-details.json | ConvertFrom-Json).client_id; New-Item -ItemType Directory -Force -Path "src\environments"; Set-Content -Path "src\environments\environment.ts" -Value "export const environment = { production: false, auth0: { domain: '$Domain', clientId: '$ClientId' } };"; Remove-Item auth0-app-details.json; Write-Output "Environment file created at src\environments\environment.ts with your Auth0 details:"; Get-Content "src\environments\environment.ts"
          ```
        </AuthCodeGroup>
      </Tab>

      <Tab title="Dashboard">
        開始する前に、プロジェクトに環境ファイルを作成してください

        ```typescript src/environments/environment.ts lines theme={null}
        export const environment = {
          production: false,
          auth0: {
            domain: 'YOUR_AUTH0_APP_DOMAIN',
            clientId: 'YOUR_AUTH0_APP_CLIENT_ID'
          }
        };
        ```

        1. [Auth0 Dashboard](https://manage.auth0.com/dashboard/) にアクセスします
        2. **Applications** > **Applications** > **Create Application** をクリックします
        3. アプリの名前 (例: `Ionic Angular App`) を入力し、アプリケーションタイプとして **Native** を選択して、**Create** をクリックします
        4. **Settings** タブに移動し、**ドメイン** と **クライアントID** の値を控えます
        5. `src/environments/environment.ts` の `YOUR_AUTH0_APP_DOMAIN` と `YOUR_AUTH0_APP_CLIENT_ID` を実際の値に置き換えます

        <Info>
          このクイックスタートでは、`YOUR_PACKAGE_ID` は `capacitor.config.ts` の `appId` フィールドを指します。デフォルト値は `io.ionic.starter` です。詳細については、[Capacitor's Config schema](https://capacitorjs.com/docs/config#schema) を参照してください。
        </Info>

        **Settings** タブで次の URL を設定します (`YOUR_PACKAGE_ID` は実際のパッケージ ID に置き換えてください) 。

        **Allowed Callback URLs:**

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

        **許可済みのログアウトURL:**

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

        **許可されたオリジン:**

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

        <Info>
          **Allowed Callback URLs** は、認証後にユーザーを安全にアプリケーションへ戻すための重要なセキュリティ対策です。一致する URL がないと、ログイン処理は失敗します。

          **Allowed Logout URLs** は、サインアウト時にシームレスなユーザー体験を提供するために不可欠です。一致する URL がないと、ユーザーはログアウト時にエラーを受け取ります。

          **Allowed Origins** は、サイレント認証に必要です。`capacitor://localhost` は iOS 用のオリジンで、`http://localhost` は Android 用です。
        </Info>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Auth0 モジュールを構成する" stepNumber={4}>
    前の手順で環境ファイルを配置したら、アプリでAuth0モジュールを設定します。

    ```typescript src/main.ts {4-6,10,17-25} lines theme={null}
    import { bootstrapApplication } from '@angular/platform-browser';
    import { RouteReuseStrategy, provideRouter } from '@angular/router';
    import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalone';
    import { provideAuth0 } from '@auth0/auth0-angular';
    import { environment } from './environments/environment';
    import config from '../capacitor.config';
    import { routes } from './app/app.routes';
    import { AppComponent } from './app/app.component';

    const redirect_uri = `${config.appId}://${environment.auth0.domain}/capacitor/${config.appId}/callback`;

    bootstrapApplication(AppComponent, {
      providers: [
        { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
        provideIonicAngular(),
        provideRouter(routes),
        provideAuth0({
          domain: environment.auth0.domain,
          clientId: environment.auth0.clientId,
          useRefreshTokens: true,
          useRefreshTokensFallback: false,
          authorizationParams: {
            redirect_uri,
          },
        }),
      ],
    });
    ```

    `provideAuth0` の設定には、次が含まれます。

    * `useRefreshTokens: true` — モバイルでは**必須**です。Capacitor アプリでは iframe ベースのサイレント認証を使用できないため、セッションの更新にはリフレッシュトークンを使用します。
    * `useRefreshTokensFallback: false` — モバイルでは**必須**です。ネイティブアプリでは利用できない iframe ベースのサイレント認証に SDK がフォールバックするのを防ぎます。
    * `authorizationParams.redirect_uri` — 認証後にアプリへリダイレクトして戻れるよう、アプリのカスタム URL スキームを使用します。

    <Warning>
      アプリケーションを閉じて再度開いたあとも認証状態を保持するには、`cacheLocation` を `localstorage` に設定するとよい場合があります。ただし、[localstorage にトークンを保存するリスク](https://auth0.com/docs/libraries/auth0-single-page-app-sdk#change-storage-options)があることに注意してください。また、Capacitor アプリでは localstorage は**一時的**なストレージとして扱う必要があります。[Capacitor ドキュメントのストレージに関するガイダンス](https://capacitorjs.com/docs/guides/storage#why-cant-i-just-use-localstorage-or-indexeddb)も確認してください。
    </Warning>
  </Step>

  <Step title="ログイン、ログアウト、Profile コンポーネントを作成する" stepNumber={5}>
    コンポーネントファイルを作成する

    <CodeGroup>
      ```shellscript Mac/Linux theme={null}
      touch src/app/login-button.component.ts && touch src/app/logout-button.component.ts && touch src/app/profile.component.ts
      ```

      ```powershell Windows theme={null}
      New-Item -ItemType File -Path src/app/login-button.component.ts
      New-Item -ItemType File -Path src/app/logout-button.component.ts
      New-Item -ItemType File -Path src/app/profile.component.ts
      ```
    </CodeGroup>

    各コンポーネントに以下のコードを追加します。

    <AuthCodeGroup>
      ```typescript src/app/login-button.component.ts expandable lines theme={null}
      import { Component, inject } from '@angular/core';
      import { AuthService } from '@auth0/auth0-angular';
      import { Browser } from '@capacitor/browser';
      import { IonButton } from '@ionic/angular/standalone';

      @Component({
        selector: 'app-login-button',
        standalone: true,
        imports: [IonButton],
        template: `<ion-button (click)="login()" expand="block">Log in</ion-button>`,
      })
      export class LoginButtonComponent {
        private auth = inject(AuthService);

        login(): void {
          this.auth
            .loginWithRedirect({
              async openUrl(url: string) {
                await Browser.open({ url, windowName: '_self' });
              },
            })
            .subscribe();
        }
      }
      ```

      ```typescript src/app/logout-button.component.ts expandable lines theme={null}
      import { Component, inject } from '@angular/core';
      import { AuthService } from '@auth0/auth0-angular';
      import { Browser } from '@capacitor/browser';
      import { IonButton } from '@ionic/angular/standalone';
      import { environment } from '../environments/environment';
      import config from '../../capacitor.config';

      const returnTo = `${config.appId}://${environment.auth0.domain}/capacitor/${config.appId}/callback`;

      @Component({
        selector: 'app-logout-button',
        standalone: true,
        imports: [IonButton],
        template: `<ion-button (click)="logout()" expand="block" color="danger">Log out</ion-button>`,
      })
      export class LogoutButtonComponent {
        private auth = inject(AuthService);

        logout(): void {
          this.auth
            .logout({
              logoutParams: { returnTo },
              async openUrl(url: string) {
                await Browser.open({ url });
              },
            })
            .subscribe();
        }
      }
      ```

      ```typescript src/app/profile.component.ts expandable lines theme={null}
      import { Component, inject } from '@angular/core';
      import { CommonModule } from '@angular/common';
      import { AuthService } from '@auth0/auth0-angular';
      import { IonAvatar, IonItem, IonLabel } from '@ionic/angular/standalone';

      @Component({
        selector: 'app-profile',
        standalone: true,
        imports: [CommonModule, IonAvatar, IonItem, IonLabel],
        template: `
          @if (auth.user$ | async; as user) {
            <ion-item lines="none">
              <ion-avatar slot="start">
                <img [src]="user.picture" [alt]="user.name" />
              </ion-avatar>
              <ion-label>
                <h2>{{ user.name }}</h2>
                <p>{{ user.email }}</p>
              </ion-label>
            </ion-item>
          }
        `,
      })
      export class ProfileComponent {
        protected auth = inject(AuthService);
      }
      ```
    </AuthCodeGroup>

    次に、Auth0 コールバックを処理するよう App コンポーネントを更新し、ホームページでコンポーネントを使用できるようにします。

    <Tabs>
      <Tab title="App コンポーネント">
        `src/app/app.component.ts` の内容を次のように置き換えます。

        ```typescript src/app/app.component.ts expandable lines theme={null}
        import { Component, OnInit, NgZone, inject } from '@angular/core';
        import { AuthService } from '@auth0/auth0-angular';
        import { mergeMap } from 'rxjs/operators';
        import { Browser } from '@capacitor/browser';
        import { App } from '@capacitor/app';
        import { IonApp, IonRouterOutlet } from '@ionic/angular/standalone';
        import { environment } from '../environments/environment';
        import config from '../../capacitor.config';

        const callbackUri = `${config.appId}://${environment.auth0.domain}/capacitor/${config.appId}/callback`;

        @Component({
          selector: 'app-root',
          standalone: true,
          imports: [IonApp, IonRouterOutlet],
          template: '<ion-app><ion-router-outlet></ion-router-outlet></ion-app>',
        })
        export class AppComponent implements OnInit {
          private auth = inject(AuthService);
          private ngZone = inject(NgZone);

          ngOnInit(): void {
            App.addListener('appUrlOpen', ({ url }) => {
              this.ngZone.run(() => {
                if (url?.startsWith(callbackUri)) {
                  if (
                    url.includes('state=') &&
                    (url.includes('error=') || url.includes('code='))
                  ) {
                    this.auth
                      .handleRedirectCallback(url)
                      .pipe(mergeMap(() => Browser.close()))
                      .subscribe();
                  } else {
                    Browser.close();
                  }
                }
              });
            });
          }
        }
        ```
      </Tab>

      <Tab title="ホームページ">
        `src/app/tab1/tab1.page.ts` の内容を次のように置き換えます。

        ```typescript src/app/tab1/tab1.page.ts expandable lines theme={null}
        import { Component, inject } from '@angular/core';
        import { CommonModule } from '@angular/common';
        import { AuthService } from '@auth0/auth0-angular';
        import {
          IonHeader, IonToolbar, IonTitle, IonContent,
          IonCard, IonCardHeader, IonCardTitle, IonCardContent,
        } from '@ionic/angular/standalone';
        import { LoginButtonComponent } from '../login-button.component';
        import { LogoutButtonComponent } from '../logout-button.component';
        import { ProfileComponent } from '../profile.component';

        @Component({
          selector: 'app-tab1',
          standalone: true,
          imports: [
            CommonModule,
            IonHeader, IonToolbar, IonTitle, IonContent,
            IonCard, IonCardHeader, IonCardTitle, IonCardContent,
            LoginButtonComponent, LogoutButtonComponent, ProfileComponent,
          ],
          template: `
            <ion-header>
              <ion-toolbar>
                <ion-title>Home</ion-title>
              </ion-toolbar>
            </ion-header>

            <ion-content class="ion-padding">
              @if (auth.isAuthenticated$ | async) {
                <ion-card>
                  <ion-card-header>
                    <ion-card-title>Welcome!</ion-card-title>
                  </ion-card-header>
                  <ion-card-content>
                    <app-profile />
                    <div class="ion-margin-top">
                      <app-logout-button />
                    </div>
                  </ion-card-content>
                </ion-card>
              } @else {
                <ion-card>
                  <ion-card-header>
                    <ion-card-title>Get Started</ion-card-title>
                  </ion-card-header>
                  <ion-card-content>
                    <p>Sign in to your account to get started.</p>
                    <div class="ion-margin-top">
                      <app-login-button />
                    </div>
                  </ion-card-content>
                </ion-card>
              }
            </ion-content>
          `,
        })
        export class Tab1Page {
          protected auth = inject(AuthService);
        }
        ```
      </Tab>
    </Tabs>

    <Info>
      App Component の `appUrlOpen` イベントコールバックは、`this.ngZone.run()` でラップされています。これは**必須**です。Capacitor プラグインのコールバックは Angular の zone の外で実行されるため、これがないとログイン後の認証状態の変化を Angular が検出できません。詳しくは、[Using Angular with Capacitor](https://capacitorjs.com/docs/guides/angular) を参照してください。
    </Info>
  </Step>

  <Step title="アプリを実行する" stepNumber={6}>
    まず、ブラウザーでテストしてください:

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

    <Warning>
      ブラウザーで `ionic serve` を実行する場合、ブラウザーはカスタム URL スキームを処理できないため、カスタム URL スキームによるリダイレクトは機能しません。ブラウザーでテストする際は、一時的に `src/main.ts` の `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` を使用してネイティブプラットフォームを追加する必要があります。これはプラットフォームごとに一度だけ実行すれば十分です。カスタム URL スキームが登録されていることを確認してください (詳細は以下の Advanced Usage を参照) 。
    </Info>
  </Step>
</Steps>

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

  これで、Auth0 のログインページが [localhost](http://localhost:8100/) 上で正常に動作しているはずです
</Check>

***

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

<Accordion title="従来の NgModule 方式を使用する">
  `--type=angular` (`--type=angular-standalone` ではなく) でプロジェクトを作成した場合や、NgModule を使いたい場合は、`AuthModule.forRoot` で SDK を設定します。

  ```typescript src/app/app.module.ts expandable lines theme={null}
  import { NgModule } from '@angular/core';
  import { BrowserModule } from '@angular/platform-browser';
  import { RouteReuseStrategy } from '@angular/router';
  import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
  import { AuthModule } from '@auth0/auth0-angular';
  import { environment } from '../environments/environment';
  import config from '../../capacitor.config';

  import { AppRoutingModule } from './app-routing.module';
  import { AppComponent } from './app.component';

  const redirect_uri = `${config.appId}://${environment.auth0.domain}/capacitor/${config.appId}/callback`;

  @NgModule({
    declarations: [AppComponent],
    imports: [
      BrowserModule,
      IonicModule.forRoot(),
      AppRoutingModule,
      AuthModule.forRoot({
        domain: environment.auth0.domain,
        clientId: environment.auth0.clientId,
        useRefreshTokens: true,
        useRefreshTokensFallback: false,
        authorizationParams: {
          redirect_uri,
        },
      }),
    ],
    providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
    bootstrap: [AppComponent],
  })
  export class AppModule {}
  ```

  <Note>
    NgModule を使用する場合は、`inject()` ではなくコンストラクター (`constructor(public auth: AuthService)`) 経由で `AuthService` を注入してください。テンプレートでは `@if` の制御フロー構文ではなく、`*ngIf="auth.user$ | async as user"` を使用します。コンポーネントは `standalone: true` としてマークするのではなく、モジュール内で宣言する必要があります。
  </Note>
</Accordion>

<Accordion title="カスタム URL スキームの設定">
  実機で認証をテストするには、各プラットフォームでカスタム URL スキームを登録します。

  ### iOS

  `ios/App/App/Info.plist` にカスタム URL スキームを登録します。

  ```xml lines theme={null}
  <key>CFBundleURLTypes</key>
  <array>
    <dict>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>YOUR_PACKAGE_ID</string>
      </array>
    </dict>
  </array>
  ```

  `YOUR_PACKAGE_ID` は `capacitor.config.ts` の `appId` に置き換えてください。詳細は、[Defining a Custom URL Scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) を参照してください。

  ### Android

  `android/app/src/main/AndroidManifest.xml` の `<activity>` タグ内にインテントフィルターを追加します。

  ```xml lines 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="YOUR_PACKAGE_ID" />
  </intent-filter>
  ```

  `YOUR_PACKAGE_ID` は `capacitor.config.ts` の `appId` に置き換えてください。詳細は、[Create Deep Links to App Content](https://developer.android.com/training/app-links/deep-linking) を参照してください。

  ### ビルドしてデバイスで実行する

  ```shellscript theme={null}
  ionic build && npx cap sync && npx cap open ios
  ```

  または Android の場合:

  ```shellscript theme={null}
  ionic build && npx cap sync && npx cap open android
  ```
</Accordion>

<Accordion title="AuthGuard でルートを保護する">
  認証が必要なルートを保護するには、関数ベースのガードを使用します。

  ```typescript src/app/app.routes.ts lines theme={null}
  import { Routes } from '@angular/router';
  import { authGuardFn } from '@auth0/auth0-angular';

  export const routes: Routes = [
    {
      path: 'tabs',
      loadComponent: () => import('./tabs/tabs.page').then((m) => m.TabsPage),
      children: [
        {
          path: 'tab1',
          loadComponent: () => import('./tab1/tab1.page').then((m) => m.Tab1Page),
        },
        {
          path: 'tab2',
          loadComponent: () => import('./tab2/tab2.page').then((m) => m.Tab2Page),
          canActivate: [authGuardFn],
        },
        {
          path: 'tab3',
          loadComponent: () => import('./tab3/tab3.page').then((m) => m.Tab3Page),
          canActivate: [authGuardFn],
        },
        {
          path: '',
          redirectTo: '/tabs/tab1',
          pathMatch: 'full',
        },
      ],
    },
    {
      path: '',
      redirectTo: '/tabs/tab1',
      pathMatch: 'full',
    },
  ];
  ```

  未認証のユーザーが保護されたルートに遷移すると、`authGuardFn` によって Auth0 のログインページへ自動的にリダイレクトされます。
</Accordion>

<Accordion title="保護された API を呼び出す">
  API 呼び出しにアクセストークンを自動的に付与するように、HTTP インターセプターを設定します。

  ```typescript src/main.ts expandable lines theme={null}
  import { bootstrapApplication } from '@angular/platform-browser';
  import { RouteReuseStrategy, provideRouter } from '@angular/router';
  import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalone';
  import { provideHttpClient, withInterceptors } from '@angular/common/http';
  import { provideAuth0, authHttpInterceptorFn } from '@auth0/auth0-angular';
  import { environment } from './environments/environment';
  import config from '../capacitor.config';
  import { routes } from './app/app.routes';
  import { AppComponent } from './app/app.component';

  const redirect_uri = `${config.appId}://${environment.auth0.domain}/capacitor/${config.appId}/callback`;

  bootstrapApplication(AppComponent, {
    providers: [
      { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
      provideIonicAngular(),
      provideRouter(routes),
      provideAuth0({
        domain: environment.auth0.domain,
        clientId: environment.auth0.clientId,
        useRefreshTokens: true,
        useRefreshTokensFallback: false,
        authorizationParams: {
          redirect_uri,
          audience: 'YOUR_API_IDENTIFIER',
        },
        httpInterceptor: {
          allowedList: [
            'http://localhost:3001/api/*',
          ],
        },
      }),
      provideHttpClient(
        withInterceptors([authHttpInterceptorFn])
      ),
    ],
  });
  ```

  次に、Angular の `HttpClient` を使って API を呼び出します。インターセプターによって Bearer トークンが自動的に付与されます。

  ```typescript src/app/api.service.ts lines theme={null}
  import { Injectable, inject } from '@angular/core';
  import { HttpClient } from '@angular/common/http';

  @Injectable({ providedIn: 'root' })
  export class ApiService {
    private http = inject(HttpClient);

    getData() {
      return this.http.get('http://localhost:3001/api/data');
    }
  }
  ```

  <Tip>
    `httpInterceptor.allowedList` は、アクセストークンを付与する API エンドポイントを決定します。API 用のアクセストークンをリクエストするには、`audience` パラメーターを含めてください。`YOUR_API_IDENTIFIER` は、[Auth0 Dashboard > APIs](https://manage.auth0.com/dashboard/) の識別子に置き換えてください。
  </Tip>
</Accordion>

<Accordion title="よくある問題と解決策">
  ### コールバック URL の不一致エラー

  **解決策:** Auth0 Dashboard のコールバック URL が、アプリで構築される URL と完全に一致していることを確認してください。`YOUR_PACKAGE_ID` が `capacitor.config.ts` の `appId` フィールドと一致していることも確認してください。

  ### ログイン後に画面が更新されない

  **解決策:** `appUrlOpen` イベントのコールバックを `this.ngZone.run()` でラップしてください。これを行わないと、Angular は `handleRedirectCallback` による state の変更を検出できません。詳しくは [Using Angular with Capacitor](https://capacitorjs.com/docs/guides/angular) を参照してください。

  ### 「PKCE not allowed」エラー

  **修正方法:**

  1. Auth0 Dashboard > Applications > Your Application に移動します
  2. **Application Type** を **Native** に変更します
  3. **Token Endpoint Authentication Method** を `None` に設定します
  4. 変更を保存して、再度試してください

  ### iOS で SSO が機能しない

  Capacitor の Browser プラグインは `SFSafariViewController` を使用します。このプラグインは、iOS 11 以降では Safari と Cookie を共有しません。SSO が必要な場合は、[ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) を使用する互換性のあるプラグインを使用してください。

  ### ログインは成功するが、アプリの再起動後もユーザーが未認証のままになる

  アプリの再起動後もトークンを保持するには、`provideAuth0` の設定で `cacheLocation: 'localstorage'` を有効にしてください。[セキュリティへの影響](https://auth0.com/docs/libraries/auth0-single-page-app-sdk#change-storage-options) と [Capacitor のストレージ制限](https://capacitorjs.com/docs/guides/storage#why-cant-i-just-use-localstorage-or-indexeddb) に注意してください。

  ### Observable がまったく実行されない

  すべての `AuthService` メソッドはコールド Observable を返します。実行するには `.subscribe()` を呼び出す必要があります。`loginWithRedirect()` または `logout()` を呼び出しても何も起こらないように見える場合は、末尾で `.subscribe()` が連結されているか確認してください。
</Accordion>

<div id="next-steps">
  ## 次のステップ
</div>

詳細については、以下のリソースを参照してください。

* [Sample Application](https://github.com/auth0-samples/auth0-ionic-samples/tree/main/angular) — Auth0 統合を含む完全な Ionic Angular サンプルアプリ
* [Auth0 Angular SDK Documentation](https://github.com/auth0/auth0-angular) — SDK の完全なリファレンスとサンプル
* [Auth0 Dashboard](https://manage.auth0.com/dashboard/) — Auth0 テナントとアプリケーションを設定および管理
* [Auth0 Marketplace](https://marketplace.auth0.com/) — Auth0 の機能を拡張するために有効にできる統合を探す
