I'm creating an authentication system in angular2 with the idea that if a user that is not authenticated tries to navigated to a "protected" url, the system will redirect the user to the login page putting in the url a query param called "next" that will help the login system redirect the user back to where he wanted to be in the first place.
login?next=my-redirect-url
To protect my components, I'm using the decorator @CanActivate(isUserAuthenticated)
in all of them. The isUserAuthenticated
function is something as follows:
function isUserAuthenticated(
prevInstr: ComponentInstruction,
nextInstr: ComponentInstruction
): boolean {
const authService = injector.get(AuthService);
const router = injector.get(Router);
if(authService.isLoggedIn()) {
return true;
} else {
router.navigate(["/Login", {next: nextInstr.urlPath}]);
return false;
}
}
This approach is not working because the urlPath
property of the nextInstr
is not showing the "complete" url (it lacks query params for example).
Is there a way to build the complete url from a ComponentInstruction
instance like nextInstr
?
Yes there is a way:
let url = router.generate(['./Login', {next: nextInstr.urlPath}]).toRootUrl();
Lets say following structure example depending on routeconfig:
login?next=my-redirect-url
And then you use navigateByUrl to navigate to following url
router.navigateByUrl('/' + url);
I have tested it with my example and result you can see on image:
let instruction = router.generate(['./Country', {country: 'de', a: 1, b: 2}]);
console.log(instruction, instruction.toRootUrl());
Another way (WITHOUT using query parameters using @angular/router 3.0.0) to achieve the same requirement of redirecting to the original requested resource after authentication is to use RouterStateSnapshot.url
, which is a string that contains the url of the resource the user requested. Before redirecting user back to your login form after a failed authentication attempt, inside of the CanActivate
hook, get the requested url from RouterStateSnapshot.url
and store it in a variable that is accessible to your login function. When the user logs in successfully simply redirect to the stored URL. Here's my example:
//GaurdService - implements CanActivate hook for the protected route
import { Injectable } from '@angular/core';
import { CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class GuardService implements CanActivate {
constructor( private router: Router, private authService: AuthService ) {}
canActivate(state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authService.loggedIn()) { return true; }
this.authService.redirectUrl = url; // set url in authService here
this.router.navigate([ '/login' ]); // then ask user to login
return false;
}
}
My AuthService (below), which performs the login, will redirect user to the originally requested resource on successful login.
import { Injectable, Inject } from '@angular/core';
import { tokenNotExpired } from 'angular2-jwt';
import { Router } from '@angular/router';
import { Headers, Http, Response, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs';
import './../rxjs-operators';
const API_URL: string = '';
@Injectable()
export class AuthService {
public redirectUrl: string = ''; //Here is where the requested url is stored
constructor( @Inject('API_URL') private apiURL: string, private router: Router, private http: Http ) {}
public loggedIn(): boolean {
return tokenNotExpired('token');
}
public authenticate(username: string, password: string) {
let body: string = JSON.stringify({ un: username, pw: password});
let headers: Headers = new Headers({ 'Content-Type': 'application/json' });
let options: RequestOptions = new RequestOptions({ headers: headers });
return this.http.post(this.apiURL + '/authenticate', body, options)
.map(res => res.json())
.subscribe(res => {
localStorage.setItem('token',res.token);
this.redirect(); // Redirect to the stored url after user is logged in
});
.catch(this.handleError);
}
private redirect(): void {
this.router.navigate([ this.redirectUrl ]); //use the stored url here
}
}
This is how your application can remember the originally requested resource WITHOUT using query parameters.
For more details please see the example guide at angular.io starting at the "GUARD THE ADMIN FEATURE" section:
https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard
Hope this helps someone.