Angular 2 AuthGuard Service with redirect? - javascript

I have an application that I am building that implements CanActivate on the dashboard route. It works fine except on page reload, I check a flag in the user service to see if a user is logged in or not. By default this flag is false which kicks the user out to login. Also on page reload I am trying to fetch user data with a token in localStorage, if fetch is successful, I want them to be able to stay on the dashboard. The problem is that I am seeing a glimpse of login and having to manually redirect them to the dashboard. Is there any way to fix this to where the authGuard doesn't do anything until after it checks the API? Code is here: https://github.com/judsonmusic/tfl
dashboard:
import { Component, ViewChild } from '#angular/core';
import { LoginComponent } from "../login.component";
import { UserService } from "../user.service";
import { SimpleChartComponent } from "../charts/simpleChart.component";
import { AppleChartComponent } from "../charts/appleChart.component";
import { BarChartComponent } from "../charts/barChart.component";
import { DonutChartComponent } from "../charts/donutChart.component";
import { AlertComponent } from 'ng2-bootstrap/ng2-bootstrap';
import { ModalDemoComponent } from "../modals/modalDemoComponent";
import { NgInitHelperComponent } from "../helpers/nginit.helper.component";
import { ModalDirective } from "ng2-bootstrap/ng2-bootstrap";
import { MODAL_DIRECTIVES, BS_VIEW_PROVIDERS } from 'ng2-bootstrap/ng2-bootstrap';
#Component({
selector: 'dashboard',
templateUrl: '/app/components/dashboard/dashboard.component.html',
providers: [UserService, BS_VIEW_PROVIDERS],
directives: [SimpleChartComponent, AppleChartComponent, BarChartComponent, DonutChartComponent, AlertComponent, ModalDemoComponent, NgInitHelperComponent, ModalDirective]
})
export class DashboardComponent {
public areas: any;
constructor() {
this.areas = [
"Spiritual",
"Habits",
"Relationships",
"Emotional",
"Eating Habits",
"Relaxation",
"Exercise",
"Medical",
"Financial",
"Play",
"Work/ Life Balance",
"Home Environment",
"Intellectual Well-being",
"Self Image",
"Work Satisfaction"
]
}
}
Routes:
import { Routes, RouterModule } from '#angular/router';
import { AboutComponent } from './components/about.component';
import { PageNotFoundComponent } from "./components/pageNotFound.component";
import { HomeComponent } from "./components/home.component";
import { DashboardComponent } from "./components/dashboard/dashboard.component";
import { SurveyComponent } from "./components/survey/survey.component";
import { ResourcesComponent } from "./components/resources.component";
import { LogoutComponent } from "./components/logout.component";
import { AuthGuard } from "./components/auth-guard.service";
import { loginRoutes, authProviders } from './login.routing';
import { LoginComponent } from "./components/login.component";
const appRoutes:Routes = [
{ path: '', component: HomeComponent },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
{ path: 'logout', component: LogoutComponent },
{ path: 'resources', component: ResourcesComponent },
{ path: 'survey', component: SurveyComponent },
{ path: 'about', component: AboutComponent },
{ path: 'login', component: LoginComponent },
{ path: '**', component: PageNotFoundComponent }
];
export const appRoutingProviders: any[] = [
authProviders
];
export const routing = RouterModule.forRoot(appRoutes);
login route:
import { Routes } from '#angular/router';
import { AuthGuard } from './components/auth-guard.service';
import { AuthService } from './components/auth.service';
import { LoginComponent } from './components/login.component';
export const loginRoutes: Routes = [
{ path: 'login', component: LoginComponent }
];
export const authProviders = [
AuthGuard,
AuthService
];

In AuthGuard do the following:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate() {
if (/*user is logged in*/) {
this.router.navigate(['/dashboard']);
return true;
} else {
this.router.navigate(['/Login']);
}
return false;
}
}

Here's how to correctly handle redirects in a guard by using an UrlTree
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivateChild {
constructor(
private authService: AuthService,
private logger: NGXLogger,
private router: Router
) {}
canActivateChild(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> {
return this.authService.isLoggedIn().pipe(
map(isLoggedIn => {
if (!isLoggedIn) {
return this.router.parseUrl('/login');
}
return true;
})
);
}
}
Big thanks to Angular In Depth for the explanation!

You can now return a UrlTree from an AuthGuard, or a boolean true / false.
Kind of amazed nobody has mentioned this yet! Sorry no example right now, but the idea is pretty simple.

I actually changed my service to this and it works:
import { Injectable } from '#angular/core';
import { CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '#angular/router';
import { AuthService } from './auth.service';
import {UserService} from "./user.service";
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router, private userService: UserService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.authService.isLoggedIn){
console.log('ATUH GUARD SAYD THEY ARE ALREADY LOGGED IN!');
return true;
}else {
this.userService.getUser().subscribe((user) => {
console.log('AUTH GUARD GETTING USER', user);
if (user._id) {
this.authService.isLoggedIn = true;
// Store the attempted URL for redirecting
this.authService.redirectUrl = state.url;
this.router.navigate(['/dashboard']);
return true;
}else{
console.log('Validation Failed.');
localStorage.clear();
this.router.navigate(['/login']);
return false;
}
}, (error) => {
console.log('There was an error.');
this.router.navigate(['/login']);
return false
});
}
}
}

I solved it like this and used it in my AuthGuard
isLoggedIn(): Observable<boolean> {
return this.afAuth.authState
.pipe(
take(1),
map(user => {
return !!user;
},
() => {
return false;
}
),
tap(loggedIn => {
if (!loggedIn) {
this.router.navigate(['/']);
}
}
));
}

This is what I did for canActivate
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// ensure the user is properly logged in and the url he's requesting is within his right
if (this.authSvc.getRole().trim().length > 0 && this.authSvc.getToken().trim().length > 0
&& state.url.includes(this.authSvc.getRole().trim())) {
let url: string;
// base on user's role send to different url to check
if (this.authSvc.getRole().trim() === 'client') {
url = ClientAccessRequestUrl;
} else {
url = AdminAccessRequestUrl;
}
return this.http.post<AccessRequestResponse>(url, {
token: this.authSvc.getToken(),
}).pipe(map(response => {
console.log('response is:', response);
// check successful then let go
if (response.reply === true) {
return true;
// unless go to visitor site
} else {
return this.router.createUrlTree(['/visitor']);
}
}));
} else {
return this.router.createUrlTree(['/visitor']);
}
}

The best way to do redirects after authentication is structuring the logic as shown below;
in the AuthGuard,
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
{
// keep the requsted url for redirect after login
let url: string = state.url;
// call the authentication function
var authenticated = this.http.isAuthenticated();
var subject = new Subject<boolean>();
authenticated.subscribe(
(res) => {
//if not authenticated redirect to the login route with the initial route attached as an query param 'returnUrl'
if(!res.successState) {
this.router.navigate(['/login'], {queryParams: {returnUrl: url}});
}else{
// the user is authenticated just go to the requested route
subject.next(res.successState);
}
});
return subject.asObservable();
}
in the login route
loginAction(data: any){
// if the auth works fine the go the route requested before the inconviniences :)
if(data.successState){
// get the query params to see if there was a route requested earlier or they just typed in the login route directly
this.route.queryParams.subscribe(params => {
// console.log(params); //returnUrl
if(params.returnUrl){
// pearse the url to get the path and query params
let urlTree: UrlTree = this.router.parseUrl(params.returnUrl);
let thePathArray : any[] = [];
// populate it with urlTree.root.children.primary.segments[i].path;
for(let i = 0; i < urlTree.root.children.primary.segments.length; i++){
thePathArray.push(urlTree.root.children.primary.segments[i].path);
}
let the_params = urlTree.queryParams;
this.router.navigate(thePathArray, {queryParams: the_params});
}else{
this.router.navigate(['']);
}
});
}else{
// tell them about it and let them try again or reset the password
}
}
That should work perfectly. it will even preserve query params for the initial request.
THANKS

Related

If I refresh the page when I'm logged in, the Guard redirects me to the login page

If I refresh the page when I'm logged in, the Guard redirects me to the login page. Is there a way for redirecting to the login page only if the user truly log out?
I am using frontend as angular (version: 12.2.13)and backend as firebase.
auth.guard.ts
import { Injectable } from "#angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "#angular/router";
import { AuthService } from "./auth.service";
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router){}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot){
if(this.authService.isAuth()){
return true;
} else {
this.router.navigate(['/auth']);
}
}
}
When user logged in the application, user's status should be saved in sessionStorage or Redux.Then An AuthGuard service must be defined.Finally the AuthGuard must be added to each Routes.
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const userLoginStatus = sessionStorage.getItem("userLogin");
const IsUserLogin: boolean | null = userLoginStatus ? JSON.parse(userLoginStatus) : null;
if ( IsUserLogin ) {
return true
}
else {
this.router.navigateByUrl("/");
return false
}
}
}
Routes with home route protected by AuthGuard
import { Routes, RouterModule } from '#angular/router';
import { LoginComponent } from './login/index';
import { HomeComponent } from './home/index';
import { AuthGuard } from './_guards/index';
const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
// home route protected by auth guard
{ path: '', component: HomeComponent, canActivate: [AuthGuard] },
// otherwise redirect to home
{ path: '**', redirectTo: '' }
];
export const routing = RouterModule.forRoot(appRoutes);
AuthGuard redirects to login page if user isn't logged in
import { Injectable } from '#angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('currentUser')) {
// logged in so return true
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
}

Redirect the user before component gets loaded

I would like to redirect the user to the homepage if there is no cookie set.
My problem is that the user may be redirected but it takes too long. For about 1 second they are still able to see the protected page. How can I prevent this?
ngOnInit() {
this.hasAccess().then(result => {
if (!result) {
this.router.navigate(['/']).then();
}
});
}
private hasAccess() {
return new Promise(resolve => {
this.login.hasCookie(this.cookieService.get('login')).subscribe(hasCookie => {
if (hasCookie === 1) {
return new Promise(() => {
this.login.getCookie().subscribe(cookieFromServer => {
if (cookieFromServer === this.cookieService.get('login')) {
return resolve(true);
} else {
return resolve(false);
}
});
});
}
return resolve(false);
});
});
I have also tried to run my function hasAccess() in constructor() instead of ngOnInit() but the problem remains the same.
You should use route guards. They check if the routes can be visited or not before the route loads its content.
#Injectable()
export class CookieGuard implements CanActivate {
constructor(
private cookieService: CookieService,
private login: LoginService
) {}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.login.hasCookie(this.cookieService.get('login'));
}
}
From your notes it is not clear what this.login is, you'll need to adjust that to fit your setup. Then you can configure your route with the following guard:
const routes: Route[] = [
{ path: '/whatever', component: WhateverComponent, canActivate: [CookieGuard] }
];
You can use Angular gurad.
#Injectable({
providedIn: 'root'
})
export class YourGuard implements CanActivate {
path: ActivatedRouteSnapshot[];
route: ActivatedRouteSnapshot;
constructor(private auth: AuthenticationService, private router: Router) { }
canActivate() {
if (your logic) {
....
}
else redirect to ...
}
in your routing.module :
{
path: 'your path',
loadChildren: () => import('path')
.then(m => m.YorModule),
canActivate:[YourGuard]
},
Use a RouteGuard like this:
export class ComponentRouteGuard implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if (this.isCookieSet) {
return true;
} else {
this.router.navigate([`unauthorized`]);
return false;
}
}
}
Routes:
export const routes: Routes = [
{ path: 'test', component: YourComponent, canActivate: [ComponentRouteGuard]},
{ path: 'unauthorized', component: UnAuthorizedComponent }
];

How to approach multiple async requests from back-end in Angular 6?

I am new to Angular and having hard time grasping how to deal with async requests.
I have 3 components:
parent - AppComponent
children - LoginComponent,NavbarComponent,DashboardComponent,MainComponent,SidebarComponent
and one AuthService
On pressing "Login" button at the logging component I need to transfer boolean "true" value to all components.
On pressing "Logout" button at the navbar component I need to transfer boolean "false" value to all components and set user=null
if true ->
set token in localStorage with user ID
preform http.get("http://localhost:3000/user/"+id) request to retrieve full user info and inject user info to Dashboard,Main,Sidebar,App and Navbar components.
The problem is that whenever I logout the false/true value updates on all components immediately but the user info does not turn into null unless I refresh the page or send it with router to another component and then return to MainComponent, same thing with new login.
How do I update both user info and status in all components immediately without refreshing the page?
authService:
import { Injectable } from "#angular/core";
import { HttpClient } from "#angular/common/http";
import { Router } from "#angular/router";
import { User } from "../models/User";
#Injectable({ providedIn: "root" })
export class AuthService {
user: User;
private _url = "http://localhost:5000/user/";
constructor(private http: HttpClient, private _router: Router) {}
registerUser(user) {
return this.http.post<any>(this._url + "register", user);
}
loginUser(user) {
return this.http.post<any>(this._url + "login", user);
}
logoutUser() {
localStorage.clear();
this._router.navigate(["/"]);
}
loggedIn() {
return !!localStorage.getItem("token");
}
getToken() {
return localStorage.getItem("token");
}
getCurrentUser() {
return this.http.get<any>("http://localhost:5000/shop/current");
}
}
Main/Sidebar component:
import { Component, OnInit, DoCheck } from "#angular/core";
import { AuthService } from "src/app/services/auth.service";
import { User } from "src/app/models/User";
#Component({
selector: "app-sidebar",
templateUrl: "./sidebar.component.html",
styleUrls: ["./sidebar.component.css"]
})
export class SidebarComponent implements OnInit, DoCheck {
isSidenavOpen: boolean = true;
user: User;
constructor(private _authService: AuthService) {}
ngOnInit() {
if (this._authService.loggedIn()) this._authService.getCurrentUser().subscribe(res => (this.user = res.user));
else this.user = null;
}
ngDoCheck() {
if (!this._authService.loggedIn()) this.user = null;
}
}
login:
constructor(private _authService: AuthService, private _router: Router) {}
// onLoginUser() {
// this._authService.loginUser(this.loginUserData).subscribe(
// res => {
// localStorage.setItem("token", res.token);
// localStorage.setItem("user", res.user._id);
// this._router.navigate(["/"]);
// },
// err => console.log(err)
// );
// }
}
Use event EventEmitter to emit event on login, logout etc and listen in each component, that depends on user data.
In service, where you call logoutUser, loginUser methods of AuthService:
// LoginService
userLoggin: EventEmitter<User> = new EventEmitter();
userLoggout: EventEmitter<any> = new EventEmitter();
constructor(private _authService: AuthService, private _router:
Router) {}
loginUser() {
this._authService.loginUser(this.loginUserData).subscribe(
res => {
localStorage.setItem("token", res.token);
localStorage.setItem("user", res.user._id);
this.userLoggin.emit(res.user);
this._router.navigate(["/"]);
},
err => console.log(err)
);
}
logoutUser() {
this._authService.logoutUser().subscribe(
res => {
this.userLoggout.emit();
},
err => console.log(err)
);
}
}
Then in component:
import { Component, OnInit, DoCheck } from "#angular/core";
import { AuthService } from "src/app/services/auth.service";
import { User } from "src/app/models/User";
import { LoginService } from "src/app/services/login.service";
#Component({
selector: "app-sidebar",
templateUrl: "./sidebar.component.html",
styleUrls: ["./sidebar.component.css"]
})
export class SidebarComponent implements OnInit, DoCheck {
isSidenavOpen: boolean = true;
user: User;
loggin$: Subscription;
logout$: Subscription;
constructor(private _authService: AuthService, private _loginService: LoginService) {
this.loggin$ = this._loginService.userLoggin.subscribe(user => {
this.user = user;
});
this.logout$ = this._loginService.userLoggout.subscribe(user => {
this.user = null;
});
}
ngOnInit() {
}
ngOnDestroy() {
this.loggin$.unsubscribe();
this.logout$.unsubscribe();
}
}

Angular Can activate infinite calls

My auth guard is
import { Injectable } from '#angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class CheckToken implements CanActivate {
constructor(private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
var token = localStorage.getItem('id_token');
if(token) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
Issue is that line this.router.navigate(['/login']) call infinite times and page break. It works fine but after updating angular-cli this error occur
My routes
const appRoutes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full', canActivate: [CheckToken] },
{ path: 'login', component: LoginComponent },
{ path: 'logout', component: LogoutComponent },
];
something go wrong these line
if(token && url=="/login"){
return true;
}
if user do not login -> token = null/undefined, -> loop forever.
try this:
if(token || url=="/login"){
return true;
}
EDIT
basically my issue is that I want when token exists and user again hit
login url I want to redirect him to dashboard page
if(token) {
if (url=="/login") {
this.router.navigate(['/dashboard']);
}
return true;
}
or
redirect in LoginComponent

Redirect to login page, if not logged in with AngularFire 2

Whats the best way to redirect the user to the login page, if he's not authenticated with Angular2 / AngularFire2?
For example; I want to protect the /dashboard page from not authenticated users. The user should instantly get redirected to the /login page,
I'm using
angular2 version 2.0.0-rc.1
angular2/router version 2.0.0-rc.1
firebase version 3.0.5
angularfire2 version 2.0.0-beta.1
You can protect urls using Angular 2's UI router guard conditions.
https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard
An example using firebase
Guard Component
Note it would probably be better to replace referencing AF here, with an authentication service.
import { Injectable } from '#angular/core';
import { CanActivate, Router } from '#angular/router';
import { AngularFire } from 'angularfire2';
#Injectable()
export class CanActivateViaAuthGuard implements CanActivate {
user;
constructor(private af: AngularFire, private router: Router) {
this.af.auth.subscribe(user => {
if (user) {
this.user = user;
} else {
this.user = undefined;
}
});
}
canActivate() {
if (this.user) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
Application Routes
import { CanActivateViaAuthGuard} from './CanActivateViaAuthGuard';
const routes: Routes = [
{path: '/Login', component: LoginComponent},
{path: '/dashboard', component: DashboardComponent, canActivate: [CanActivateViaAuthGuard]}
];
export const routing = RouterModule.forRoot(routes);
Finally the login code
onSubmit() {
this.af.auth.login({
email: this.email,
password: this.password,
}).then(() => {
this.submitted = true;
this.router.navigate(['/dashboard', this.dashboard]);
});
}
You can use the Auth method here.
if ($scope.auth === null) {
$state.go('login');
}
Inject your $firebaseAuth and assign it to the $scope.auth then let the if check of its true or false
Found the solution.
Thanks to todo-angular-2 from r-park
import { ReflectiveInjector } from '#angular/core';
import { Router } from '#angular/router-deprecated';
import { AuthService } from './auth-service';
let appInjector: ReflectiveInjector;
/**
* This is a workaround until `CanActivate` supports DI
* #see https://github.com/angular/angular/issues/4112
*/
export class AuthRouteHelper {
static dependencies(): {auth: AuthService, router: Router} {
const injector: ReflectiveInjector = AuthRouteHelper.injector();
const auth: AuthService = injector.get(AuthService);
const router: Router = injector.get(Router);
return {auth, router};
}
static injector(injector?: ReflectiveInjector): ReflectiveInjector {
if (injector) appInjector = injector;
return appInjector;
}
static requireAuth(): boolean {
const { auth, router } = AuthRouteHelper.dependencies();
if (!auth.authenticated) router.navigate(['/SignIn']);
return auth.authenticated;
}
static requireUnauth(): boolean {
const { auth, router } = AuthRouteHelper.dependencies();
if (auth.authenticated) router.navigate(['/Tasks']);
return !auth.authenticated;
}
}

Categories

Resources