Skip to content

Render 404 page with CanActivate guard without aborting navigation #17004

Closed

Description

@simon-xiong-ca

I'm submitting a ... (check one with "x")

[ ] bug report => search github for a similar issue or PR before submitting
[X] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Background
When a user navigates to a route that requires authentication and they are not authenticated, there's no way to use a CanActivate guard to render a 404 component and still update the url to what the user was trying to access.

Current behavior
Let's say a user navigates to /protected and is not authenticated. Currently, in the CanActivate guard, in order to render my 404 component, I would use router.navigate(['404'], { skipLocationChange: true }); return false;. However, after rendering the 404 component, the url in the address bar is the previous url before the navigation since the navigation was aborted.

Expected behavior
In CanActivate guard, there should be a way to render the 404 route while still updating the url in the address bar to /protected.

Minimal reproduction of the problem with instructions

@Injectable()
export class LoggedInGuard implements CanActivate {
  constructor(private router: Router, private userService: UserService) {}

  canActivate() {
    if (this.userService.isLoggedIn) return true;

    this.router.navigate(['/404'], { skipLocationChange: true });
    return false;
  }
}

This code will render a 404 page, but not update the url. I would like it to render 404 and update the url in the address bar to what the user was trying to access.

What is the motivation / use case for changing the behavior?
Github does this, for example, if you go to https://github.com/simonxca/webpack-guide, it will render a 404 page even though the repo exists because you're not authenticated. The motivation is to prevent users from being able to distinguish between a protected from a non-existent page for security.

Please tell us about your environment:
Mac OSX Sierra, Node 6.10.3

  • Angular version: 4.1.X

  • Browser: all

  • Language: all

Activity

  1. changed the title [-]Render 404 page with CanActivate guard without aborting the navigation[/-] [+]Render 404 page with CanActivate guard without aborting navigation[/+] on May 24, 2017
  2. added
    featureLabel used to distinguish feature request from other issues
    on Dec 8, 2017
  3. nickwesselman commented on Jan 5, 2018

    @nickwesselman

    Would be helpful if this were possible in other guards as well -- for example I have a Resolve guard which does a server lookup for route data, which can result in a 404 or authentication error as well, and I'd like to be able to display a 404 or 401 error without aborting navigation.

      resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : Observable<MyData>
      {
        return this.myService.getRoute('/' + route.url.join(''))
          .map(myData => {
            if (myData) {
              return myData;
            }
            this.router.navigate(['/NotFound'], { skipLocationChange: true });
            return null;
          })
          .catch((error: MyError) => {
            if (error.status >= 400 && error.status < 500) {
              this.router.navigate(['/NotFound'], { skipLocationChange: true });
            } else {
              this.router.navigate(['/ServerError'], { skipLocationChange: true });
            }
            return Observable.of(null);
          });
      }
    
  4. added this to the Backlog milestone on Jan 23, 2018
  5. JaxonWright commented on Feb 26, 2018

    @JaxonWright

    Are there any updates on this? This seems like it would be a common request, but I cannot find much on it.

    The use case for me is to have pages only certain users can access. I already don't render the pages in the navigation bar when they don't have them, but when they manually navigate to the certain pages, I want it to act as if the page does not exist, even though it does. Right now, I got it showing the 404, but the URL just goes back to the root.

  6. JackMorrissey commented on Aug 20, 2018

    @JackMorrissey

    Imperfect workaround for anyone else landing here. You still get a small blink of the previous route since you're returning canActivate false and then the url is replaced.

    In my case this was good enough.

    • The components route (/no-access in this example) is never in the history due to skipLocationChange: true
    • Customers will see the intended route in the URL, but the contents of the /no-access component instead

    location.replaceState docs

    Guard

    import { Injectable } from '@angular/core';
    import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, CanActivate } from '@angular/router';
    
    @Injectable()
    export class MyAccessGuard implements CanActivate {
      constructor(
        private router: Router
      ) { }
    
    
      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        const hasAccess = hasAccess();
    
        if (hasAccess) {
          return true;
        }
    
        this.router.navigate(['/no-access'], {
            skipLocationChange: true, // minimal effect. see https://github.com/angular/angular/issues/17004
            queryParams: {
                url: state.url
            }
        });
        return false;
      }
    }

    Receiving Component for /no-access route

    import { Component, OnInit } from '@angular/core';
    import { Location } from '@angular/common';
    import { ActivatedRoute } from '@angular/router';
    
    @Component({
      selector: 'app-no-access',
      templateUrl: 'no-access.component.html',
      styleUrls: [
        'no-access.component.scss'
      ]
    })
    
    export class NoAccessComponent implements OnInit {
      constructor(
        private activatedRoute: ActivatedRoute,
        private location: Location
      ) { }
    
      ngOnInit() {
        const replaceUrl = this.activatedRoute.snapshot.queryParams['url'];
        if (replaceUrl) {
            this.location.replaceState(replaceUrl);
        }
      }
    }
  7. nhducseuit commented on Sep 20, 2018

    @nhducseuit

    I hope that this will addressed and released soon. We are looking for alternative solution but for now none seems to cover our needs.

  8. laurentgoudet commented on Dec 21, 2018

    @laurentgoudet

    @JackMorrissey's workaround does the trick, but using location.replaceState breaks back navigation as no new state had been pushed to the History stack since the guard returned true. Using location.go (which is basically just pushState) solves that. The URL still blinks when no route had been previously activated though.

  9. RomanOleksiuk commented on Feb 5, 2019

    @RomanOleksiuk

    Same here. Expected skipLocationChange:true to prevent the problem or router to fall into checking next route from the config.

  10. vahidvdn commented on Jul 13, 2019

    @vahidvdn

    Is there any update on this? Since it's a very common case for all projects.

  11. FirstVertex commented on Aug 7, 2019

    @FirstVertex

    My Brothers and my Sisters I have solution about this.

    When you said "false" to canActivate, you told the Router, "do not change the Url".

    If you need to keep that Url, canActivate is not the right tool for you.

    You can use a route Resolve. Inside the Resolve, return either your object you want, or undefined.

    Now let your component load. The one that requires the object from the Resolve.

    Inside that component you gonna have an onInit who is listening to ActivatedRoute.data.

    If you found your object you're looking for inside ActivatedRoute.data, your component can do it's thing.

    Now the best part, in your component who is already loaded, when you found out your ActivatedRoute.data didn't have your object because the Resolver set it to undefined. Now you can just use router.navigate to your 404 with skipLocationChange true.

    You permitted navigation, because you wanted the Url to stay. But you show the 404 route silently. Your component needs to know how to do that.

  12. laurentgoudet commented on Oct 15, 2019

    @laurentgoudet

    @JackMorrissey's workaround does the trick, but using location.replaceState breaks back navigation as no new state had been pushed to the History stack since the guard returned true. Using location.go (which is basically just pushState) solves that. The URL still blinks when no route had been previously activated though.

    It actually depends if urlUpdateStrategy: 'eager' is enabled or not: if it is, then replaceState is the way to go, as the new state (the missing route) would always have been activated.

    This remains an imperfect workaround though, as in some situations the internal 404 URL can show up in the address bar and/or blink, i.e. we still very much need the underlying Angular issue to be fixed.

  13. borjapazr commented on Nov 4, 2019

    @borjapazr
    Contributor

    This is related with #16981. A workarround could be the proposed by @ccondrup on this comment #16981 (comment).

  14. 44 remaining items

  15. added a commit that references this issue on Feb 28, 2024
    850c3b8
  16. added 2 commits that reference this issue on Mar 12, 2024
    8735af0
  17. angular-automatic-lock-bot commented on Apr 12, 2024

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  18. locked and limited conversation to collaborators on Apr 12, 2024
  19. reopened this on May 2, 2024
  20. unlocked this conversation on May 2, 2024
  21. added a commit that references this issue on May 30, 2024
    bb915ac
  22. added a commit that references this issue on Jun 13, 2024
    1d3a752
  23. angular-automatic-lock-bot commented on Jul 14, 2024

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  24. locked and limited conversation to collaborators on Jul 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: routerfeatureLabel used to distinguish feature request from other issuesfeature: under considerationFeature request for which voting has completed and the request is now under considerationfreq3: high

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions