node.jsangularexpressexpress-sessionangular-route-guards

Login fails for the first time login and refreshing page forces user to logout in Angular+(Node/express).js


When I first try to login it does authenticate the user and set the loggedInStatus in AuthService as true but the loggedIn in the AuthGuard is set false and thus does not allow to navigate to the dashboard.. also the sequence of console.logs is out of order i.e the GUARD is logged first and then the LOGGEDINSTATUS while it should be opposite of that. And after I successfully log in after I try it the second time and am navigated to dashboard, on reloading the page it redirects me to login page and the value of loggedIn in AuthGaurd is undefined.

Login Component Typescript

import { Component, OnInit } from '@angular/core';
import { FormGroup, Validators, FormBuilder, NgForm } from '@angular/forms';
import { Router } from '@angular/router';

import { Credentials } from '../Services/models';
import { AuthService } from '../Services/auth/auth.service';
import * as util from '../../../Server/Utilities/util';


@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  loginForm: FormGroup;
  hide = true;


  constructor( 
    private authService: AuthService,
    private formBuilder: FormBuilder,
    private router: Router
  ) { 
      this.loginForm = this.formBuilder.group({
        email: ['', [Validators.required, Validators.email]],
        pass: ['', [Validators.required, Validators.minLength(6)]]
      });
  }

  ngOnInit() {

  }

  auth(form: NgForm){
    event.preventDefault();
    event.stopPropagation();
    let creds: Credentials = {
      email: this.loginForm.controls.email.value,
      password: this.loginForm.controls.pass.value
    }
    this.authService.authenticateUser(creds).subscribe((data: any) => {
      if(data.statusCode == util.statusCode.OK) {
        form.resetForm();
        this.router.navigate(['dashboard']);
      }
      else{
        console.log("resp", data);
      }
    });
  }

}

AuthService

import { Response, Headers, URLSearchParams, RequestOptions } from '@angular/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError, shareReplay } from 'rxjs/operators';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { User, Credentials } from '../models';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  baseUrl = 'http://localhost:3000';
  private loggedInStatus: boolean;

  constructor( private http: HttpClient ) { 
    this.getIsLoggedIn();
  }


  get loggedIn() : boolean {
    this.getIsLoggedIn();
    return this.loggedInStatus;
  }


  getIsLoggedIn() {
    this.http.get(this.baseUrl + '/login', {
      withCredentials: true
    }).subscribe((res: any) => {
      this.loggedInStatus = res.isLoggedIn;
      console.log("LOGGEDINSTATUS:", this.loggedInStatus);
    }, (err) => {
      this.handleError(err);
    });
  }

  registerUser(user: User): Observable<any> {
    console.log("Mapping your request to appropriate route from Front End Service.");
    console.log("User: ", user);
    const cpHeaders = new HttpHeaders({'Content-Type': 'application/json'});
    const options = {headers: cpHeaders};
    return this.http.post(this.baseUrl + '/register-user', user, options)
      .pipe(shareReplay(1))
      .pipe(catchError(err => this.handleError(err)));
  }

  authenticateUser(creds: Credentials): Observable<any> {
    console.log("Mapping your request to appropriate route from Front End Service.");
    console.log("Credentials: ", creds);

    const cpHeaders = new HttpHeaders({'Content-Type': 'application/json'});
    const options = {headers: cpHeaders, withCredentials: true};
    return this.http.post(this.baseUrl + '/auth-user', creds, options)
      .pipe(shareReplay(1))
      .pipe(catchError(err => this.handleError(err)));
  }

  private handleError(error: any){
    console.error(error.message || error);
    return throwError(error.status);
  }

}

AuthGuard

import { Injectable } from '@angular/core';
import { CanActivate, CanActivateChild, CanLoad, Route, UrlSegment, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
  constructor(
    private authService: AuthService,
    private router: Router
    ){ }
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    this.authService.getIsLoggedIn();
    const loggedIn = this.authService.loggedIn;
    console.log("GUARD", loggedIn);
    if(loggedIn){
      return true;
    }else{
      this.router.navigate(['']);
    }
  }
  canActivateChild(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
  }
  canLoad(
    route: Route,
    segments: UrlSegment[]): Observable<boolean> | Promise<boolean> | boolean {
    return true;
  }
}

auth.js(Routes)

const express = require('express');
const router = express.Router();
const authService = require('../Services/Service');
const util = require('../Utilities/util');


router.post('/register-user', (req, res) => {
    console.log(">>> Routing you requests to backend services");
    authService.registerUser(req.body, (data) => {
    sess = req.session;
    sess.email = req.body.email;
    console.log(">>> Setting up a cookie-session \n"+">>>", sess);
    res.send(data);
  });
});

router.get('/auth-admin', (req, res) => {
  authService.authenticateAdmin(req.query, (data) => {
    res.send(data);
  });
});

router.post('/auth-user', (req, res) => {
  console.log(">>> Routing you requests to backend services");
  authService.authenticateUser(req.body, (data) => {
    sess = req.session;
    if(data.statusCode == util.statusCode.OK){
      sess.email = req.body.email;
    }
    console.log(">>> Setting up a cookie-session \n"+">>>", sess);
    res.send(data);
  });
});

// Check if user is logged in.
router.get('/login', (req, res) => {
  console.log(">>> Check if user is logged in \n >>>", req.session.email);
  req.session.email ? res.status(200).send({isLoggedIn: true}) : res.status(200).send({isLoggedIn: false})
});

module.exports = router;

Server.js

const app = require('express')();
const server = require('http').Server(app);
const bodyParser = require('body-parser');
const express = require('express');
const cors = require('cors');
const http = require('http');
const path = require('path');
const session = require('express-session');

let routes = require('./Routes/auth');
let util = require('./Utilities/util');
app.use(session({secret: 'mlwacvbspaurfyp',saveUninitialized: true,resave: true, cookie: { maxAge: 1 * 60 * 60 * 1000 }}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// To avoid cors browser exception.
app.use(cors({origin: [
    "http://localhost:4200"
  ], credentials: true}));

app.use(function(err, req, res, next) {
  return res.send({ "statusCode": util.statusCode.ONE, "statusMessage": util.statusMessage.SOMETHING_WENT_WRONG });
});

// app.use(express.static(path.join(__dirname, 'public')));

app.use(routes);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    next();
});

/*first API to check if server is running*/
// app.get('*', (req, res) => {
//     res.sendFile(path.join(__dirname, '../server/client/dist/index.html'));
// });

server.listen(3000,function(){
    console.log('app listening on port: 3000');
});

Solution

  • Change the methods in auth service as i mentioned below :-

    async loggedIn() {
        await this.getIsLoggedIn().toPromise();
        return this.loggedInStatus;
      }
    
    
      getIsLoggedIn() {
        return this.http.get(this.baseUrl + '/login', {
          withCredentials: true
        }).pipe(map((res: any) => {
          this.loggedInStatus = res.isLoggedIn;
          return true;
        }), catchError((err) => {
          return this.handleError(err);
        }));
      }