Angular and Firebase: How to keep user logged in as long as he/she does not explicitly log out?

1.1k Views Asked by At

I have a problem with my angular app. If, after logging in, I go to another website and back to my app again, I'm logged out.

Even if I only refresh the page, I'm getting logged out.

This is my AuthService:

export class AuthService {

    private isAuthenticated = false;


    constructor(private router: Router, private db: AngularFirestore, private angularFireAuth: AngularFireAuth) {}


    login(authData: AuthData) {

        this.angularFireAuth.auth.signInWithEmailAndPassword(
            authData.email, 
            authData.password
        ).then(result => {
            this.loggedInSuccessfully();
        })
        .catch(error => {
            console.log(error);
        });

    }

    logout() {
        this.loggedOutSuccessfully();
    }

    isAuth() {
        return this.isAuthenticated;
    }



    private loggedInSuccessfully() {
        this.isAuthenticated = true;
        this.router.navigate(['']);
    }

    private loggedOutSuccessfully() {
        this.angularFireAuth.auth.signOut();
        this.router.navigate(['login']);
        this.isAuthenticated = false;
    }

}

... and this is the AuthGuard class:

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(['login']);
            return false;
        }     
    }
} 

How can I keep the user logged in as long as he/she does not explicitly log out?

1

There are 1 best solutions below

4
On

The problem is within your code, as this is the default behavior the variable isAuthenticated is initialized as false, and only modified after explicitly calling the login()function I suggest using a Replaysubject datatype for communicating between your AuthGuard and your service Here is a sample code With this logic you can even queue up a database read in case the routing logic depends on user levels stored in the database

import { ReplaySubject } from 'rxjs'
import { take, tap, map } from 'rxjs/operators';

export class AuthService {
private isAuthenticated: ReplaySubject<boolean> = new ReplaySubject(1)
constructor(private router: Router, private db: AngularFirestore, 
private angularFireAuth: AngularFireAuth) {
    angularFireAuth.authState.subscribe(state => {
        this.isAuthenticated.next(state)
    })
}

login(authData: AuthData) {
    this.angularFireAuth.auth.signInWithEmailAndPassword(
        authData.email,
        authData.password
    ).then(result => {
        this.loggedInSuccessfully();
    })
        .catch(error => {
            console.log(error);
        });
}

logout() {
    this.loggedOutSuccessfully();
}
/**
 * This function will never return a value until the replay subject gets a value, which is assigned only when the auth state changes
 */
isAuth() {
    return this.isAuthenticated.pipe(
        take(1),
        map(authState => !!authState),
        tap(authenticated => {
            if (!authenticated) {
                return false;
            }
            else {
                return true;
            }
        }), );
}

private loggedInSuccessfully() {
    this.isAuthenticated.next(true)
    this.router.navigate(['']);
}

private loggedOutSuccessfully() {
    this.angularFireAuth.auth.signOut();
    this.router.navigate(['login']);
    this.isAuthenticated.next(false)
}}

Your guard can remain unchanged