5
votes

In my web Angular project I've created an AuthenticationGuard and an AuthenticationService to handle the security.

These files comes from another branch of my project who works perfectly.

Here is how my scripts should works:

  1. Navigate to 'auth/login'
  2. User inputs his credentials
  3. Authservice call the back-end wepApi to get a Bearer Token
  4. Back-end returns the token.
  5. AuthService set his var 'isLoggedIn' to true;
  6. AuthService use to router to navigate to '/home'
  7. AuthGuard check authentication by checking the 'isLoggedIn' of AuthService.

My problem is when AuthGuard access the AuthService : AuthService always return false.

auth.guard.ts

import { Injectable }       from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot,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): boolean {
    let url: string = state.url;

    return this.checkLogin(url);
  }

  checkLogin(url: string): boolean {
    if (this.authService.getIsLoggedIn()) { 
      return true; 
    }

    // Store the attempted URL for redirecting
    this.authService.redirectUrl = url;

    // Navigate to the login page with extras
    this.router.navigate(['/auth/login']);
    return false;
  }
}

auth.service.ts

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/delay';

import { config } from './../shared/smartadmin.config';

import { Http, Headers, RequestOptions, Response } from '@angular/http';
import 'rxjs/add/operator/map'

@Injectable()
export class AuthService {
    private isLoggedIn: boolean = false;

    public redirectUrl: string;

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

    public getIsLoggedIn(): boolean {
        console.log("getIsLoggedIn() = " + this.isLoggedIn); // Always returns false
        return this.isLoggedIn;
    }

    public login(username: string, password: string) {
        this.ProcessLogin(username, password)
            .subscribe(result => {
                if (result === true) {
                    console.log("before attribution");
                    console.log("this.isLoggedIn = " + this.isLoggedIn); // returns false
                    this.isLoggedIn = true;
                    console.log("after attribution");
                    console.log("this.isLoggedIn = " + this.isLoggedIn); // returns true
                    this.router.navigate(this.redirectUrl ? [this.redirectUrl] : ['/home']);
                } else {
                    this.logout();
                }
            });
    }


    public logout(): void {
        localStorage.removeItem('oAuthToken');
        this.isLoggedIn = false;
    }

    private ProcessLogin(username: string, password: string): Observable<boolean> {

        let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
        let options = new RequestOptions({ headers: headers });
        let body = 'grant_type=password&username=' + encodeURIComponent(username) + '&password=' + encodeURIComponent(password);

        let endpoint = config.API_ENDPOINT + 'token';

        return this.http.post(endpoint, body, options)
            .map((response: Response) => {
                // login successful if there's a jwt token in the response
                let token = response.json() && response.json().access_token;
                if (token) {
                    localStorage.setItem('oAuthToken', token);

                    // return true to indicate successful login
                    return true;
                } else {
                    localStorage.removeItem('oAuthToken');
                    // return false to indicate failed login
                    return false;
                }
            });
    }
}
3
Can you move your if else logic to the subscribe part and try again? Just return the response.json() inside map.eko
Excuse me but I don't really understand your proposition. Could you precise it ?Franck Charlier
Instead of returning an Observable<boolean> inside ProcessLogin method, just return the response.json() in your map. Do that if/else logic when you subscribe to ProcessLogin inside your login method.eko
Ok, I've move the logic of map inside of the Subscribe part, but the behaviour is the same :/Franck Charlier
Can you also provide the module definition of the module that defines the AuthService injectable and the "root" module definition, i.e. AppModule?Hampus

3 Answers

6
votes

Without having seen your module definitions I suspect that you have not made AuthService a core service (a Singleton) which mean that every module that makes use of it will have its own instance (keeping track of its own isLoggedIn flag).

To make a service a singleton in angular it has to be served by the root module injector. To accomplish this you will need to do this:

import { NgModulep } from '@angular/core';
import { CommonModule, ModuleWithProviders } from '@angular/common';
import { AuthService, AuthGuard } from './services/index';

@NgModule({
  imports: [
    CommonModule,
    ModuleWithProviders
  ]
})
export class SharedModule {

  static forRoot(): ModuleWithProviders {
    return {
      ngModule: SharedModule,
      providers: [
        AuthService,
        AuthGuard
      ]
    };
  }

}

And then call the forRoot method when importing the SharedModule into the root AppModule.

@NgModule({
  imports: [
    ...
    SharedModule.forRoot(),
    ...
  ],
  ...,
  bootstrap: [AppComponent]
})
export class AppModule { }

Have a look att "Configure Core Services" here https://angular.io/docs/ts/latest/guide/ngmodule.html#!#core-for-root

1
votes

I had a similar problem, which in my case was being caused by the click event being wired to a button element, but in Chrome the entire form was being submitted every time the button was clicked, because CHrome's default action is to treat a button click as a submit if there is no type attribute on the button.

The fix was to add the tag type="button" to the login button in the html

Description here

1
votes

Dependencies are singletons within the scope of an injector.

However, Angular DI is a hierarchical injection system, which means that nested injectors can create their own service instances. For more information, see Hierarchical Injectors.
Source

Just double check where you provide your service.

List your modules and inspect providers section in each.
If there are more than one instance - each module will provide its own instance of service.

I had a same issue and found I add AuthService to AppModule and forget to remove it from AuthModule, so login page(in the auth module) had other instance of it.