1

I am struggling a bit on a particular use case with Angular2 and the router.

Let's say we have 3 routes. Home, List and Profile.

"Profile" is a guarded route : user has to be authenticated to access this route.

If user is on "Profile" page and logs out, I want to be able to detect that he is not allowed anymore to be on the current page, and redirect him to "Home" page.

However if he is on "List" page and logs out, I don't want to do anything (user is allowed to stay here as it's not a guarded route).

Do someone know how I can achieve that, assuming that I have many routes and I want to avoid to put this "user-is-allowed" logic in each component ?

Cœur
  • 37,241
  • 25
  • 195
  • 267

2 Answers2

3

Summary

In my case I like to give my user's access to the Guarded routes by checking the validation of a token I save to local storage. So if I log them out I remove the token along with any data I currently have in local storage. You can use this function in each route.

  public logout() {
    localStorage.removeItem('profile');
    localStorage.removeItem('access_token');
    this.userProfile = undefined;
    this.router.navigateByUrl('/home');
  };

I create a service for authentication. You could create two different services, or two different functions. Really you have a lot of options. Here is one option.

Solution

To logout and redirect,

  public logout() {
    localStorage.removeItem('profile');
    localStorage.removeItem('access_token');
    this.userProfile = undefined;
    this.router.navigateByUrl('/home');
  };

You could use this function in each one of your components. Or pages. Basically redirect the route if the user is on the profile page. but if the user is not on a page or route that needs to be redirected then remove the

this.router.navigateByUrl('/home');

From the function so the user is not redirected.

So you could have two services

    public.service.ts
    @Injectable()
export class Public {
     public logout() {
        localStorage.removeItem('profile');
        localStorage.removeItem('access_token');
        this.userProfile = undefined;
      };

Then in your pages that you want to log the user out but leave them on the same page use this service

export class SomeComponent {
       constructor( private router: Router, private public: Public  ) { }
}

So when the use the logout function it wont redirect.

Then to redirect when the user logs out add this service like this,

       secure.service.ts
    @Injectable()
export class Secure {
     public logout() {
        localStorage.removeItem('profile');
        localStorage.removeItem('access_token');
        this.userProfile = undefined;
        this.router.navigateByUrl('/home');
      };

And of course any component you include the service too you call the correct logout function in your html like this,

<a class="myClass" href="#"(click)="public.logout()">Logout</a>

or

<a class="myClass" href="#" (click)="secure.logout()">Logout</a>
wuno
  • 9,547
  • 19
  • 96
  • 180
  • No problem. Did that make sense? and help? – wuno Dec 19 '16 at 17:00
  • In fact I came up with something similar but wanted to avoid putting the logic in each component, which can be a bit cumbersome if you have plenty. I guess there's no other alternative :/ – Jonathan Trang Dec 19 '16 at 17:05
  • You do not need to put it in each component. You can make a secure template and a public template. Then put the logout button in the template. Then include the pages inside the template. So any page that needs to be in the secure template will be a child of secure.component.ts I have answered many questions that look for a setup like this. I explain how to do this here http://stackoverflow.com/a/41219564/2218253 – wuno Dec 19 '16 at 17:07
  • If this answers your question please accept my answer as correct with the check mark by my answer. If not then please let me know what is not clear and I will try and explain to continue helping you. – wuno Dec 19 '16 at 17:08
  • I understand your approach. But it looks like it works only if the logout button is in the component template (secure or public). In my case, the logout link is in a NavComponent which is a top level component, and is common to all child components (secure or public). – Jonathan Trang Dec 19 '16 at 17:16
  • Yes. SO what I am saying is create two nav components. One for secure and one for public. Then make everything else children of those. So basically change nav component to secure and public component. You can then add a footer to each one if you wanted and then just fill the body with whatever content you want. Based on if it is a secure page or a public page. – wuno Dec 19 '16 at 17:18
  • Hi wuno. Sorry for the delay, I had to find some time to implement this properly. Your solution works perfectly, thanks for your help and patience ! – Jonathan Trang Jan 01 '17 at 18:05
  • No problem! Let me know if you need anything else – wuno Jan 01 '17 at 20:35
  • Hi wuno, I've just implemented a solution like you describe with the templates but now when I'm on a public page (but logged in so on secured template) and pressing the logout button, I would expect that it will switch to public template but probably because the url was not changed (because I don't need to redirect from public page). Am I missing something in your solution or should I refresh / reset the router? if yes, how? – DiSol May 04 '17 at 21:31
  • I always serve public pages on a public template. and secure pages on secure templates. So if you are showing a public page on a secure template it sounds like the easiest solution would be to redirect someone back to a public page. this.router.navigateByUrl('/public-page'); – wuno May 04 '17 at 21:35
  • Yes, I do understand that redirect is the easiest solution but is not correct UX wise because I'm refreshing the page while it is not needed... This was actually the real reason I implemented this approach. :) – DiSol May 04 '17 at 22:20
  • The only way I can think of to do this would be to update all of the components on the page when someone logs out. So anything that needs to be changed when someone logs out you would have to use an observable to update. So basically it would be waiting and watching for a certain action to happen, then when it does the observable updates based on the new information. – wuno May 05 '17 at 00:45
  • What does "update all component on the page" means? How can I update a component? – DiSol May 05 '17 at 07:17
  • I actually understood now that your solution is perfect and it is working as expected - I found that I was not refreshing some variable that is responsible for the switch between the buttons. Thank you for your help! – DiSol May 05 '17 at 07:40
  • No problem. Glad I could help! – wuno May 05 '17 at 10:58
0

This can be achieved with a single (menu) component which serves all routes, public and private where the private routes are only made visible when signed in.

This same component also includes a sign out button, only visible if signed in. The handler for this signs out and determines whether the current route requires sign in. If it does, redirect to home page, if not do nothing.

The guarded private routes will probably be defined in app.module with canActivate defined, so these are the paths that require sign in.

app.module.ts

const appRoutes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'sign-in', component: SignInComponent },
  { path: 'private', loadChildren: './private/private.module#PrivateModule', 
canActivate: [LoginRouteGuardService] }
];

menu.component.ts

signOut() {
  // your sign out method
  // then check if redirect necessary
  if (this.router.url.includes('/private/')) {
    this.router.navigateByUrl('/');
  }
}

Variation on the above provided here: https://stackblitz.com/edit/free-vote-redirect-on-sign-out

Alex Cooper
  • 475
  • 3
  • 7
  • 18