Firebase: Keep user logged in angular 7 - javascript

I use firebase and angularfire2 within an authentication system!
The problem is that after refresh the user needs to log in again! I need to avoid that issue so I found out that firebase gives me that option by using authState
but still not working!
This the code for the authService:
import { Injectable } from '#angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import { Observable } from 'rxjs/internal/observable';
import { NavController } from '#ionic/angular';
import { ToastMessagesService } from './toast-messages.service';
import * as firebase from 'firebase';
#Injectable({
providedIn: 'root'
})
export class AuthService {
public user: Observable<firebase.User>;
public userDetails: firebase.User = null;
constructor(
private af: AngularFireAuth,
private navCtrl: NavController,
private statusMessage: ToastMessagesService
) {
this.user = af.authState;
this.user.subscribe(
user => this.userDetails = user
)
}
async siginInRegular(username: string, password: string) {
try {
// const credentials = this.af.auth.email
return await this.af.auth.signInWithEmailAndPassword(username, password).then(
user => {
if (user) {
this.navCtrl.navigateForward('/home');
this.statusMessage.message(`Welcome ${user.user.email}`);
}
},
err => {
console.log(err);
}
);
} catch (error) {
console.dir(error);
}
}
async register(username: string, password: string) {
try {
return await this.af.auth.createUserWithEmailAndPassword(username, password).then(
user => {
this.navCtrl.navigateForward('/profile');
this.statusMessage.message(`Welcome ${user.user.email}`);
}
);
} catch (error) {
console.dir(error);
}
}
signOut() {
return this.af.auth.signOut();
}
isLoggedIn(): boolean {
return (this.userDetails != null) ? true : false;
}
}
The guard code:
import { Injectable } from '#angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
import { NavController } from '#ionic/angular';
import { AngularFireAuth } from 'angularfire2/auth';
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private auth: AuthService,
private navCtrl: NavController,
private af: AngularFireAuth
) {
}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if (this.auth.isLoggedIn()) {
return true
}
console.log('Access denied!');
return false;
}
}

Firebase actually automatically will sign the user in when you reload the page. But since your handling of the sign-in is only in the then() block, it only happens when you explicitly sign them in.
To fix this, you want to use an onAuthState listener as shown in get the currently signed in user:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
Unlike the then() handler, this onAuthStateChanged handler will be called every time the user's authentication state changes, including when you reload the page.
Since you're using AngularFire2, you can also use af.auth.subscribe as shown here: How to get the firebase.User in AngularFire2

Related

Why is the user not set here?

I`m having problems understanding why I'm not able to set the user in the app. Register and login work.
Upon refreshing the app the user is lost, but the access token is still there. Also when I try to create a product, the manufacturer(owner) of it is undefined and not listed in the DB.
I've been debugging it and I can see that req.user is not found by the app and stays undefined/null.
I`m attaching the whole github project for easier code check, if someone decides to help me out here.
https://github.com/theBoomstick7/projectOils2
Thanks in advance everyone!
Changed all possible setting of the user, register and logout and login do work, but not the intended way.
I understand the design is not good, will be fixed later
As I understand this is not a good way to ask a question here, let me add some parts of the code.
This is the server controller of the product:
const {getAll,createProduct} = require(`../services/productService`)
const productController = require(`express`).Router()
productController.post(`/create`, async(req,res) => {
const data = req.body
console.log(req.user)
try {
const userId = req?.user?._id
const product = await createProduct(data,userId)
res.status(201).json(product)
} catch (error) {
res.status(400).json({error:error.message})
}
res.end()
})
This is the angular auth service :
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { tap } from 'rxjs';
import { IUser } from '../interfaces/user';
const AUTH_API_URL = 'http://localhost:3000' // Change this to environment directory later
#Injectable({
providedIn: 'root'
})
export class AuthService {
user: null | IUser | undefined
constructor(private http: HttpClient, private router: Router) { }
get isLogged(): boolean {
if(this.user){
return true
}else
{
return false
}
}
register(data: {}){
return this.http.post<IUser>(`${AUTH_API_URL}/register`,data).pipe(
tap((user) => {
this.user = user
localStorage.setItem(`accessToken`, this.user.accessToken)
})
)
}
login(data: {}){
return this.http.post<IUser>(`${AUTH_API_URL}/login`, data). pipe(
tap((user) => {
this.user = user
localStorage.setItem(`accessToken`, this.user.accessToken)
})
)
}
logout(){
this.user = null
return localStorage.removeItem(`accessToken`)
}
}
This is the way the user register is handled :
export class RegisterComponent {
errors: any;
constructor(private fb: FormBuilder, private userService: AuthService, private router: Router) {}
registerForm = this.fb.group({
email: [``,[Validators.required, Validators.email]],
username: [``, [Validators.required, Validators.minLength(6)]],
password: [``, [Validators.required, Validators.minLength(6)]],
rePass: [``, [Validators.required,passwordValidator]]
})
register(): void{
this.userService.register(this.registerForm.value).subscribe
({
next: () => this.router.navigate([`/`]),
error:(err)=> {
this.errors = err.error?.error
}
})
this.registerForm.reset()
}
}
This is how a product is created
Product Angular service :
export class ProductsService {
constructor(private http : HttpClient, private router : Router) { }
createProduct(data : {}){
return this.http.post<IProduct>(`${API_URL}/create`, data)
}
}
Create product form :
import { Component } from '#angular/core';
import { FormBuilder, Validators } from '#angular/forms';
import { Route, Router } from '#angular/router';
import { ProductsService } from '../products.service'
#Component({
selector: 'app-create-product',
templateUrl: './create-product.component.html',
styleUrls: ['./create-product.component.css']
})
export class CreateProductComponent {
errors: string | undefined = undefined;
constructor(private fb : FormBuilder, private productService: ProductsService, private router : Router){}
createProductForm = this.fb.group({
title : [``, [Validators.required, Validators.maxLength(12)]],
imageUrl: [``, [ Validators.required]],
description: [``, [Validators.required, Validators.minLength(10)]]
})
createProduct(){
this.productService.createProduct(this.createProductForm.value).subscribe({
next: () => this.router.navigate([`/products`]),
error: (err) => {
this.errors = err.error?.error
}
})
this.createProductForm.reset()
}
}
I hope this makes it easier to everyone.
PP, this is my app interceptor :
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HTTP_INTERCEPTORS } from "#angular/common/http";
import { Injectable, Provider } from "#angular/core";
import { mergeMap, Observable, tap } from "rxjs";
#Injectable()
export class AppInterceptor implements HttpInterceptor{
accessToken:any | [] | null = localStorage.getItem(`accessToken`)
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
{
if(this.accessToken)
{
return next.handle(req.clone({ setHeaders: {'X-Authorization' : this.accessToken}}))
}
else
{
return next.handle(req.clone())
}
}
}
export const AppInterceptorProvider: Provider = {
provide:HTTP_INTERCEPTORS,
useClass: AppInterceptor,
multi:true
}
I hope I understood well your question. If not, let me know. I will edit my answer.
I could debug your App.
You save your accessToken in the local storage, but not the User object. When you refresh the browser, the "accessToken" is there, but the user not because it was removed from the browser's memory.
In this case, your server knows that you are logged in, but your app (frontend) not.
In the auth.service.ts you have to save the user:
localStorage.setItem(`user`, JSON.stringify(this.user));
In the app.component.ts you have to load the user again and set it into the userService instance:
const myUser: string= localStorage.getItem('user') + '';
const loadedUser: IUser = JSON.parse(myUser);
console.log('Loaded User: ' + loadedUser)
this.userService.setLoggedUser(loadedUser);
An alternative solution could be check in the app.component.tsthe presence of the accessToken. If it is there, you can call a new REST method on the server to get the logged user for that token.

How can I get my user data and send it to a component?

I have an application using laravel as backend and Angular in frontend.
What I want to do is to get the user data and output it anywhere on my website. For example I would like to get the name for the user and output it on the homepage when the user is logged in.
I can successfully register and log in a user. I can get the user data from my login method in the authService in the console.log. But how can I use that user data and get the user data from my getUser method? Is there any way for me to send the data from login method to the getUser method?
authService
import { Injectable } from '#angular/core';
import { User } from '../shared/user';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import {
HttpClient,
HttpHeaders,
HttpErrorResponse
} from '#angular/common/http';
import { Router } from '#angular/router';
#Injectable({
providedIn: 'root'
})
export class AuthService {
endpoint: string = `${environment.RECIPE_LIST_API}`;
headers = new HttpHeaders().set('Content-Type', 'application/json');
currentUser = {};
constructor(private http: HttpClient, public router: Router) {}
// Log in
login(user: User) {
return this.http
.post<any>(`${this.endpoint}/login`, user)
.subscribe((res: any) => {
console.log(res);
localStorage.setItem('access_token', res.token);
this.currentUser = res;
});
}
getUser() {
this.currentUser
// want to get my userdata here so that I can send it to any component
}
}
component that I want to send my data to
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { AuthService } from '../services/auth.service';
#Component({
selector: 'app-recipe-lists',
templateUrl: './recipe-lists.component.html',
styleUrls: ['./recipe-lists.component.css']
})
export class RecipeListsComponent implements OnInit {
currentUser: Object = {};
constructor(
public authService: AuthService,
private actRoute: ActivatedRoute
) {
this.authService.getUser();
}
ngOnInit(): void {}
}
user.ts
export class User {
email!: String;
password!: String;
}

Angular authentication and route guard

I'm building a angular spa front-end that consumes a GraphQL endpoint. After the users login, i set the token on localstorage and on my AuthService i set the auth state.My idea (coming from a React) was when the App component mounts ngOnInit i'll make a request for a me query who will return the user from the token stored on local storage, and i want to set the user on the AuthService. The problem i'm facing is that i've created a dashboard route which is protected, but the AuthGuard is not waiting for the App Component ngOnInit to finish and it will redirect to the login page.
import {Component, OnDestroy, OnInit} from '#angular/core';
import {MeGQL, User} from "../generated/graphql";
import {AuthService} from "./auth.service";
import {Router} from "#angular/router";
#Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit {
title = 'frontend';
loading: boolean = true
private meSubs: any;
constructor(private meQuery: MeGQL, private authService: AuthService, private router: Router) {
}
async ngOnInit() {
this.loading = true
console.log("MONTOU APP")
this.loading = true
return this.meQuery.fetch({}, {
fetchPolicy: "network-only",
}).toPromise()
.then(({data}) => {
console.log("ENTROU NO THEN")
if (data.me) {
console.log(data.me)
this.authService.setUser(data.me)
this.loading = false
}
}).catch(e => {
this.loading = false
console.log("ERROR: ", e)
})
}
}
{{ loading }}
<div *ngIf="loading">Carregando...</div>
<div *ngIf="!loading">
<router-outlet></router-outlet>
</div>
import { Injectable } from '#angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from "#angular/router";
import {AuthService} from "../auth.service";
import {Observable} from "rxjs";
#Injectable({
providedIn: 'root'
})
export class AuthGuardService implements CanActivate{
constructor(private authService: AuthService, private router: Router) { }
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean > {
console.log("Auth Guard user mount")
if(!this.authService.isAuthenticated()) {
console.log("Não autenticado")
await this.router.navigate(['/login'])
return false
}
return true
}
}
import {Injectable} from '#angular/core';
import {User, MeQuery, MeDocument, MeQueryVariables} from "../generated/graphql";
import {BehaviorSubject} from "rxjs";
import {Apollo} from "apollo-angular";
export type CurrentUserType = Pick<User, 'id' | 'name' | 'email' | 'active' | 'type'>
#Injectable({
providedIn: 'root'
})
export class AuthService {
private TOKEN_KEY = "AGENDEI_TOKEN"
private currentUser: CurrentUserType | null = null
private _isAuthenticated = new BehaviorSubject(false);
private authSource = new BehaviorSubject<CurrentUserType | null>(null)
constructor(private apollo: Apollo) { }
loginUser(user: CurrentUserType, accessToken: string) {
localStorage.setItem(this.TOKEN_KEY, accessToken)
this.setUser(user)
this._isAuthenticated.next(true)
}
setUser(user: CurrentUserType) {
this.currentUser = user
}
async logout() {
localStorage.removeItem(this.TOKEN_KEY)
await this.apollo.getClient().resetStore()
this._isAuthenticated.next(false);
}
public isAuthenticated(): Boolean {
return this._isAuthenticated.value
}
public getUserFromMeQuery() {
return this.apollo.query<MeQuery, MeQueryVariables>({
query: MeDocument
}).toPromise()
}
}
I believe making changes in canActivate method in your guard service will work.
Cause While checking if a user is logged in or not you are not waiting for auth service to set the Authentication state.
AuthService
public isAuthenticated(): Promise<boolean> {
return this._isAuthenticated.toPromise();
}
AuthGuardService
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean > {
console.log("Auth Guard user mount")
const isAuthenticated = await this.authService.isAuthenticated()
if(!isAuthenticated) {
console.log("Não autenticado")
await this.router.navigate(['/login'])
return false
}
return true
}
Try to use await in ngOninit of AppComponent:
async ngOnInit() {
this.loading = true
console.log("MONTOU APP")
this.loading = true
let response;
try {
response = await this.meQuery.fetch({}, {
fetchPolicy: "network-only",
}).toPromise()
let {data} = response;
if (data.me) {
console.log(data.me)
this.authService.setUser(data.me)
}
this.loading = false
} catch (err) {
this.loading = false
console.log("ERROR: ", err)
}
}

Angular make canActivateGuard asynchronous

I have a web application where there is a JWT token passed to the admin service. This JWT comes from the query URL as there is a redirect from another application. The constructor in the service checks for that URL and set the token value to it (if that params is there).
The problem I am facing is that the canActivateGuard fires too early. When this is called the observable to get the JWT in the service is not solved yet, so the JWT is always not there when the guard is being fired.
I have figure out that to make this work isLoggedIn() in the AdminService has to become an observable that listen to the changes in the URL, and the canActivate() in the guard has to subscribe to it, but can not make this works.
The below code is what I got so far
// Admin Service
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import 'rxjs/Rx';
import {Router, ActivatedRoute, Params} from '#angular/router';
#Injectable()
export class AdminService {
token: string;
constructor(private activatedRoute: ActivatedRoute) {
activatedRoute.queryParams.subscribe(
(params) => {
console.log('queryParams', params);
if(localStorage.getItem('jwt')) {
this.token = localStorage.getItem('jwt');
}
else if(params['jwt']) {
localStorage.setItem('jwt', params['jwt']);
this.token = params['jwt'];
}
});
}
// Check that JWT is in local storage and valid
isLoggedin() {
return (localStorage.getItem('jwt') !== null && localStorage.getItem('jwt') !== 'undefined');
}
}
// Can Activate guard
// Note that this.authService.isLoggedIn() is called before the set JWT in the service is solved
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AdminService,
private router: Router
) {
}
canActivate() {
if (this.authService.isLoggedin()) {
console.log('all ok, proceed navigation to routed component')
return true;
}
else {
// start a new navigation to redirect to login page
this.router.navigate(['/unauthorized']);
return false;
}
}
}
using ActivatedRouteSnapshot and RouterStateSnapshot your problem will be get resolve, and you don't need to subscribe for JWT in your service.
Here is a sample of my code which i had used in my Angular2 application.
auth-guard.ts
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { AuthCookie } from '../shared/services/auth-cookies-handler';
#Injectable()
export default class AuthGuard implements CanActivate {
constructor(private router: Router, private _authCookie: AuthCookie) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
if (this._authCookie.getAuth()) {
return true;
}
else {
this.router.navigate(['/login']);
return false;
}
}
}
Hope this will help you.

Angular2: Passing Value from One Component to Another

I have successfully implemented a login service for my Angular 2 app. What I want to do now is provide the username of the logged-in user from that component to a separate component - specifically the chat component.That way I can display the user's name when they are chatting with another user. I'm still a little hazy on how you pass values like this from one component to another in Angular 2. Here is the code from my login component:
import { AuthenticationService } from './../../data/authentication.service';
import { AlertService } from './../../data/alert.service';
import { Component, OnInit, Output } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-login',
templateUrl: 'app/views/login/login.component.html',
styleUrls: ['app/views/login/login.component.css']
})
export class LoginComponent implements OnInit {
//#Output() username;
model: any = {};
loading = false;
constructor(
private router: Router,
private authenticationService: AuthenticationService,
private alertService: AlertService) { }
ngOnInit() {
// reset login status
this.authenticationService.logout();
}
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(
data => {
this.router.navigate(['/']);
console.log('User logged in as: ' + this.model.username);
},
error => {
this.alertService.error(error);
this.loading = false;
});
}
reqPasswordReset() {
let popup = document.getElementById('myPopup');
popup.classList.toggle('show');
}
}
This component make use of an authentication service, which looks like this:
import { LoginComponent } from './../views/login/login.component';
import { ContextMenu } from './../ui/context-menu.component';
import { Router, RouterLinkActive } from '#angular/router';
import { Injectable } from '#angular/core';
import { Http, Headers, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthenticationService {
constructor(private http: Http) {}
login(username: string, password: string) {
return this.http.post('/api/authenticate', JSON.stringify({ username: username, password: password }))
.map((response: Response) => {
// login successful if there's a jwt token in the response
let user = response.json();
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
}
});
}
isAuthenticated() {
if (localStorage.getItem('currentUser')) {
//console.log('User successfully authenticated...');
return true;
} else {
// console.log('User is not authenticated...');
return false;
}
}
logout() {
// remove user from local storage to log user out
localStorage.removeItem('currentUser');
console.log('User successfully logged out');
}
}
I'm able to successfully log to the console the username, so I know I'm capturing that value. What I'd like to do is now pass that value from "this.model.username" to my room component, which currently looks like this:
import { ChatService } from './../chat/chat.service';
import { User } from './../../views/user/user';
import { Component, OnInit, Input } from '#angular/core';
import { AuthenticationService } from './../../data/authentication.service';
import { Http, Headers, Response } from '#angular/http';
import { Router } from '#angular/router';
#Component({
selector: 'app-room',
templateUrl: './room.component.html',
styleUrls: ['./room.component.less']
})
export class RoomComponent implements OnInit {
otherImg = 'app/img/photo-ph.png';
// otherImg = 'app/img/portrait-place-holder.svg';
model: any;
loading = false;
others = [
{ id: 1, name: 'John Smith', avatar: 'app/img/photo-ph.png' },
{ id: 2, name: 'Javier Sanchez', avatar: 'app/img/photo-ph.png' }
];
user;
token;
name;
nickname;
constructor(private authenticationService: AuthenticationService,
private router: Router,
private http: Http,
private chatService: ChatService) { }
isLoggedIn() {
this.loading = true;
if (this.authenticationService.isAuthenticated()) {
return true;
}
}
gotoChat() {
this.chatService.gotoChat(this.user);
}
ngOnInit() {
}
}
The view for this component is looping through users (others) and displaying an icon for each "other" user. Here is that code:
<div *ngIf="isLoggedIn()" class="others">
<span *ngFor="let other of others"><i [ngClass]="'material-icons'" (click)="gotoChat()" [routerLink]="['/chat']">person</i></span>
<a [routerLink]="['/login']">Logout</a>
</div>
The ultimate goal here is for people to see others that are logged in, and be able to initiate a chat with them.
Where I'm stuck is in how to pass the value (this.model.username) that I'm capturing in the login component, down to the room component.
In the login method of your AuthenticationService you're storing the user object in local storage, you should also store it in the authentication service so that when it is injected into the component, you are able to access the user object.
You will need to add another method called getUsername to the AuthenticationService to get the username.
It will look something like this:
#Injectable()
export class AuthenticationService {
private username: string;
constructor(private http: Http) {}
login(username: string, password: string) {
return this.http.post('/api/authenticate', JSON.stringify({ username: username, password: password }))
.map((response: Response) => {
// login successful if there's a jwt token in the response
let user = response.json();
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
// store username
this.username = user.username;
}
});
}
getUsername(): string {
return this.username;
}
make use of #input tag in angular2 this will help you pass information between components
Look at this link for ref
http://www.thoughtdelimited.org/thoughts/post.cfm/learning-angular-2-tour-of-heroes-tutorial-lesson-5

Categories