Skip to content

Double login redirect on ionic + angular workflow #668

Open
@mrentmeister-tt

Description

@mrentmeister-tt

Checklist

Description

I followed the instructions with how to setup Auth0 in a capacitor + Angular app listed here: https://auth0.com/docs/quickstart/native/ionic-angular. I then added the AuthGuard to my root path, because I wanted all of my main routes to be protected. After completing a successful login, the app redirects a second time to the login screen, and then immediately redirects back (or makes you select your organization again if doing that flow).

Expected behavor: The app should not need to authenticate you twice in a row.

Reproduction

  1. Setup the sample capacitor + angular demo app: https://auth0.com/docs/quickstart/native/ionic-angular
  2. Setup your main route to be protected by the AuthGuard
// app.routes.ts
export const APP_ROUTES: Route[] = [
  {
    path: 'login-failed',
    component: LoginFailedComponent
  },
  {
    path: 'logout',
    component: LogoutComponent
  },
  {
    path: '',
    runGuardsAndResolvers: 'always',
    canActivate: [AuthGuard],
    children: [
      {
        path: 'tenant-select',
        component: TenantSelectComponent
      },
      {
        path: 'usage',
        component: UsageComponent
      },
      // ... More routes
      {
        path: '',
        pathMatch: 'full',
        redirectTo: 'tenant-select'
      }
    ]
  },
  {
    path: '**',
    redirectTo: 'tenant-select'
  }
];
  1. Launch the app and login. Once logged in, you will be redirect back to the login page again.

Additional context

Looking through the auth0 code, this is happening because of the following sequence of events:

  1. The app launches, goes to '/' route. This triggers the AuthGuard to call loginWithRedirect, because it's not authenticated: https://github.com/auth0/auth0-angular/blob/main/projects/auth0-angular/src/lib/auth.guard.ts#L45
  2. After successfully logging in, the appUrlOpen code executes handleRedirectCallback:
ngOnInit(): void {
    // Use Capacitor's App plugin to subscribe to the `appUrlOpen` event
    App.addListener('appUrlOpen', ({ url }) => {
      // Must run inside an NgZone for Angular to pick up the changes
      // https://capacitorjs.com/docs/guides/angular
      ngZone.run(() => {
        if (url?.startsWith(callbackUri)) {
          // If the URL is an authentication callback URL..
          if (
            url.includes('state=') &&
            (url.includes('error=') || url.includes('code='))
          ) {
            // Call handleRedirectCallback and close the browser
            this.auth
              .handleRedirectCallback(url)
              .pipe(mergeMap(() => Browser.close()))
              .subscribe();
          } else {
            Browser.close();
          }
        }
      });
    });
  }
  1. The code in handleRedirectCallback calls this.authState.refresh(); (this ASYNCHRONOUSLY updates the state), and then continues to SYNCHRONOUSLY redirect back to '/'. Nothing waits for the state to be updated before redirecting to '/'. https://github.com/auth0/auth0-angular/blob/main/projects/auth0-angular/src/lib/auth.service.ts#L305
  2. The redirect to '/' triggers the AuthGuard to execute again, checking the isAuthenticated$ observable (which is still false), and triggers the loginWithRedirect AGAIN.

As best as I can tell from running the code, since isAuthenticated$ uses shareReplay(1), the AuthGuard is getting the replayed value of false BEFORE the isAuthenticatedTrigger$ can be re-run from the authState.refresh() call.

auth0-angular version

2.2.3

Angular version

19.1.3

Which browsers have you tested in?

Safari, Edge

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis points to a verified bug in the code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions