0

I have an angular app (ver. 20.2, zoneless, standalone), running locally for now at https://localhost:4200. The app uses Angular Auth OIDC Client.

As per docs, I use autoLoginPartialRoutesGuard for app's routes:

export const routes: Routes = [
   {
      path: "register",
      component: IlgRegisterUser
   },
   {
      path: "login",
      component: IlgLogin
   },
   {
      path: "users",
      component: IlgUsersList,
      canActivate: [autoLoginPartialRoutesGuard]
   },
   {
      path: "unauthorized",
      component: IlgUnauthorized
   }
];

The app.config.ts looks like this:

export const appConfig: ApplicationConfig = {
   providers: [
      provideBrowserGlobalErrorListeners(),
      provideZonelessChangeDetection(),

      provideHttpClient(withInterceptors([authInterceptor()])),
      provideAuth(authConfig, withAppInitializerAuthCheck()),

      provideRouter(routes) // , withEnabledBlockingInitialNavigation()
   ]
};

The auth for Azure AD B2C is the following:

export const authConfig: PassedInitialConfig = {
   config: {
      authority: "https://my-domain.b2clogin.com/{TenantId}/v2.0/",
      authWellknownEndpointUrl: "https://my-domain.b2clogin.com/my-domain.onmicrosoft.com/B2C_1_si/v2.0/.well-known/openid-configuration",

      redirectUrl: "https://localhost:4200",
      postLogoutRedirectUri: window.location.origin,

      triggerAuthorizationResultEvent: false,
      unauthorizedRoute: "/unauthorized",

      clientId: "{ClientId Guid}",
      scope: "openid offline_access",
      responseType: "code",
      silentRenew: true,
      useRefreshToken: true,
      ignoreNonceAfterRefresh: true,
      maxIdTokenIatOffsetAllowedInSeconds: 600,
      issValidationOff: false, // this needs to be true if using a common endpoint in Azure
      autoUserInfo: false,
      logLevel: LogLevel.Debug,
      customParamsAuthRequest: {
         prompt: "select_account" // login, consent
      }
   }
};

The app.component.ts uses checkAuth() to check authentication status (even though I use provideAuth(authConfig, withAppInitializerAuthCheck()) in app.config.ts):

@Component({
   selector: "ilg-root",
   imports: [AsyncPipe, JsonPipe, RouterOutlet, MatSidenavModule, MatButtonModule, MatIconModule, MatDividerModule, IlgNavBar],
   templateUrl: "./app.html",
   styleUrl: "./app.scss"
})
export class App implements OnInit {
   ngOnInit(): void {
      this.oidcSecurityService.checkAuth().subscribe(({ isAuthenticated, userData, accessToken, idToken, configId }) => {
         console.log(`isAuthenticated: ${isAuthenticated}`);
      });
   }

   protected readonly oidcSecurityService = inject(OidcSecurityService);
   protected authenticated = this.oidcSecurityService.authenticated;
   protected userData$ = this.oidcSecurityService.userData$;
}

The app.component.html shows the auth info:

      <p>User is {{ oidcSecurityService.authenticated().isAuthenticated ? "Authenticated" : "NOT authenticated" }}</p>
      <br />
      UserData
      <pre>{{ userData$ | async | json }}</pre>

When I open the /users page, auth kicks in and I see the Azure AD B2C log in page. I enter my user/pass and then I get back to the https://localhost:4200, which shows me now:

  1. User is Authenticated
  2. The whole bunch of claims from my user's token

The problem is if I go to the /users/ page again, being authenticated, it redirects me to Azure B2C Auth login page! And whatever I do it works this way only!

2 Answers 2

0

The problem on being redirected to the login screen even after successful login when visiting /users page can be caused by the router logic, the router logic needs to check using maybe conditionals, if the session is valid already per OIDC Client object from Angular, so instead of redirecting to the /users or when redirecting to the /users route, some state check function must be run on route load /users to determine whether you or any user already logged in successfully:

You can use localStorage or Cookie based verification or keep it within the authentication logic of the OIDC client:

On successful log in:

import { OidcSecurityService } from 'angular-auth-oidc-client';

constructor(private oidcSecurityService: OidcSecurityService) {
  this.oidcSecurityService.checkAuth().subscribe(({ isAuthenticated }) => {
    if (isAuthenticated) {
      localStorage.setItem('session_valid', 'true');
    }
  });
}

Then when routing to /users page:

import { CanActivate } from '@angular/router';

@Injectable({ providedIn: 'root' })
export class autoLoginPartialRoutesGuard implements CanActivate {
  constructor(private router: Router) {}

  canActivate(): boolean {
    const sessionValid = localStorage.getItem('session_valid') === 'true';
    if (!sessionValid) {
      this.router.navigate(['/login']);
      return false;
    }
    return true;
  }
}

Which will read the value accordingly and decide whether to route to /users or to go back to Login.

Sign up to request clarification or add additional context in comments.

Comments

0

I ended up using Microsoft's own MSAL.js. They have examples and their code just works. Here's part of my signal store for Auth:

export const AuthStore = signalStore(
   { providedIn: "root" },
   withState(initialState),

   withMethods(
      (
         store,
         msalGuardConfig: MsalGuardConfiguration = inject<MsalGuardConfiguration>(MSAL_GUARD_CONFIG),
         authService: MsalService = inject(MsalService)
      ) => ({
         setAuth(account: AccountInfo | null): void {
            patchState(store, {
               account,
               idToken: account?.idToken ?? null,
               idTokenClaims: account?.idTokenClaims as IdTokenClaims | null
            });
         },

         clearAuth(): void {
            patchState(store, initialState);
         },

         loginRedirect(): void {
            if (msalGuardConfig.authRequest) {
               authService.loginRedirect({
                  ...msalGuardConfig.authRequest
               } as RedirectRequest);
            } else {
               authService.loginRedirect({ scopes: ["openid", "profile", "email"] });
            }
         },

         loginPopup(): void {
            if (msalGuardConfig.authRequest) {
               authService.loginPopup({ ...msalGuardConfig.authRequest } as PopupRequest).subscribe((response: AuthenticationResult) => {
                  authService.instance.setActiveAccount(response.account);
               });
            } else {
               authService.loginPopup().subscribe((response: AuthenticationResult) => {
                  authService.instance.setActiveAccount(response.account);
               });
            }
         },

         logout(popup?: boolean): void {
            if (popup) {
               authService.logoutPopup({
                  mainWindowRedirectUri: "/"
               });
            } else {
               authService.logoutRedirect();
            }

            this.clearAuth();
         }
      })
   )
);

MSAL.js is supported and actively maintained. Unlike other libs. Some turn to garbage, some introduce paid licenses.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.