> ## 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.

# Agrega Login a tu aplicación de Ionic Angular con Capacitor

> Esta guía muestra cómo integrar Auth0 en una aplicación de Ionic Angular y Capacitor mediante el SDK de Auth0 para Angular.

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("El nombre de la aplicación es obligatorio");
        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("Error creating client:", err);
        const errorMessage = err instanceof Error ? err.message : "No se pudo crear la aplicación";
        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">Crear aplicación de 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 ? "Creando..." : "Crear"}</Button>
        </div>
        {error && <p className="text-red-500">{error}</p>}
      </Card>;
  };
  const SignInForm = () => {
    return <Card className="items-center">
        <Button onClick={login}>Iniciar sesión</Button> <span>para crear la aplicación</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) + "*****ENMASCARADO*****";
          }
          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="Usa la IA para integrar Auth0" icon="microchip-ai" iconType="solid" defaultOpen>
  Si usas un asistente de programación con IA como Claude Code, Cursor o GitHub Copilot, puedes añadir la autenticación de Auth0 automáticamente en cuestión de minutos con [agent skills](https://agentskills.io/home).

  Primero, instala las agent skills de Auth0:

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

  Después, pídele a tu asistente de IA:

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

  Tu asistente de IA creará automáticamente tu aplicación de Auth0, obtendrá las credenciales, instalará el SDK de Auth0 para Angular y los plugins de Capacitor, configurará el deep linking e implementará flujos de inicio y cierre de sesión con integración con el navegador nativo. Obtén más información sobre [Auth0 agent skills](/es/docs/quickstart/agent-skills).
</Accordion>

<div id="get-started">
  ## Primeros pasos
</div>

Este inicio rápido muestra cómo agregar la autenticación de Auth0 a una aplicación de Ionic Angular con Capacitor. Crearás una aplicación lista para dispositivos móviles con funciones de Login, cierre de sesión y perfil de usuario mediante el SDK de Auth0 para Angular y los plugins de navegador nativos de Capacitor.

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

<Steps>
  <Step title="Crear un proyecto nuevo" stepNumber={1}>
    Cree un nuevo proyecto de Ionic Angular con Capacitor

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

    Abre el proyecto

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

    <Warning>
      Asegúrate de usar el paquete `@ionic/cli` (no el paquete `ionic`, que está obsoleto). Si aparece un error relacionado con `--npm-client` durante la creación del proyecto, actualiza la CLI:

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

  <Step title="Instala el SDK de Auth0 para Angular y los plugins de Capacitor" stepNumber={2}>
    Instala el SDK de Auth0 para Angular junto con los complementos Browser y App de Capacitor:

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

    * [`@capacitor/browser`](https://capacitorjs.com/docs/apis/browser) - abre la página de Login de Auth0 en el navegador del sistema del dispositivo ([SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) en iOS, [Chrome Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs) en Android)
    * [`@capacitor/app`](https://capacitorjs.com/docs/apis/app) - gestiona los callbacks de enlaces profundos de Auth0 después de la autenticación

    <Info>
      El plugin Browser de Capacitor en iOS usa `SFSafariViewController`, que en iOS 11+ no comparte cookies con Safari. Esto significa que el [SSO](https://auth0.com/docs/sso) no funcionará en esos dispositivos. Si necesita SSO, use un plugin compatible que utilice [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession).
    </Info>
  </Step>

  <Step title="Configura tu aplicación en Auth0" stepNumber={3}>
    A continuación, debes crear una nueva aplicación en tu tenant de Auth0 y agregar las variables de entorno a tu proyecto.

    Tienes tres opciones para configurar tu aplicación de Auth0: usar la herramienta Quick Setup (recomendado), ejecutar un comando de CLI o configurar manualmente a través del Dashboard:

    <Tabs>
      <Tab title="Configuración rápida (recomendada)">
        Crea una aplicación nativa de Auth0 y copia el archivo de entorno rellenado previamente con los valores de configuración correctos.

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

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

        Después de crear la aplicación, actualiza **Allowed Callback URLs** y **Allowed Logout URLs** en la pestaña **Settings** del [Auth0 Dashboard](https://manage.auth0.com/dashboard/). Sustituye `YOUR_PACKAGE_ID` por el `appId` de `capacitor.config.ts` (valor predeterminado: `io.ionic.starter`):

        **Allowed Callback URLs** y **Allowed Logout URLs:**

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

      <Tab title="CLI">
        Ejecuta el siguiente comando en el directorio raíz de tu proyecto para crear una aplicación nativa de Auth0 y generar un archivo de entorno. Actualiza `PACKAGE_ID` si `capacitor.config.ts` usa un `appId` distinto:

        <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">
        Antes de empezar, crea un archivo de variables de entorno en tu proyecto

        ```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. Ve a [Auth0 Dashboard](https://manage.auth0.com/dashboard/)
        2. Haz clic en **Applications** > **Applications** > **Create Application**
        3. Ingresa un nombre para tu aplicación (por ejemplo, `Ionic Angular App`), selecciona **Native** como tipo de aplicación y haz clic en **Create**
        4. Ve a la pestaña **Settings** y toma nota de los valores de **dominio** e **ID de cliente**
        5. Reemplaza `YOUR_AUTH0_APP_DOMAIN` y `YOUR_AUTH0_APP_CLIENT_ID` en `src/environments/environment.ts` por tus valores

        <Info>
          A lo largo de este inicio rápido, `YOUR_PACKAGE_ID` hace referencia al campo `appId` de tu `capacitor.config.ts`. El valor predeterminado es `io.ionic.starter`. Consulta el [esquema de configuración de Capacitor](https://capacitorjs.com/docs/config#schema) para obtener más información.
        </Info>

        Configura las siguientes URL en la pestaña **Settings** (reemplaza `YOUR_PACKAGE_ID` por tu ID de paquete real):

        **Allowed Callback URLs:**

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

        **URL permitidas para el cierre de sesión:**

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

        **Orígenes permitidos:**

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

        <Info>
          **Allowed Callback URLs** son una medida de seguridad fundamental para garantizar que, después de la autenticación, los usuarios regresen de forma segura a tu aplicación. Sin una URL que coincida, el proceso de Login fallará.

          **Allowed Logout URLs** son esenciales para ofrecer una experiencia fluida al cerrar sesión. Sin una URL que coincida, los usuarios recibirán un error al cerrar sesión.

          **Allowed Origins** son necesarios para la autenticación silenciosa. El origen `capacitor://localhost` es para iOS y `http://localhost` es para Android.
        </Info>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Configura el módulo Auth0" stepNumber={4}>
    Con el archivo de entorno ya creado en el paso anterior, configure el módulo de Auth0 en su aplicación:

    ```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,
          },
        }),
      ],
    });
    ```

    La configuración de `provideAuth0` incluye:

    * `useRefreshTokens: true` — **obligatorio** para móviles. Las aplicaciones de Capacitor no pueden usar autenticación silenciosa basada en iframes, por lo que se usan tokens de actualización para renovar las sesiones.
    * `useRefreshTokensFallback: false` — **obligatorio** para móviles. Evita que el SDK recurra a la autenticación silenciosa basada en iframes, que no está disponible en aplicaciones nativas.
    * `authorizationParams.redirect_uri` — usa el esquema de URL personalizado de tu aplicación para redirigirla de nuevo tras la autenticación.

    <Warning>
      Para mantener la autenticación después de cerrar y volver a abrir la aplicación, quizá quieras establecer `cacheLocation` en `localstorage`, pero ten en cuenta [los riesgos de almacenar tokens en localstorage](https://auth0.com/docs/libraries/auth0-single-page-app-sdk#change-storage-options). Además, `localstorage` debe tratarse como **transitorio** en las aplicaciones de Capacitor. Consulta la [guía sobre almacenamiento en la documentación de Capacitor](https://capacitorjs.com/docs/guides/storage#why-cant-i-just-use-localstorage-or-indexeddb).
    </Warning>
  </Step>

  <Step title="Crear componentes de Login, Logout y perfil" stepNumber={5}>
    Crear los archivos de componentes

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

    Agrega el siguiente código a cada componente:

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

    Ahora actualiza el componente App para gestionar los callbacks de Auth0 y la página de inicio para utilizar tus componentes:

    <Tabs>
      <Tab title="Componente de la aplicación">
        Reemplaza el contenido de `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="Página de inicio">
        Reemplaza el contenido de `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>Inicio</ion-title>
              </ion-toolbar>
            </ion-header>

            <ion-content class="ion-padding">
              @if (auth.isAuthenticated$ | async) {
                <ion-card>
                  <ion-card-header>
                    <ion-card-title>¡Bienvenido!</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>Comenzar</ion-card-title>
                  </ion-card-header>
                  <ion-card-content>
                    <p>Inicia sesión en tu cuenta para empezar.</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>
      El callback del evento `appUrlOpen` en el componente App está encapsulado en `this.ngZone.run()`. Esto es **necesario** porque los callbacks del plugin de Capacitor se ejecutan fuera de la zona de Angular y, sin esto, Angular no detectará los cambios en el estado de autenticación después del inicio de sesión. Consulta [Using Angular with Capacitor](https://capacitorjs.com/docs/guides/angular) para obtener más información.
    </Info>
  </Step>

  <Step title="Ejecuta tu aplicación" stepNumber={6}>
    Primero, pruebe en el navegador:

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

    <Warning>
      Al ejecutar la aplicación en el navegador con `ionic serve`, la redirección mediante un esquema de URL personalizado no funcionará porque los navegadores no pueden gestionar este tipo de esquemas. Para hacer pruebas en el navegador, cambie temporalmente `redirect_uri` a `http://localhost:8100` en `src/main.ts` y añada `http://localhost:8100` a **Allowed Callback URLs** y **Allowed Logout URLs** de su aplicación de Auth0 en el Dashboard. Recuerde revertir este cambio antes de compilar para un entorno nativo.
    </Warning>

    Para ejecutarla en un dispositivo o simulador, primero agregue las plataformas nativas:

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

    Luego, compila, sincroniza y ejecuta:

    <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>
      Debes añadir las plataformas nativas con `npx cap add` antes de poder ejecutarlas. Esto solo debe hacerse una vez por plataforma. Asegúrate de que tu esquema de URL personalizado esté registrado (consulta Uso avanzado más abajo).
    </Info>
  </Step>
</Steps>

<Check>
  **Punto de control**

  Ahora deberías tener una página de Login de Auth0 totalmente funcional ejecutándose en [localhost](http://localhost:8100/)
</Check>

***

<div id="advanced-usage">
  ## Uso avanzado
</div>

<Accordion title="Uso del enfoque tradicional con NgModule">
  Si creó su proyecto con `--type=angular` (en lugar de `--type=angular-standalone`) o prefiere usar NgModules, configure el SDK con `AuthModule.forRoot`:

  ```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>
    Al usar NgModules, inyecte `AuthService` mediante el constructor (`constructor(public auth: AuthService)`) en lugar de `inject()`. En las plantillas, use `*ngIf="auth.user$ | async as user"` en lugar de la sintaxis de control de flujo `@if`. Los componentes deben declararse en el módulo en lugar de marcarse como `standalone: true`.
  </Note>
</Accordion>

<Accordion title="Configuración del esquema de URL personalizado">
  Para probar la autenticación en un dispositivo real, registre su esquema de URL personalizado para cada plataforma.

  ### iOS

  Registre su esquema de URL personalizado en `ios/App/App/Info.plist`:

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

  Reemplace `YOUR_PACKAGE_ID` por su `appId` de `capacitor.config.ts`. Para obtener más información, consulte [Defining a Custom URL Scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app).

  ### Android

  Agregue un filtro de intents a `android/app/src/main/AndroidManifest.xml` dentro de la etiqueta `<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>
  ```

  Reemplace `YOUR_PACKAGE_ID` por su `appId` de `capacitor.config.ts`. Para obtener más información, consulte [Create Deep Links to App Content](https://developer.android.com/training/app-links/deep-linking).

  ### Compilar y ejecutar en un dispositivo

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

  O, para Android:

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

<Accordion title="Protección de rutas con AuthGuard">
  Usa el guard funcional para proteger las rutas que requieren autenticación:

  ```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',
    },
  ];
  ```

  Cuando un usuario no autenticado navega a una ruta protegida, `authGuardFn` lo redirige automáticamente a la página de inicio de sesión de Auth0.
</Accordion>

<Accordion title="Llamadas a APIs protegidas">
  Configura el interceptor HTTP para adjuntar automáticamente tokens de acceso a las llamadas a la API:

  ```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])
      ),
    ],
  });
  ```

  Luego, haz llamadas a la API con `HttpClient` de Angular; el interceptor adjunta automáticamente el Bearer Token:

  ```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` determina qué endpoints de la API reciben tokens de acceso. Incluye el parámetro `audience` para solicitar un token de acceso para tu API. Reemplaza `YOUR_API_IDENTIFIER` por el identificador de [Auth0 Dashboard > APIs](https://manage.auth0.com/dashboard/).
  </Tip>
</Accordion>

<Accordion title="Problemas comunes y soluciones">
  ### Error de discrepancia en la URL de callback

  **Solución:** Verifica que la URL de callback en tu Auth0 Dashboard coincida exactamente con la URL construida en tu aplicación. Asegúrate de que `YOUR_PACKAGE_ID` coincida con el campo `appId` de tu `capacitor.config.ts`.

  ### La pantalla no se actualiza después del login

  **Solución:** Asegúrate de que el callback del evento `appUrlOpen` esté envuelto en `this.ngZone.run()`. De lo contrario, Angular no detectará los cambios de estado de `handleRedirectCallback`. Consulta [Uso de Angular con Capacitor](https://capacitorjs.com/docs/guides/angular).

  ### Error "PKCE not allowed"

  **Solución:**

  1. Ve a Auth0 Dashboard > Applications > Your Application
  2. Cambia **Application Type** a **Native**
  3. Establece **Token Endpoint Authentication Method** en `None`
  4. Guarda los cambios y vuelve a intentarlo

  ### SSO no funciona en iOS

  El plugin Browser de Capacitor usa `SFSafariViewController`, que no comparte cookies con Safari en iOS 11+. Si necesitas SSO, usa un plugin compatible que utilice [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession).

  ### Login funciona, pero el usuario sigue sin autenticarse después de reiniciar la aplicación

  Habilita `cacheLocation: 'localstorage'` en la configuración de `provideAuth0` para conservar los tokens entre reinicios de la aplicación. Ten en cuenta las [implicaciones de seguridad](https://auth0.com/docs/libraries/auth0-single-page-app-sdk#change-storage-options) y las [limitaciones de almacenamiento de Capacitor](https://capacitorjs.com/docs/guides/storage#why-cant-i-just-use-localstorage-or-indexeddb).

  ### Un Observable nunca se ejecuta

  Todos los métodos de `AuthService` devuelven Observables fríos. Debes llamar a `.subscribe()` para que se ejecuten. Si parece que `loginWithRedirect()` o `logout()` no hacen nada, verifica que `.subscribe()` esté encadenado al final.
</Accordion>

<div id="next-steps">
  ## Próximos pasos
</div>

Consulta los siguientes recursos para obtener más información:

* [Aplicación de ejemplo](https://github.com/auth0-samples/auth0-ionic-samples/tree/main/angular) — Una aplicación de ejemplo completa de Ionic con Angular integrada con Auth0
* [Documentación del SDK de Auth0 para Angular](https://github.com/auth0/auth0-angular) — Referencia completa del SDK y ejemplos
* [Auth0 Dashboard](https://manage.auth0.com/dashboard/) — Configura y administra tu inquilino de Auth0 y tus aplicaciones
* [Auth0 Marketplace](https://marketplace.auth0.com/) — Descubre integraciones que puedes habilitar para ampliar la funcionalidad de Auth0
