Angular make canActivateGuard asynchronous - javascript

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.

Related

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 Universal, app will not load until clicking 'refresh' twice

I'm building an Angular Universal App. I recently added a route resolver and started seeing this weird behavior where when I run npm run dev:ssr, the page won't load until I click the reload button twice.
1st: click: browser spins and doesn't seem to timeout...
2nd click: page loads
Here is my github repo. I suspect it has something to do with my route resolver which simply fetches data from Firestore and places it in the TransferState.
Here's the resolver for convenience:
import { Inject, Injectable, PLATFORM_ID } from '#angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '#angular/router';
import { Observable, of } from 'rxjs';
import { isPlatformServer } from '#angular/common';
import { makeStateKey, TransferState } from "#angular/platform-browser";
import { Restaurant } from '../restaurants/Interfaces.model';
import { AngularFirestore } from '#angular/fire/firestore';
import { first, tap } from 'rxjs/operators';
#Injectable()
export class RestaurantResolver implements Resolve<Restaurant> {
constructor(
public afs: AngularFirestore,
private transferState: TransferState,
#Inject(PLATFORM_ID) private platformId) {
}
async resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Promise<Restaurant> {
console.log('platformId', this.platformId)
const rId = route.url[1].path;
const restaurantId = makeStateKey<Restaurant>("restaurant-" + rId);
if (this.transferState.hasKey(restaurantId)) {
console.log('has key', restaurantId)
const restaurant = this.transferState.get(restaurantId, null);
this.transferState.remove(restaurantId);
return restaurant;
}
else {
let result: Restaurant = (await this.afs.doc('restaurants/' + rId).get().toPromise()).data() as Restaurant
if (isPlatformServer(this.platformId)) {
this.transferState.set(restaurantId, result);
}
return result;
}
}
}
It turns out there is a bug in the AngularFire library where observables are not completing, this the given behavior.

Firebase: Keep user logged in angular 7

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

Angular 4: use loggedInMethod to change menu

i'm struggling to find a solution to this problem. I googled for hours but couldn't solve.
I need to change the menu item in my navbar template showing login vs logout when the user logs in and out.
I have 2 services, the authService which let the user login/logout and the sessionService which provides some basic functions to check if the session is set or not.
I want that every time i logIn or logout the user (so i update the localStorage in sessionService) my components which use this get updated too.
I tried with .subscribe, .map but cannot make this work. The only thing that make this work is call this.loggedIn.next(this._sessionService.isSetUserSession()); in login and logout methods.
Please, what am i doing wrong?
navbar.component.html
<div class="dropdown-menu" aria-labelledby="dropdownAccount">
<ng-template *ngIf="(loggedIn$ | async); else elseDiv;">
<a class="nav-link" href="javascript:void(0)" (click)="logout()">LOGOUT</a>
</ng-template>
<ng-template #elseDiv>
<a class="nav-link" href="javascript:void(0)"(click)="login(...);">
LOGIN
</a>
</ng-template>
</div>
navbar.component.ts
import {Component, OnInit} from '#angular/core';
import {AuthService} from '../services/auth.service';
#Component({
moduleId: module.id,
selector: 'app-nav-bar',
templateUrl: 'navbar.component.html',
styleUrls: ['./navbar.component.css'],
})
export class NavbarComponent implements OnInit {
isNavbarCollapsed = true;
loggedIn$: any;
constructor(
private authService: AuthService) {
}
ngOnInit() {
this.loggedIn$ = this.authService.isLoggedIn;
}
}
auth.service.ts
import {Injectable} from '#angular/core';
import {Http, Response} from '#angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import {IUser} from '../entities/user';
import {SessionService} from './session.service';
import {Router} from '#angular/router';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class AuthService {
private loggedIn: Subject<boolean> = new Subject<boolean>();
get isLoggedIn() {
return this.loggedIn.asObservable();
}
constructor(private _http: Http,
private _sessionService: SessionService,
private _router: Router) {
this.loggedIn.next(this._sessionService.isSetUserSession());
}
login(user: IUser) {
return this._http.get('assets/api/responseSuccess.json?email=' + user.email + '&password=' + user.password)
.map((responseLogin => {
const jsonResponse = responseLogin.json();
if (jsonResponse.response === 'success') {
const userResponse: IUser = jsonResponse.user;
this._sessionService.setUserSession(userResponse);
//this.loggedIn.next(this._sessionService.isSetUserSession()); ==> This makes this works but i don't want fo call this every time i change the session, i just want that the session syncs automatically
return true;
} else {
console.log("error loggin in");
return false;
}
}));
}
logout() {
this._sessionService.clearUserSession();
// this.loggedIn.next(this._sessionService.isSetUserSession()); =>> Same here
return this._router.navigate(['/']);
}
}
session.service.ts
import {Injectable} from '#angular/core';
import {IUser} from '../entities/user';
import {isNullOrUndefined} from 'util';
import {Subject} from "rxjs/Subject";
#Injectable()
export class SessionService {
isSetUserSession(): boolean {
return !!localStorage.getItem('user');
}
clearUserSession() {
localStorage.removeItem('user');
}
setUserSession(user: IUser) {
localStorage.setItem('user', JSON.stringify(user));
}
}
This can be achieved without using subject instead use Shared service
Have the variable in service as below,
export class DataService {
isLoggedIn: boolean = false;
}
In the component get and set it as below,
get data():string {
return this.dataService.isLoggedIn;
}
set data(value: string) {
this.dataService.isLoggedIn = value;
}
Update 1 : Alternatively it can updated via another service also as below,
#Injectable()
export class AuthService {
constructor(public dataService: DataService) { }
update(value){
console.log(value)
this.dataService.isLoggedIn = value;
}
}
LIVE DEMO
Please make following changes.
Use BehaviorSubject instead of Subject.
A BehaviorSubject holds one value. When it is subscribed it emits the value immediately. A Subject doesn't hold a value. When you set the subject variable, it doesn't reflect in the nav component.
auth.service.ts
private loggedIn = new BehaviorSubject<boolean>(false);
get isLoggedIn() {
return this.loggedIn.asObservable();
}
Set the loggedIn variable in login and logout functions.
login(user: IUser) {
return this._http.get('assets/api/responseSuccess.json?email=' + user.email + '&password=' + user.password)
.map((responseLogin => {
const jsonResponse = responseLogin.json();
if (jsonResponse.response === 'success') {
const userResponse: IUser = jsonResponse.user;
this._sessionService.setUserSession(userResponse);
this.loggedIn.next(this._sessionService.isSetUserSession());
return true;
} else {
console.log("error loggin in");
return false;
}
}));
}
logout() {
this._sessionService.clearUserSession();
this.loggedIn.next(this._sessionService.isSetUserSession());
return this._router.navigate(['/']);
}
in navbar.component.ts
replace loggedIn$: any; by
isLoggedIn$: Observable<boolean>;
An explanation as follows.
The BehaviorSubject keeps the latest value cached which is set when user log ins or logs out. So when an Observer subscribes to the isLoggedIn(), the cached value is going to be emitted right away, depending on user is signed in or not.
This code will work for sure, as I am using in my current project.

Struggling through AuthGuard in Angular

Tried to follow AuthGuard example available here:
http://www.sparkbit.pl/angular-2-route-guards-real-life-example/
Unfortunately, while trying to implement the ActivationGuard.ts file, I'm receiving few errors.
ERROR in C:/Users/app/src/app/ActivationGuard.ts (6,24): Cannot find name 'ActivatedRouteSna
pshot'.)
C:/Users/app/src/app/ActivationGuard.ts (6,55): Cannot find name 'RouterStateSnapshot'.)
C:/Users/app/src/app/ActivationGuard.ts (13,62): Cannot find name 'CurrentUserService'.)
C:/Users/app/src/app/ActivationGuard.ts (15,31): Cannot find name 'ActivatedRouteSnapshot'.)
C:/Users/app/src/app/ActivationGuard.ts (15,62): Cannot find name 'RouterStateSnapshot'.)
Which basically means that the elements inside the CanActivate interface and inside constructors are not defined.
routing file:
import { WorksheetAccessGuard } from "./ActivationGuard";
const appRoutes: Routes = [
{ path: '', component: LoginComponent },
{ path: 'app', component: AppComponent, canActivate: [WorksheetAccessGuard] },
{ path: '**', redirectTo: '' }
];
My question: From where could I get these missing elements?
Provided image of my IDE: (the red words are the missing ones)
EDIT
I have made a custom service. I'm not sure if its fine or not:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
#Injectable()
export class UserAuthenticationService {
isUserAuthenticated: boolean = false;
username: string;
constructor(private http: Http) {
}
authentication() {
this.http.get(`http://localhost/api/auth/isLogged/${this.username}`)
.subscribe(res => {
this.isUserAuthenticated = res.json();
},
err => {
console.error('An error occured.' + err);
});
}
}
Now I'm receiving some error inside the AuthGuard file:
ERROR PIC
**My main goal is checking with every component change (when user navigates over the page) if he is logged or not. If not - return him to the login page.
EDIT2
Can I just post all logic from the service in the AuthGuard file? It will look like:
import {Injectable} from '#angular/core';
import {Router, RouterStateSnapshot, ActivatedRouteSnapshot} from '#angular/router';
import {Observable} from 'rxjs/Observable';
import {UserAuthenticationService} from './UserAuthenticationService';
import {Http} from '#angular/http';
interface CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>|Promise<boolean>|boolean
}
#Injectable()
export class WorksheetAccessGuard implements CanActivate {
private static username: string;
isUserAuthenticated: boolean = false;
constructor(private router: Router, private userService: UserAuthenticationService, private http: Http) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
this.http.get(`http://localhost/api/auth/isLogged/${this.username}`)
.subscribe(res => {
this.isUserAuthenticated = res.json();
},
err => {
console.error('An error occured.' + err);
});
if (!this.isUserAuthenticated) {
this.router.navigate(['/']);
return false;
}
return true;
}
}
RouterStateSnapshot and ActivatedRouteSnapshot are imported from #angular/router, while the currentUser Service is supposed to be your own where you should store the authenticated state of your User (with a boolean for example).
You retrieve an instance of it through Dependency Injection in your guard's constructor like so :
import { CurrentUserService } from './path/to/your/service/file';
import { RouterStateSnapshot, ActivatedRouteSnapshot } from '#angular/router';
constructor(private userService: CurrentUserService)
{}
Your service needs to be provided in your module, (as well as your guard), and you need to have a property like this in your CurrentUserService :
CurrentUserService :
isAuthenticated: boolean = false;
That way, when you log in from your Login Component (I assume you have one), you can set the service property to true :
LoginComponent :
import { CurrentUserService } from './path/to/your/service/file';
constructor(private userService: CurrentUserService)
{}
login() {
... // Your existing code where you login on form submit or anything
this.userService.isAuthenticated = true;
}
EDIT :
Check out my example, it should fit for yours.
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (!this.authService.isAuthenticated) {
// Deny navigation and redirect to login
this.router.navigate(['/path/to/login']);
return false;
}
// Allow navigation (be careful that the guard always resolve a value)
return true;
}

Categories