I'm trying to create a link to get redirected after login(/signin route) to a /dashboard/overview child route without any luck. I click the link and I get no errors nor any response. I can see the path on the bottom bar of the browser that is correct and If I enter the url manually I access the correct page, the route is /dashboard/overview. One thing I'm not sure if it has anything to do is that the route is AuthGuarded.
I tried both programatically after sign in redirect the user to a dashboard, I can even see the 'redirecting to dashboard' message on chrome console
onSignin(form: NgForm){
const email = form.value.email;
const password = form.value.password;
this.user = {email, password};
this.authServiceSubscription = this.authService.signinUser(this.user).subscribe(
(response) => {
//redirect to dashboard
const loginResultCode = response.login_result_code;
if (loginResultCode == "SUCCESS") {
console.log("Sponsor logged in");
this.authService.changeStatusToAuthenticated();
//redirect to dashboard
console.log('Redirecting to dashboard');
this.router.navigate(['/dashboard/overview']);
} else {
console.log("There were errors with the data");
//present errors to the user
this.errorMessage = "Los datos de autenticación son incorrectos. Intente nuevamente";
}
},
(error) => { console.log("Error Login", error); this.errorMessage = "Hubo un error interno, intente de nuevo mas tarde";}
);
}
And also creating a routerLink, but it doesn't work either, nothing happens, not even an error in the console:
<li><a style="cursor: pointer;" routerLink="/dashboard/overview">Go To Dashboard</a></li>
Here's my routing file:
const appRoutes: Routes = [
{ path: '', redirectTo: '/', pathMatch:'full'},
{ path: '', component: MainComponent },
{ path: 'signin', component:SigninComponent},
{ path: 'signup', component: SignupComponent},
{ path: 'dashboard', canActivate:[AuthGuard],component: DashboardComponent,
children: [
{ path: '', redirectTo:'/dashboard/overview', pathMatch: 'full'},
{ path: 'overview', component: OverviewCampaignsComponent },
{ path: 'active', component: ActiveCampaignsComponent},
{ path: 'history', component: HistoryCampaignsComponent}
] },
{ path: 'not-found', component: ErrorPageComponent },
{ path: '**', redirectTo: '/not-found' }
]
I even put a console.log on the ngOnInit of the Dashboard component to see if the component gets created, or in the overview component but I didn't have any luck, I couldn't see any messages on console when navigating programatically nor with routerLink. I did get the message when I accessed manually as I stated above. Any ideas? Thank you very much
EDIT: Apparently is a problem with the authguard I'm applying to the dashboard route, this is the AuthGuard file, could it be that it's not catching some error or maybe the returned values are not the ones that should be??:
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.isAuthenticated().map(isAuth => {
if (isAuth){
console.log("Auth Guard approves the access");
return true;
}
else {
console.log('AuthGuard Denying route, redirecting to signin');
this.router.navigate(['/signin']);
return false;
}
});
}
}
the isAuthenticated() method on the authService just returns an observable with the auth state of the user. I wonder if there's a race condition or something...cause that observable gets set initially by making an http async request....If I put a console.log in isAuthenticated method it gets logged on the console. If I put a console.log inside the map on the authguard function in or out the if it doesn't get logged so for some reason that code is not being executed....
auth.service.ts
import { Injectable, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Http, Response, RequestOptions, Headers } from '@angular/http';
import 'rxjs/add/operator/map';
import {Observable, Subject} from "rxjs/Rx";
@Injectable()
export class AuthService implements OnInit {
userIsAuthenticated = new Subject();
constructor(private router: Router, private http: Http) {
this.ngOnInit();
}
private getHeaders(){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
headers.append('Authorization','Bearer');
return headers;
}
ngOnInit(){
this.changeStatusToUnauthenticated();
//initial check with the server
let options = new RequestOptions({ headers: this.getHeaders(), withCredentials: true });
this.http.get('http://localhost:3000/api/sponsor/check/login',options)
.map(response => {
console.log("Execute this");
if (response.status === 200) {
console.log("execute also this");
this.changeStatusToAuthenticated();
return Observable.of(true);
}
}
).catch((err)=>{
//maybe add in the future if the code is 403 then send him to login otherwise send him elsewhere
if(err.status === 403){
console.log('Forbidden 403');
// If I want to redirect the user uncomment this line
// this.router.navigate(['/signin']);
}
this.changeStatusToUnauthenticated();
return Observable.of(false);
}).subscribe((isAuth)=>{
console.log("Initial refresh auth state ", isAuth);
});
}
isAuthenticated(): Observable<boolean> {
if(this.userIsAuthenticated){
//if I change this line for return Observable.of(true) it works
return this.userIsAuthenticated;
}else{
return Observable.of(false);
}
}
logout() {
console.log('logging out');
let options = new RequestOptions({ headers: this.getHeaders(), withCredentials: true });
return this.http.get('http://localhost:3000/api/sponsor/logout/', options).map(res=>res.json())
.subscribe(
(response) => {
//redirect to dashboard
const logoutResultCode = response.code;
if (logoutResultCode == "200") {
console.log("Sponsor logged out successfully");
//redirect to dashboard
this.changeStatusToUnauthenticated();
this.router.navigate(['/signin']);
}
},
(error) => {
console.log("Error Logout- Header", error);
//check for 403 if it's forbidden or a connection error
this.changeStatusToUnauthenticated();
this.router.navigate(['/signin']);}
);
}
signinUser(user) {
console.log("Logging user");
let options = new RequestOptions({ headers: this.getHeaders(), withCredentials: true });
return this.http.post('http://localhost:3000/api/sponsor/login/', user, options).map(
response => response.json());
}
registerUser(user) {
let options = new RequestOptions({ headers: this.getHeaders(), withCredentials: true });
return this.http.post('http://localhost:3000/api/sponsor/register/', user, options).map(
response => response.json());
}
changeStatusToUnauthenticated(){
this.userIsAuthenticated.next(false);
}
changeStatusToAuthenticated(){
this.userIsAuthenticated.next(true);
}
}
EDIT 2: I used Behaviour Subject instead of Subject on the authService cause it lets me get the last emitted value which is a pretty cool feature compared to the regular subject in which you have to subscribe, which sometimes is not enough. More details on my answer below.
In the end the problem was in what the authService returned on the isAuthenticated() method, I was not returning a resolved value apparently according to the logs, so the authguard got stucked before being able to resolve the route to the component. I solved my question by searching on the rxjs documentation. I found the BehaviorSubject https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/behaviorsubject.md
It lets you get the last value emmited so I can return an Observable.of(userIsAuthenticated.getValue()) and deliver it to the AuthGuard and it works perfectly now. I added the logic that if the last emitted value was false then I do a dummy request to decide if the user should be sent to the login screen. Then this goes hand in hand with changing the value of the BehaviourSubject to false if I get an http forbidden response on EVERY request I make to the server. This things combined will assure consistency between frontend and backend traditional sessions, avoiding the expired session on the backend and non-expired state on the frontend. Hope this helps someone. The code:
auth.service.ts
@Injectable()
export class AuthService implements OnInit {
userIsAuthenticated= new BehaviorSubject(null);
constructor(private router: Router, private http: Http) {
this.ngOnInit();
}
private getHeaders(){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
headers.append('Authorization','Bearer');
return headers;
}
ngOnInit() {
//initial check with the server
this.doAuthCheck();
}
doAuthCheck(): Observable<boolean> {
let options = new RequestOptions({ headers: this.getHeaders(), withCredentials: true });
return this.http.get('http://localhost:3000/api/check/login', options)
.map(response => {
if (response.status === 200) {
this.changeStatusToAuthenticated();
return Observable.of(true);
}
}
).catch((err) => {
//maybe add in the future if the code is 403 then send him to login otherwise send him elsewhere
if (err.status === 403) {
console.log('Forbidden 403');
// If I want to redirect the user uncomment this line
// this.router.navigate(['/signin']);
}
this.changeStatusToUnauthenticated();
return Observable.of(false);
});
}
isAuthenticated(): Observable<boolean> {
const isAuth = this.userIsAuthenticated.getValue();
if (isAuth) {
return Observable.of(isAuth);
} else {
return this.doAuthCheck();
}
}
logout() {
console.log('logging out');
let options = new RequestOptions({ headers: this.getHeaders(), withCredentials: true });
return this.http.get('http://localhost:3000/api/logout/', options).map(res => res.json())
.subscribe(
(response) => {
//redirect to dashboard
const logoutResultCode = response.code;
if (logoutResultCode == "200") {
console.log("logged out successfully");
//redirect to dashboard
this.changeStatusToUnauthenticated();
this.router.navigate(['/signin']);
}
},
(error) => {
console.log("Error Logout- Header", error);
//check for 403 if it's forbidden or a connection error
this.changeStatusToUnauthenticated();
this.router.navigate(['/signin']);
}
);
}
signinUser(user) {
console.log("Logging user");
let options = new RequestOptions({ headers: this.getHeaders(), withCredentials: true });
return this.http.post('http://localhost:3000/api/login/', user, options).map(
response => response.json());
}
registerUser(user) {
let options = new RequestOptions({ headers: this.getHeaders(), withCredentials: true });
return this.http.post('http://localhost:3000/api/register/', user, options).map(
response => response.json());
}
changeStatusToUnauthenticated() {
this.userIsAuthenticated.next(false);
}
changeStatusToAuthenticated() {
this.userIsAuthenticated.next(true);
}
}
auth-guard.service.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.isAuthenticated().map(isAuth => {
console.log("is Authenticated",isAuth);
if (isAuth){
console.log("Auth Guard approves the access");
return true;
}
else {
console.log('AuthGuard Denying route, redirecting to signin');
this.router.navigate(['/signin']);
return false;
}
});
}
}
routes file
const appRoutes: Routes = [
{ path: '', redirectTo: '/', pathMatch:'full'},
{ path: '', component: MainComponent },
{ path: 'signin', component:SigninComponent},
{ path: 'signup', component: SignupComponent},
{ path: 'dashboard', canActivate:[AuthGuard],component: DashboardComponent,
children: [
{ path: '', redirectTo:'/dashboard/overview', pathMatch: 'full'},
{ path: 'overview', component: OverviewCampaignsComponent },
{ path: 'active', component: ActiveCampaignsComponent},
{ path: 'history', component: HistoryCampaignsComponent}
] },
{ path: 'not-found', component: ErrorPageComponent },
{ path: '**', redirectTo: '/not-found' }
]