Keycloak-angular: Login call in guards destroys last history entry

Created on 30 Jan 2020  路  11Comments  路  Source: mauriciovigolo/keycloak-angular

Bug Report or Feature Request (mark with an x)

- [x] bug report -> please search for issues before submitting
- [ ] feature request

Versions.

keycloak-angular: 7.0.1,
keycloak-js: 8.0.1,
angular: 8.2.14

Repro steps.

Add an AuthGuard as described in the docs to a route /routeWhichRequiresLogin, so that the user is redirected to the keycloak login page if he is not logged in and tries to access the guarded route.
Add a <a routerLink=['/routeWhichRequiresLogin']> on a different view used in route routeWithoutLogin.

When the user is not logged and wants to navigate from routeWithoutLogin to /routeWhichRequiresLogin by using the anchor for example, then he lands on the keycloak login page (all good so far). When the user clicks on the browser back button he does NOT land on the route he came from (i.e. /routeWithoutLogin) but on the page he was before that. In case he opened /routeWithoutLogin he will land on an empty window.

https://github.com/keycloak/keycloak/pull/5485

Desired functionality.

When the user is not logged and wants to navigate from routeWithoutLogin to /routeWhichRequiresLogin, then he lands on the keycloak login page. When the user clicks on the browser back button he should land on the route he came from (i.e. /routeWithoutLogin).

Problem ist that keycloak-js wrapper REPLACES the current location with the login page instead of adding a new one. This change was introduced in this PR #5485. In most cases this is the desired behaviour but for the AuthGuard it makes little sense as the user comes from a place where no login was required and he might to decide to navigate back there instead of logging in. The abstract KeycloakAuthGuard should make sure to push the current history state to the history, something like window.history.pushState(window.history.state, this.document.title, this.document.location.href)

Question

Most helpful comment

I opened a ticket about this issue on Keycloak bugtracker, if anyone is interested in participating:
https://issues.redhat.com/browse/KEYCLOAK-15463

All 11 comments

Or, maybe a cleaner solution would be to make the changed behavior from keycloak/keycloak#5485 configurable in upstream, such that the user (KeycloakAuthGuard of the keycloak-angular adapter in this case here) can tell the keycloak-js adapter NOT to replace the history state for this specific case?
This would avoid workarounds in keycloak-angular, but is probably harder to achieve.

The case describe above is special because angular evaluates guards before actually navigating to the target page, so when the guard is rejected and the redirect to the keycloak login page is executed, the target page (guarded by the KeycloakAuthGuard) is not yet added to the browser history - which means keycloak-js effectively replaces the _previous_ history entry.

Is there a safe workaround? I currently do this in my AuthGuard:

isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
    return new Promise((resolve, reject) => {
  if (!this.authenticated) {
      this.keycloakAngular.login({redirectUri: `${location.origin}/${state.url.toString()}`}).catch(e => console.error(e));
    return reject(false);
  }
...

That works on most of the cases, but messes with the history and its very confusing if you use the back button a few times.

The suggestion from @jebner above works fine so far: adding window.history.pushState(window.history.state, this.document.title, this.document.location.href); before triggering the this.keycloakAngular.login call will ensure the current URL is added to the history stack (duplicated in fact) - before it is replaced by the keycloak-js adapter again (in the login call)...

This works sufficiently well, but is of course far from ideal 馃槃
KUDOS to @jebner who implemented the workaround.

We could probably provide a full working sample if needed 馃

We have since updated the documentation for this in our README to resolve this specific problem. It's recommend to pass the redirect URI in explicitly as follows:

public isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
  // Force the user to log in if currently unauthenticated.
  if (!this.authenticated) {
    await this.keycloak.login({ redirectUri: window.location.origin + state.url });
  }
}

Hi @jonkoops,

I don't think the redirectUri solves this as https://github.com/keycloak/keycloak/blob/master/adapters/oidc/js/src/main/resources/keycloak.js#L1340 is explicitly calling replace():

The replace() method of the Location interface replaces the current resource with the one at the provided URL. The difference from the assign() method is that after using replace() the current page will not be saved in session History , meaning the user won't be able to use the back button to navigate to it.

So it works as designed. But I am not sure why the keycloak-js library is using replace instead of assign:

The Location.assign() method causes the window to load and display the document at the URL specified. After the navigation occurs, the user can navigate back to the page that called Location.assign() by pressing the "back" button.

Anyway, I don't think this can be solved by angular-keycloak.

I opened a ticket about this issue on Keycloak bugtracker, if anyone is interested in participating:
https://issues.redhat.com/browse/KEYCLOAK-15463

Yeah, I think this is best resolved in keycloak-js I am not familiar with the reasoning why replace() is used here. I would recommend to open up a PR and get some of their developers involved.

@jonkoops
I'm extremely doubtful about a PR of mine being took in account...
Anyway, the reason for using replace() is, from original ticket:
>

Log in to an app that uses the Keycloak JS adapter
From an external site, follow a link that points to this app
Once the page loads (after the app has redirected to the Keycloak login page and back), click the browser's "back" button

Expected behavior: the user is taken back to the external site
Actual behavior: the user is taken "back" to the page they are currently on

I'm not sure about that, but I think that the original issue would be automatically solved using silent check SSO.

In my experience the authors of Keycloak are pretty responsive, they also have various mailing lists where developers are active which is a good place to start.

I'm not sure about that, but I think that the original issue would be automatically solved using silent check SSO.

Yes, it should be resolved here if the user is already signed in.

Adding my voice here... Why can't user's back out of keycloak login with history?

@mattishii
Probably it would be more useful commenting on the ticket on redhat bugtracker.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

livthomas picture livthomas  路  4Comments

nfriend picture nfriend  路  4Comments

Wassim24 picture Wassim24  路  4Comments

ayoubnejm picture ayoubnejm  路  3Comments

unib1t picture unib1t  路  4Comments