1

I'm using the angular-oauth2-oidc library in combination with the Implicit Flow with keycloak.

There is no problem to login with this.oauthService.initImplicitFlow(); or logout with this.oauthService.logOut();

However, I'm wondering, is it possible to check if I already logged in at somewhere else? (different domain but use the same keycloak server)

I've searched through the docs of angular-oauth2-oidc but didn't find any.

I've tried this.oauthService.initImplicitFlowInternal(); but it seems works the same as the this.oauthService.initImplicitFlow();

UPDATE:

I've got the token using silentRefresh, however, it seems created another nav component (or maybe an entirely new page). The problem is, this 'new' page is hidden and I can only see the 'old' page. enter image description here

I created an id for the nav component using timestamp, as you can see, there are actually two ids.

The 'new' component got the token, so it's logged in (but the component is hidden!).

The 'old' component doesn't know the token, so it's still shows 'LOGIN' on navbar, unless I manually make a button and fetch the token from session storage on click.

UPDATE: That second component is iframe. I will explore more.

MaXon
  • 465
  • 2
  • 8
  • 17
  • Saw your edit. Good to hear you got one step further. However, I suggest putting the part after "UPDATE" in a new question (and removing it here), reference this question, and also add a minimal repro for the scenario. For one, that'll get a fresh audience for your second question (since the new question will be without answers, freshly on the home page), but also since I estimate it is really a separate issue. – Jeroen Aug 10 '18 at 07:07
  • @Jeroen thanks for the suggestion and your guidance, I've made it working! – MaXon Aug 10 '18 at 07:15
  • Hey @MaXon is there any way to know whether a component is authenticated or not using angular-oauth2-oidc? – anirudh talluri May 26 '23 at 11:08

2 Answers2

2

As I mentioned in my question, there is no problem to get the relative status of whether logged in or not. The problem is that iframe, because only that iframe knows what is going on (because it's the redirected url!). To let my main app responsive as well. I made some tweaks and 'hacks'.

Here is how I got it working. It will detect if I logged in at somewhere else and log me in automatically. It will also log me out in my app if I log out at another site.

* Detect if I logged in at somewhere else *
I created a method 'checkLoginState', and it's responsible for checking if there is a token in my session or check with the server is I already logged in.
The interval there is just to periodically check if the iframe got the token.

checkLoginState() {
    const claims = this.oauthService.getIdentityClaims();
    if (!claims) {
        if (this.ssoInterval) {
            // if we are waiting on response, return;
            return;
        }
        // try to get a token if already logged in somewhere else
        this.oauthService
            .loadDiscoveryDocument()
            .then(() => this.oauthService.tryLogin())
            .then(() => {
                if (!this.oauthService.hasValidAccessToken()) {
                    this.setupSSOInterval();
                    this.oauthService.silentRefresh().catch(err => {
                        // this will throws a time_out error as we don't have a 
valid token to refresh
                        // console.error('refresh error', err);
                        this.clearSSOInterval();
                    });
                }
            })
            .catch(e => {
                // console.log(e);
                // if not logged in anywhere, it will throw a token error.
                this.clearSSOInterval();
            });
        return;
    }
    if (this.oauthService.getIdTokenExpiration() < new Date().getTime()) {
        this.userService.removeToken();
        return this.logout();
    }
    this.isLoggedIn = true;
    this.userService.authenticateWithNID(claims['email']);
}
private setupSSOInterval() {
    this.ssoInterval = setInterval(() => {
        if (this.isLoggedIn) {
            clearInterval(this.ssoInterval);
        } else {
            this.checkLoginState();
        }
    }, 1000);
}
private clearSSOInterval() {
    if (this.ssoInterval) {
        clearInterval(this.ssoInterval);
    }
}

and call this method in ngOnInit();

* Detect if I logged out at somewhere else *
To detect if I'm logged out, first set the sessionChecksEnabled to true (as @Jeroen said). Then listen to the session storage change. (because the iframe will update the session storage)

ngOnInit() {
    window.addEventListener(
        'storage',
        this.storageEventListener.bind(this)
    );
    // this is for handle the normal redirect when we login from this app
    this.oauthService.events.subscribe(({ type }: OAuthEvent) => {
        switch (type) {
            case 'token_received': {
                this.checkLoginState();
            }
        }
    });
    this.checkLoginState();
}
private storageEventListener(event: StorageEvent) {
    // if there is a session change and claims is missing, means I am no longer logged in
    if (event.storageArea === sessionStorage) {
        if (!sessionStorage.getItem('id_token_claims_obj')) {
            this.isLoggedIn = false;
        }
    }
}

REMEMBER to delete the this.oauthService.loadDiscoveryDocumentAndTryLogin(); in your constructor method. It will throw some errors if you log out at other sites. (you can catch the error if you want, but the same method has been called inside checkloginState()).

I just realised that I could use the session storage listener for login check as well (replace the interval). But I will leave it for now.

MaXon
  • 465
  • 2
  • 8
  • 17
1

I suggest setting up silent refreshes if you haven't already, then use this.oauthService.silentRefresh().

Technically, that doesn't answer your question straight up, because it doesn't "check" if you're logged in, but instead just straight up logs you into the application. And if it would fail (you're not yet logged in) it would reject the promise returned from silentRefresh().

For reference, you can check my sample repo that has this login flow which supports silently logging a user in even if the login happened elsewhere. This is the nitty gritty:

// 0. LOAD CONFIG:
// First we have to check to see how the IdServer is
// currently configured:
this.authService.loadDiscoveryDocument()

  // 1. HASH LOGIN:
  // Try to log in via hash fragment after redirect back
  // from IdServer from initImplicitFlow:
  .then(() => this.authService.tryLogin())

  .then(() => {
    if (!this.authService.hasValidAccessToken()) {

      // 2. SILENT LOGIN:
      // Try to log in via silent refresh because the IdServer
      // might have a cookie to remember the user, so we can
      // prevent doing a redirect:
      this.authService.silentRefresh()
        .catch(result => {
          // Subset of situations from https://openid.net/specs/openid-connect-core-1_0.html#AuthError
          // Only the ones where it's reasonably sure that sending the
          // user to the IdServer will help.
          const errorResponsesRequiringUserInteraction = [
            'interaction_required',
            'login_required',
            'account_selection_required',
            'consent_required',
          ];

          if (result && result.reason && errorResponsesRequiringUserInteraction.indexOf(result.reason.error) >= 0) {

            // 3. ASK FOR LOGIN:
            // At this point we know for sure that we have to ask the
            // user to log in, so we redirect them to the IdServer to
            // enter credentials:
            this.authService.initImplicitFlow();
          }
        });
    }
});

The straight up answer to you question though is likely that you cannot get a push notification ("check") when you are logged in elsewhere, because to do that you need to know who you are (log in) to establish session checks. (The sessionChecksEnabled config however does help you "check" when you get logged out elsewhere, see this recent question)

Jeroen
  • 60,696
  • 40
  • 206
  • 339
  • Thanks for your reply! I did get it working after tweaking your snippet. However, there is a weird behaviour. After I call silentRefresh, I do get the token, at the same time, I set the login status to logged in. The problem is that this does not trigger the dom refresh. (The dom still shows LOGIN instead of LOGOUT). Have you ever experience this problem? @Jeroen – MaXon Aug 10 '18 at 00:58
  • @MaXon Good to hear it works for you. I recognize the symptom you describe, but it was almost always a bug in my own code around rxjs or Angular usage. If you clone the repo I linkend you'll find that DOM refreshes do work. So I suggest creating a minimal repro for the issue and asking a follow up question. – Jeroen Aug 10 '18 at 05:51