0

I'm new to Angular and I'm working on a project which uses Angular 5 as frontend and Laravel5.5 as backend.

What I am trying to do: I'm doing token based authentication to check if the correct user is sending the request to backend and if the token is not authenticated it will throw an exception and the user will be redirected to login page.

What I did: I followed a tutorial on Youtube which uses Authguard to verify the user, Below is my Code.

auth.guard.ts

import {Injectable} from "@angular/core";
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router";
import {UserService} from "../_services/user.service";
import {Observable} from "rxjs/Rx";

@Injectable()
export class AuthGuard implements CanActivate {

constructor(private _router: Router, private _userService: UserService) {
}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):  Observable<boolean> | boolean {
    let currentUser = JSON.parse(localStorage.getItem('currentUser'));
    console.log("AuthGuard.ts:  "+ currentUser);
    if (currentUser == null) {
        this._router.navigate(['/login'], {queryParams: {returnUrl: state.url}});
        return false;
    }
    else{
        let response = this._userService.verify().map(
            data => {
                console.log("This is the data returned in authguard: "+data)
                if (data !== null) {
                    // logged in so return true
                    // console.log("Not null");
                    return true;
                }
                // error when verify so redirect to login page with the return url
                this._router.navigate(['/login'], {queryParams: {returnUrl: state.url}});
                return false;
            },
            error => {
                console.log('myerror');
                // error when verify so redirect to login page with the return url
                this._router.navigate(['/login'], {queryParams: {returnUrl: state.url}});
                return false;
            });

        console.log("response : "+ typeof response);
        return response;
    }
}
}

In tutorial they used subscribe instead of map, but I did some research and it comes to me that we can not use subscribe with Observable.

My code though shows the error with subscribe and redirects the user to login page but with subscribe it does not return true or false so user is not redirected to next pages even if the credentials are correct.

Here is the code for verify function.

user.service.ts

import {Injectable} from "@angular/core";
import { HttpClient , HttpHeaders , HttpErrorResponse } from '@angular/common/http';
import {User} from "../_models/index";
import { Observable } from 'rxjs';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

interface DataResponse { status: string; }

@Injectable()
export class UserService {

constructor(private http: HttpClient) {
}

verify(): Observable<any>  {
    let currentUser = JSON.parse(localStorage.getItem('currentUser'));
    let headers = new HttpHeaders({'Authorization': 'Bearer ' + currentUser.token});
    let abc = this.http.get<DataResponse>('http://127.0.0.1:8000/api/auth/verify', {headers:headers}).map(response =>{ 
                                                                                                        // console.log("hjsdgjd:  "+response.status ); 
                                                                                                            response
                                                                                                    })
                                                                                                    .catch(this.errorHandler);
    console.log("abc:  " + abc);
    return abc;
}
errorHandler(error: HttpErrorResponse){
    console.log('some thing');
    return Observable.throw(error.message);
}
}

This is my console when I modify the token

Error

halfer
  • 19,824
  • 17
  • 99
  • 186
Adnan Sheikh
  • 760
  • 3
  • 13
  • 27
  • @SurjitSD: thanks for wanting to improve questions here. However, there is no typographical reason to wrap software names like Laravel and Angular in bold or italics. They are proper nouns, so just fix the case, and otherwise leave them as they are please. Excessive formatting makes things harder to read, not easier. – halfer Jan 15 '18 at 12:56

2 Answers2

0

I don't know if you followed the tutorial and if you made some changes by yourself, but in any case, your code is wrong on many levels.

About subscribe, I don't know where you looked for to find you can't use it with Observable, because that's the first operator you should know when using observables.

Let me rewrite your code and explain you what you did wrong :

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):  Observable<boolean> | boolean {

  let currentUser: any;

  // You need to surround your parsing with a try catch, in case it goes wrong
  try {
    currentUser = JSON.parse(localStorage.getItem('currentUser'));
  } catch { return false; }

  console.log("AuthGuard.ts:  " + currentUser);

  // Use falsy values, they cover more cases (such as empty or undefined)
  if(!currentUser) {
    this._router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
    return false;
  }
  else {
    // You will subscribe to your Observable, because you have to do something with the response
    this._userService.verify().subscribe(data => {

      console.log("This is the data returned in authguard: " + data)

      // This time, use truthy values (opposite of falsy)
      if (data) {
        // logged in so return true
        // console.log("Not null");
        return true;
      }
      // Empty response 
      this._router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
      return false;
    },
    error => {
      console.log('myerror');
      // error when verify so redirect to login page with the return url
      this._router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
      return false;
    });

  // Don' return anything here : this will be returned before your HTTP call ends, and return a falsy value ('return false;')
  // console.log("response : " + typeof response);
  // return response;
  }
}
  • By the way, you should not make an http call on each and every guard. You should make one when the user logs in, then store the token in the session storage. Otherwise your application will be heavy, and not user-friendly. Next thing, what is the point of checking if the token is correct, when you make an HTTP call with the very same token ? Your user-checking logic should be on your backend, not your front-end –  Jan 15 '18 at 07:46
  • Thank you for replying, I was wrong saying subscribe does not work with Observable, What I wanted to say is subscribe does not work with Observable. Because when I use this, It gives a syntex error saying type subscribe is not assignable to type Observable. – Adnan Sheikh Jan 15 '18 at 07:52
  • Now about your comment on why making http call on each and every guard. What if I change my token in the local storage and submit a request that i'm not authorized to. – Adnan Sheikh Jan 15 '18 at 07:54
  • It works well, believe me. You just did something wrong in your code : on the last lines, you were returning the subscription, while your methods asks for a boolean, or an Observable of a boolean. I removed that part, it should work. `What if I change my token in the local storage and submit a request that i'm not authorized to` => then your backend should return the proper response with an error code, I believe it is 403 or 401 –  Jan 15 '18 at 07:57
  • I did followed your code and now I'm just returning true or false, but again after giving correct credentials it does not redirecting the user to next pages. :( and there is no error in the console when i give correct credentials – Adnan Sheikh Jan 15 '18 at 08:10
  • Yes My backend throws 401 exception but we have to handle that exception on frontend. – Adnan Sheikh Jan 15 '18 at 08:12
  • What are your console logs ? make also a log of the return of your call to see what it contains. And if your backend already throws a 401, you should focus on making an Interceptor, not a Guard : the interceptor will catch the response and delete the token from your local storage. In your guard, you should only test if the token is valid and not expired. –  Jan 15 '18 at 08:23
  • Thank you. Yes, I did used an interceptor and my problem is solved now. Still i didn't get that what the actual problem was with map. – Adnan Sheikh Jan 15 '18 at 12:47
  • map doesn't trigger an HTTP call, up to its name, it just `maps` the content of an answer. This allows you to change it before returning it from your function. Since you didn't use subscribe, the call was enver made. Glad to hear it works ! remember to mark your issue as resolved :) –  Jan 15 '18 at 12:49
0

I solved this problem by using an interceptor.

What it does: It takes the http request from frontend and appends the header(containing token) to request before sending it to backend. And then it gets the response from backend, checks the response for error and if there is an error then it redirects the user to login page.

Here is my Interceptor Code.

AuthorizationInterceptor.ts

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Router } from '@angular/router';

import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';

@Injectable()
export class AuthorizationInterceptor implements HttpInterceptor
{
constructor(private _router: Router) { }

    private handleAuthError(err: HttpErrorResponse): Observable<any> {
        //handle your auth error or rethrow
        console.log('server error : ', err);
        if (err.status === 401 || err.status === 403) {
            //navigate /delete cookies or whatever
            localStorage.removeItem('currentUser');
            this._router.navigate(['/login']);
            // if you've caught / handled the error, you don't want to rethrow it unless you also want downstream consumers to have to handle it as well.
            // return Observable.of(err.message);
        }
        return Observable.throw(err);
    }

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
{
    // console.log('resquest passing from interceptor....', req, next);
    var authReq;

    let currentUser = JSON.parse(localStorage.getItem('currentUser'));
    // console.log("In the Interceptor: " + req);
    if (currentUser) {

        // Clone the request to add the new header.
        req = req.clone({headers: req.headers.set('Authorization', 'Bearer ' + currentUser.token)});
        // console.log("Authentication Request:  " + authReq);
        // shortcut for above...
        // const authReq = req.clone({setHeaders: {Authorization: authHeader}});

        // Pass on the cloned request instead of the original request.
    }

 return next.handle(req).catch(x => this.handleAuthError(x));
}
}

Note: Do not forget to import the interceptor in your module.ts file. See This guide for building an interceptor.

Adnan Sheikh
  • 760
  • 3
  • 13
  • 27