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)
}
}
Related
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.
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;
}
I'm trying to start my angular app and i'm getting this error below for some reason. I tried removing the auth service provider from my component and removing also the auth service from my constractor inside my component, but nothing changed... I can't figure out what i'm doing wrong and i'm beginner on angular.
The error:
Can't resolve all parameters for AuthService: (?, ?, [object Object]).
My component:
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { AuthService } from '../../Services/auth.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
providers: [AuthService]
})
export class LoginComponent {
isUserLoggedIn: boolean;
emailAddress: string;
password: string;
invalidLogin: boolean;
invalidText: string;
constructor(private authService: AuthService, private router: Router) {
if (authService.getCurrentUser() != null) {
router.navigate(['/home']);
}
}
login() {
if (!this.authService.login(this.emailAddress.toString(), this.password.toString())) {
this.invalidLogin = true;
this.invalidText = "Wrong Email Address or password";
}
}
}
My service:
import { Inject } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Router } from '#angular/router';
import { UserModel } from "../Models/UserModel";
export class AuthService {
constructor(private router: Router, private http: HttpClient, #Inject('BASE_URL') private baseUrl: string) {}
login(email: string, password: string) {
const headers = {'Content-Type': 'application/json', };
const body = { emailAddress: email, password: password };
let userLoggedIn: Boolean = false;
this.http.post<any>(this.baseUrl + "Api/Login", body, { headers }).subscribe(response => {
if (response.username != null) {
let user: UserModel
user = response;
this.router.navigate(['/home']);
this.saveUserToLocalStorage(user);
userLoggedIn = true
}
}, error => console.error(error));
return userLoggedIn;
}
saveUserToLocalStorage(loggedInUser: UserModel) {
sessionStorage.setItem("loggedInUser", JSON.stringify(loggedInUser));
}
getCurrentUser() {
return sessionStorage.getItem("loggedInUser")
}
logout() {
sessionStorage.removeItem("loggedInUser")
this.router.navigate([''])
}
createUser(userData: UserModel) {}
sendResetPasswordEmail() {}
}
I think this is because you use Router in your constructor, try to do this
constructor(private readonly injector: Injector,
...) {}
public get router(): Router {
return this.injector.get(Router);
}
I found what was the problem. I had to mark my service class as #Injectable()
Like this:
#Injectable()
export class SchoolService {
...
}
I use canActivate in history.guard and how can I check if the user login or not!
the value which I console always return false!
Do I need to create a new function in auth.service or just edit in history.guard ? Is there any way instead of using subscribe ??
auth.service.ts
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { Subject } from 'rxjs/Subject';
import { ApiService, VERSION, ENDPOINT } from '../api/api.service';
import { Observable, BehaviorSubject } from 'rxjs';
#Injectable()
export class AuthService {
logger = new BehaviorSubject<Object>(false);
referralRoute: string;
constructor(
private router: Router,
private api: ApiService
) {
}
logout() {
localStorage.removeItem('access-token');
localStorage.removeItem('uid');
localStorage.removeItem('client');
this.redirectToLogin();
this.logger.next(false);
}
postLogin(body: any) {
this.api.get(['token.json'], {}).subscribe(
(res: any) => {
localStorage.setItem('access-token', res['access-token']);
localStorage.setItem('uid', res['uid']);
localStorage.setItem('client', res['client']);
this.logger.next(true);
this.redirectToPrevStep();
},
(err) => {
this.logger.next(err);
});
}
checkLogin(body: any) {
this.api.get([VERSION, ENDPOINT.checkLogin], {}).subscribe(
(res: any) => {
this.logger.next(true);
},
(err) => {
this.logger.next(err);
});
}
checkUserLogin() {
const isLogin = !!localStorage.getItem('JWT_TOKEN');
if (isLogin) {
this.logger.next(true);
} else {
this.logger.next(false);
}
}
subscribeLogger(): Observable<Object> {
return this.logger.asObservable();
}
isAuthenticated() {
const token = localStorage.getItem('access-token');
let isAuthenticated: boolean;
if (this.isTokenInvalid()) {
localStorage.removeItem('access-token');
isAuthenticated = false;
} else {
isAuthenticated = true;
}
return isAuthenticated;
}
getUserInfo() {
const token = localStorage.getItem('access-token');
// let userInfo = this.jwtHelper.decodeToken(token);
return {};
// this.jwtHelper.decodeToken(token),
// this.jwtHelper.getTokenExpirationDate(token),
// this.jwtHelper.isTokenExpired(token)
// );
}
isTokenInvalid() {
const token = localStorage.getItem('access-token');
if (!token) {
return true
} else {
// this.api.setHeaders(token);
return false;
}
}
/**
* Helper method for set up referral route, enable useful redirect after login
* #method setRoute
* #param {string} route Route as defined in app.routes, eg. /user/1
*/
setRoute(route: string): void {
this.referralRoute = route;
}
redirectToPrevStep() {
const route = this.referralRoute ? this.referralRoute : '/';
this.router.navigateByUrl(route);
}
redirectToLogin(current: string = '/') {
// Store current url as referral and use latter for login redirection
this.setRoute(current);
window.scroll(0, 0);
this.router.navigate(['/auth/login']);
}
}
history.guard.ts
import { Injectable } from '#angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { AuthService } from '../../core/service/auth/auth.service';
#Injectable({ providedIn: 'root' })
export class HistoryGuard implements CanActivate {
checkUserLogin: boolean;
constructor(
private router: Router,
private auth: AuthService
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const checkUserLogin = this.auth.subscribeLogger().subscribe(
(data: any) => {
this.checkUserLogin = data;
}
);
if (!this.checkUserLogin) {
return this.router.navigate(['mypage']);
}
else {
return this.checkUserLogin;
}
}
}
history.module.ts
import { NgModule } from '#angular/core';
import { HistoryComponent } from './history.component';
import { HistoryItemComponent } from './history-item/history-item.component';
import { RouterModule, Routes } from '#angular/router';
import { CommonModule } from '#angular/common';
import { HistoryGuard } from './history.guard';
const routes: Routes = [
{
path: '',
component: HistoryComponent,
canActivate: [HistoryGuard]
},
{
path: ':id',
component: HistoryItemComponent,
canActivate: [HistoryGuard]
}
];
#NgModule({
imports: [
CommonModule,
RouterModule.forChild(routes)
],
declarations: [HistoryComponent, HistoryItemComponent]
})
export class HistoryModule { }
Hi this how I implemented AuthGuard, you can check just if in local storage is a JWT token or not, because on logout you should delete jwt token from localStorage and that's it
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if (this.authService.isLoggedIn()) {
return true;
} else {
this.router.navigate(['/login']);
return false;
}
}
}
// Auth service
isLoggedIn() {
return Boolean(this.getToken());
}
getToken() {
return this.localStorage$.retrieve('authenticationToken');
}
logout() {
this.localStorage$.clear('authenticationtoken');
}
This is how your canActivate should look like:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.auth.subscribeLogger().pipe(
tap(login => {
if(!login) {
this.router.navigate(['mypage']); // If user is not logged in, just navigate away
}
})
);
}
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