0

I am struggling to figure out what is going on with my canActivate Method. Currently, I am able to log in and see that my user credentials are being saved and used in both localStorage and a console.log() so I know that I am creating them. I can also use an autologin feature. The problem is when I try to protect routes with AuthGuard, my canActivate method is (I'm guessing) returning false every time. I have tried to use console.log to see what is being output in canActivate but I cannot log anything past the first return.

Here is my auth.guard.ts file:

    import { Injectable } from "@angular/core";
    import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree, Router } from "@angular/router";
    import { Observable } from "rxjs";
    import { AuthService } from "./auth.service";
    import { map, take } from 'rxjs/operators';
    
    @Injectable({providedIn: 'root'})
    export class AuthGuard implements CanActivate {
    
        constructor(private authService: AuthService, private router: Router){}
        
        canActivate(
            route: ActivatedRouteSnapshot,
            state: RouterStateSnapshot
            ): boolean 
            | UrlTree 
            | Observable<boolean | UrlTree> 
            | Promise<boolean | UrlTree> {
                console.log(this.authService.user);
                console.log('In canActivate Method');
            return this.authService.user.pipe(
                take(1),
                map( user => {
                const isAuth = !!user
                console.log('Deeper In canActivate Method');
                console.log(isAuth);
                console.log('Deeper In canActivate Method');
                if(!isAuth){
                    return true;
                } 
                else {
                this.router.createUrlTree(['']);
                return false;
            }
            }));
        }
    
        
    }

And here is my app.module.ts file:

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { FormsModule, ReactiveFormsModule } from '@angular/forms';
    import { HttpClientModule } from '@angular/common/http';
    import { AuthGuard } from './auth/auth.guard';
    
    
    import { AppComponent } from './app.component';
    import { generalMachiningComponent } from './generalMachining/generalMachining.component';
    import { setupSheetsComponent } from './setupSheets/setupSheets.component';
    import { toolReOrderComponent } from './toolReOrder/toolReOrder.component';
    import { RouterModule, Routes } from '@angular/router';
    import { UserprofileComponent } from './userprofile/userprofile.component';
    import { NavbarComponent } from './navbar/navbar.component';
    import { SawComponent } from './saw/saw.component';
    import { ButtonBarGenMachComponent } from './button-bar-gen-mach/button-bar-gen-mach.component';
    import { DeburringComponent } from './deburring/deburring.component';
    import { Cat50leadwellsComponent } from './cat50leadwells/cat50leadwells.component';
    import { Cat40leadwellComponent } from './cat40leadwell/cat40leadwell.component';
    import { CinciMillComponent } from './cinci-mill/cinci-mill.component';
    import { DoosanComponent } from './doosan/doosan.component';
    import { LeadwellLatheComponent } from './leadwell-lathe/leadwell-lathe.component';
    import { MoriLatheComponent } from './mori-lathe/mori-lathe.component';
    import { CylinderKingComponent } from './cylinder-king/cylinder-king.component';
    import { RodHoneComponent } from './rod-hone/rod-hone.component';
    import { PartsWashingComponent } from './parts-washing/parts-washing.component';
    import { LoginHeaderComponent } from './login-header/login-header.component';
    import { MainTemplateComponent } from './main-template/main-template.component';
    import { LoginMainComponent } from './login-main/login-main.component';
    import { AssemblyComponent } from './assembly/assembly.component';
    import { EngineeringComponent } from './engineering/engineering.component';
    import { HeaderComponent } from './header/header.component';
    import { ENGCompressorFundamentalsComponent } from './eng-compressor-fundamentals/eng-compressor-fundamentals.component';
    import { ENGCompressorFundamentalsIndexAdvancedRecipCompressorInfoComponent } from './eng-compressor-fundamentals-index-advanced-recip-compressor-info/eng-compressor-fundamentals-index-advanced-recip-compressor-info.component';
    import { SignupComponent } from './signup/signup.component';
    import { ForgotauthComponent } from './forgotauth/forgotauth.component';
    import { LoadingSpinnerComponent } from './shared/loading-spinner/loading-spinner.component';
    
    
    
    
    const appRoutes: Routes =[
    {path: '', component: LoginMainComponent},
    {path: 'header', component:HeaderComponent},
    {path: 'signup', component:SignupComponent},
    {path: 'forgotauth', component: ForgotauthComponent},
    {path: 'userprofile', component: UserprofileComponent}, 
    {path: 'setupsheets', component: setupSheetsComponent, canActivate: [AuthGuard]},
    {path: 'toolreorder', component: toolReOrderComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining', component: generalMachiningComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/gm', component: generalMachiningComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/saw', component: SawComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/deburring', component: DeburringComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/partswashing', component: PartsWashingComponent, canActivate: [AuthGuard] },
    {path: 'generalmachining/cat50leadwells', component: Cat50leadwellsComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/cat40leadwell', component: Cat40leadwellComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/cinci', component:CinciMillComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/doosan', component: DoosanComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/leadwell-lathe', component:LeadwellLatheComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/moriseiki', component: MoriLatheComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/cylinderking', component: CylinderKingComponent, canActivate: [AuthGuard]},
    {path: 'generalmachining/rodhone', component: RodHoneComponent, canActivate: [AuthGuard]},
    {path: 'assembly', component: AssemblyComponent, canActivate: [AuthGuard]},
    {path: 'engineering', component: EngineeringComponent, canActivate: [AuthGuard]},
    {path: 'engineering/compressor-fundamentals-index', component: ENGCompressorFundamentalsComponent, canActivate: [AuthGuard]},
    {path: 'engineering/compressor-fundamentals-index/Advanced_Recip_Compressor_Info', component:ENGCompressorFundamentalsIndexAdvancedRecipCompressorInfoComponent, canActivate: [AuthGuard]}
    ];
    
    
    @NgModule({
      declarations: [
        AppComponent,
        generalMachiningComponent,
        setupSheetsComponent,
        toolReOrderComponent,
        UserprofileComponent,
        NavbarComponent,
        SawComponent,
        ButtonBarGenMachComponent,
        DeburringComponent,
        Cat50leadwellsComponent,
        Cat40leadwellComponent,
        CinciMillComponent,
        DoosanComponent,
        LeadwellLatheComponent,
        MoriLatheComponent,
        CylinderKingComponent,
        RodHoneComponent,
        PartsWashingComponent,
        LoginHeaderComponent,
        MainTemplateComponent,
        LoginMainComponent,
        AssemblyComponent,
        EngineeringComponent,
        HeaderComponent,
        ENGCompressorFundamentalsComponent,
        ENGCompressorFundamentalsIndexAdvancedRecipCompressorInfoComponent,
        SignupComponent,
        ForgotauthComponent,
        LoadingSpinnerComponent
      ],
      imports: [
        BrowserModule,
        RouterModule.forRoot(appRoutes),
        FormsModule,
        ReactiveFormsModule,
        HttpClientModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }

I have read several posts stating that it was the providers in the app.module that need to have AuthGuard added to it, but other posts stated that my @Injection to Root should take care of that. I have tried both and it is not making a difference. Any help with this problem would be greatly appreciated.

EDIT: Add auth.service file

import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Router } from '@angular/router';
import { catchError, Subject, throwError, tap } from "rxjs";
import { environment } from "../../environments/environment";
import { User } from "./user.model";
import { map, take } from 'rxjs/operators';

interface AuthResponseData {
    kind: string;
    idToken: string;
    email: string;
    refreshToken: string;
    expiresIn: string;
    localId: string;
    registered?: boolean;
}


@Injectable({ providedIn: 'root' })
export class AuthService {
    user = new Subject<User>();
    private tokenExpirationTimer: any;

    constructor(private http: HttpClient, private router: Router) { }

    private handleAuthentication(email: string, userId: string, token: string, expiresIn: number) {
        const expirationDate = new Date(new Date().getTime() + expiresIn * 1000);
        const user = new User(
            email,
            userId,
            token,
            expirationDate
        );
        this.user.next(user);
        this.autoLogout(expiresIn * 1000);
        localStorage.setItem('userData', JSON.stringify(user));
    }

    checkUserAuth() {
        const userData: {
            email: string;
            id: string;
            _token: string;
            _tokenExpirationDate: string;
        } = JSON.parse(localStorage.getItem('userData'));
        if (!userData) {
            console.log("No stored user data: returning false.");
            this.router.navigate(['']);
            return false;
        }
        const loadedUser = new User(userData.email, userData.id, userData._token, new Date(userData._tokenExpirationDate));
        if (loadedUser.token) {
            this.user.next(loadedUser);
            const expirationDuration = new Date(userData._tokenExpirationDate).getTime() - new Date().getTime()
            this.autoLogout(expirationDuration);
            return true;
         }
    }
    

    autoLogin() {
        const userData: {
            email: string;
            id: string;
            _token: string;
            _tokenExpirationDate: string;
        } = JSON.parse(localStorage.getItem('userData'));
        if (!userData) {
            return;
        }
        const loadedUser = new User(userData.email, userData.id, userData._token, new Date(userData._tokenExpirationDate));
        if (loadedUser.token) {
            this.user.next(loadedUser);
            this.router.navigate(['/userprofile']);
            const expirationDuration = new Date(userData._tokenExpirationDate).getTime() - new Date().getTime()
            this.autoLogout(expirationDuration);
        }
    }


    autoLogout(expirationDuration: number) {
        this.tokenExpirationTimer = setTimeout(() => {
            this.logout();
        }, expirationDuration);
    }


    signup(email: string, password: string) {
        return this.http
            .post<AuthResponseData>(
                'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=' + environment.API_KEY,
                {
                    email: email,
                    password: password,
                    returnSecureToken: true
                }
            )
            .pipe(catchError(this.handleError), tap(resData => {
                this.handleAuthentication(
                    resData.email,
                    resData.localId,
                    resData.idToken,
                    +resData.expiresIn
                );
            }));
    }

    login(email: string, password: string) {
        return this.http
            .post<AuthResponseData>('https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=' + environment.API_KEY,
                {
                    email: email,
                    password: password,
                    returnSecureToken: true
                }
            )
            .pipe(catchError(this.handleError), tap(resData => {
                this.handleAuthentication(
                    resData.email,
                    resData.localId,
                    resData.idToken,
                    +resData.expiresIn
                );
            }));
    }

    logout() {
        this.user.next(null);
        localStorage.removeItem('userData');
        this.router.navigate(['']);
        if (this.tokenExpirationTimer) {
            clearTimeout(this.tokenExpirationTimer)
        }
        this.tokenExpirationTimer = null;
    }

    private handleError(errorRes: HttpErrorResponse) {

        let errorMessage = 'An unknown error occured.';
        if (!errorRes.error || !errorRes.error.error) {
            return throwError(errorMessage);
        }
        switch (errorRes.error.error.message) {
            case 'EMAIL_EXISTS':
                errorMessage = 'This email exists already.';
                break;
            case "INVALID_PASSWORD" || "EMAIL_NOT_FOUND":
                errorMessage = "Email or password are invalid";
                break;
            case 'MISSING_PASSWORD':
                errorMessage = "A password is required."
                break;
            case 'USER_DISABLED':
                errorMessage = "The user account has been disabled by an administrator."
        }
        return throwError(errorMessage);

    }
}

Note: I have added the checkUserAuth() method and that has allowed me to return true or false in the canActivate() method in auth.guard, but I don't think this is the correct solution to the problem even though it is working now.

M. Bal.
  • 43
  • 2
  • 6

1 Answers1

0

You don't need the first return, I bet that is the problem. You just do the .pipe and the .map and in there you evaluate and return a result.

Also, you are doing const isAuth = !!user but immediately after you negate that result in the if statement:

if(!isAuth){
  return true;
} 
else {
  this.router.createUrlTree(['']);
  return false;
}

Change that if(!isAuth) for if(isAuth) and try again. And the guards only need to be specified in the providers array of your routing module.

JuanDeLasNieves
  • 354
  • 1
  • 3
  • 8
  • Thank you for your response. Unfortunately, the code will not compile without the first return so that did not work. I found this in the official docs with Angular: https://angular.io/api/router/CanActivate and in the examples, it shows that there is a return at the beginning of the statement. I also tried to adjust the code as you suggested with the if statement and that did not work. – M. Bal. Feb 21 '22 at 15:06
  • And then with the guards in the provider's array, I have tried both putting them in that array as well as injecting them and leaving the providers array empty as well as putting them in the array and it seems to make no difference. I am currently injecting them because that is what has been shown to me in another resource but I am not opposed to either solution. – M. Bal. Feb 21 '22 at 15:06
  • Ahh I see, but the example provided in the Angular docs returns true because it's hard-coded. Also reviewing your code, you are piping and mapping your `authService.user` but never `subscribe` to it. If it is an observable, then it never emits and that's why your `canActivate` never returns true. Would you share the code of `authService` to have a better idea? – JuanDeLasNieves Feb 21 '22 at 16:18
  • I have updated the question to include the authService file. I was able to add the checkUserAuth() method to authService that is able to return true or false and now canActivate() is working, but as you have pointed out, I am not confident that I am using my observable correctly and that is why the code in canActivate was not working. – M. Bal. Feb 22 '22 at 13:23