What's the best approach for user auth login? - javascript

AuthService
login(user: User){
return this.http.post<any>(`${this.API_URL}`, {
email: user.username,
password: user.password
});
}
isUserLoggedIn(){
return this.http.get<any>(`${this.API_URL}/1`);
}
logoff(){
//this.loggedIn.next(null);
this.router.navigate(['/login']);
}
LoginComponent
onSubmit(){
this.submitted = true;
this.authService.login(this.loginForm.value).subscribe(r => {
if(r.success){
this.router.navigate(['/home']);
}
});
}
Auth Guard
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): any{
return this.authService.isUserLoggedIn()
.pipe(
map(res => {
if (res.isAuth){
return true;
}
this.router.navigate(['/login']);
return false;
})
);
}
LoginComponent: sends the data to an API for login (username and password). The API will return success = true if the user exists on the database;
Guard: (isUserLoggedIn()) check if user is logged in
and if the session is still alive.
Is there a better approach for user authentication in the Angular 4+?
Is this approach used in most projects?

Maybe, you could store user's data in local storage when he logins the first time. Then use a BehaviorSubject from rxjs and convert it to Observable. So every time the user has any changes (language, first name, etc), in any parts of your app you can access to that information.
So a thing like that:
Login Component
this.userService.login(username, password).subscribe(s => {
this.userService.user = s;
this.router.navigate(['/home'])
})
User Service
private userSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
readonly currentUser$: Observable<any> = this.userSubject.asObservable();
set user(user: any) {
// manipulate your user info and put it in localStorage after
this.userSubject.next(user);
}
get user() {
return this.userSubject.value ? this.userSubject.value : window.localStorage.getItem('user');
}
Auth Guard
Now in your guard, you can check the current user from the get method or you can subscribe to user observable, it's your choice
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): any{
return this.userServivce.user ? true : false;
}
You can complicate your logic as you wish.

Related

Angular await service between components, Behavior Subject

I have an issue in my Angular web store when i refresh the window, i create a service that takes the user data from the server and then inject to the 'products' section with BehaviorSubject, my goal is to make just one request to the server:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable({
providedIn: 'root'
})
export class DataService {
private userId = new BehaviorSubject<any>('');
currentUserId = this.userId.asObservable();
constructor() { }
sendUserId(message: string){
this.userId.next(message)
}
}
This works fine but the problem is when i refresh the window in products section, in console i can see that the service takes the user data but when i getProducts() it throws an error, it seems like getProducts() makes the request before the service had the response, i need the user Id to make the products request. My question: Is there a way to await the response of BehaviorSubject and then make the getProducts() request?. This is the code in the products section:
ngOnInit(): void {
this._dataService.currentUserId.subscribe(userId => this.userId = userId);
if(this.userId.length === 0){
this.userService.getUserProfile().subscribe(
res => {
this.userDetails = res['user'];
this.userId = this.userDetails._id;
this.getProducts();
},
err => {
console.log(err);
}
);
} else {
this.getProducts();
}
}
As you can see, i do a condition to check if userId exists, if not i have to make a new user request, this fix the bug but i think there's a better way to solve this. Thanks in advance.
How about placing all your logic within the observer's next function as below:
this._dataService.currentUserId.subscribe(userId => {
if (userId.length === 0)
{
this.userService.getUserProfile().subscribe(
res => {
this.userDetails = res['user'];
this.userId = this.userDetails._id;
this.getProducts();
},
err => {
console.log(err);
}
);
} else
{
this.getProducts();
}
});

How to wait for firebase auth before starting angular app

I would like to display a small loading logo while the firebase authentication is retrieving a user token, before starting "for real" the single page application.
So far I have an authentication service :
constructor(
public afAuth: AngularFireAuth,
) {
this.afAuth.onAuthStateChanged(user => {
if (user) {
this.setCredentials(user)
}
})
}
setCredentials(user: firebase.User) {
return user.getIdTokenResult(true).then(idTokenResult => {
this.credentials = {
userId: idTokenResult.claims.id,
role: idTokenResult.claims.role,
token: idTokenResult.token,
};
// STARTS THE APPLICATION NOW ?
})
}
Is it possible to achieve such behavior ? I've read about APP_INITIALIZER without success. I want to avoid localstorage / session storage and instead rely solely on this initialization.
Update :
created an init function :
export function initApp(auth: AuthService, afAuth: AngularFireAuth) {
return () => {
return new Promise((resolve) => {
afAuth.user.pipe(
take(1),
).subscribe(user => {
if (user) {
auth.setCredentials(user)
.then(() => resolve())
} else {
resolve();
}
})
});
}
}
And edited AppModule providers:
providers: [
interceptorProviders /* my interceptors */,
{
provide: APP_INITIALIZER,
useFactory: initApp,
deps: [AuthService, AngularFireAuth],
multi: true
}
]
Still need to figure out how to add a waiting logo but it's another question. I'll update asap.
Answering to my own question
To summarize I wanted to make sure my token claims (role, and user id per say) associated with a firebase user were stored in my auth service before dealing with routing, because components inside these routes would use those credentials.
In the end I did not follow the APP_INITIALIZER that is not really a good solution.
Auth Service
private _credentials: BehaviorSubject<Credentials> = new BehaviorSubject<Credentials>(null);
public readonly credentials$: Observable<Credentials> = this._credentials.asObservable();
constructor(private afAuth: AngularFireAuth) {
this.afAuth.authState.subscribe(user => {
this._credentials.next(null);
if (user) {
user.getIdTokenResult().then(data => {
const credentials = {
role: data.claims.role,
token: data.token,
userId: data.claims.userId
}
this._credentials.next(credentials);
console.log(credentials);
})
} else {
this._credentials.next({role: null, token: null, userId: null});
}
})
}
get credentials(): Credentials {
return this._credentials.value;
}
Display a waiting spinner in app.component
Below prevents routes from displaying if credentials not set.
In the template :
<div *ngIf="!(credentials$ | async)" class="logged-wrapper">
<div class="spinner-wrapper">
<mat-spinner class="spinner"></mat-spinner>
</div>
</div>
<router-outlet *ngIf="(credentials$ | async)"></router-outlet>
In the component :
credentials$: Observable<any>;
constructor(
private auth: AuthService,
) {
this.credentials$ = this.auth.credentials$;
}
Auth Guard
The takewhile allows me to make sure my credentials are set before going further.
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot):Promise<boolean> {
return new Promise((resolve) => {
this.auth.credentials$.pipe(
takeWhile(credentials => credentials === null),
).subscribe({
complete: () => {
const credentials = this.auth.credentials
if (!credentials.role) {
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } })
resolve(false);
}
if (next.data.roles && next.data.roles.indexOf(credentials.role) === -1) {
this.router.navigate(['/']);
resolve(false);
}
resolve(true)
}
})
})
}
You should use your authentication service in a CanActivate router guard: https://angular.io/api/router/CanActivate
This means your AppModule will initially load and then your child route (ex. MainModule with router path '') has the guard. Then in AppModule you can check for the status of the service and show a loading information until MainModule is activated (when firebase auth is finished)

Object is exist with value, but when access the property returned undefined

i find weird things. I have AuthService which saves authentication needs of my apps, included authentication token.
#IonicPage()
#Component({
selector: 'page-login',
templateUrl: 'login.html',
})
export class LoginPage {
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl:ModalController,public auth: AuthService) {
}
ionViewDidLoad() {
console.log(this.auth)
console.log(this.auth.loggedIn)
if(this.auth.loggedIn){
console.log(this.auth);
this.navCtrl.push("TabsPage");
}
}
}
when i call
console.log(this.auth)
it returned authentication
buth when i call
console.log(this.auth.loggedIn)
it return null
this my auth.service.ts
import { Injectable, NgZone, Component } from '#angular/core';
import { Storage } from '#ionic/storage';
// Import AUTH_CONFIG, Auth0Cordova, and auth0.js
import { AUTH_CONFIG } from './auth.config';
import Auth0Cordova from '#auth0/cordova';
import * as auth0 from 'auth0-js';
#Injectable()
export class AuthService {
Auth0 = new auth0.WebAuth(AUTH_CONFIG);
Client = new Auth0Cordova(AUTH_CONFIG);
accessToken: string;
user: any;
loggedIn: boolean;
loading = true;
constructor(
public zone: NgZone,
private storage: Storage
) {
this.storage.get('profile').then(user => this.user = user);
this.storage.get('access_token').then(token => this.accessToken = token);
this.storage.get('expires_at').then(exp => {
this.loggedIn = Date.now() < JSON.parse(exp);
this.loading = false;
});
}
login() {
this.loading = true;
const options = {
scope: 'openid profile offline_access'
};
// Authorize login request with Auth0: open login page and get auth results
this.Client.authorize(options, (err, authResult) => {
if (err) {
throw err;
}
// Set access token
this.storage.set('access_token', authResult.accessToken);
this.accessToken = authResult.accessToken;
// Set access token expiration
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
this.storage.set('expires_at', expiresAt);
// Set logged in
this.loading = false;
this.loggedIn = true;
// Fetch user's profile info
this.Auth0.client.userInfo(this.accessToken, (err, profile) => {
if (err) {
throw err;
}
this.storage.set('profile', profile).then(val =>
this.zone.run(() => this.user = profile)
);
});
});
}
logout() {
this.storage.remove('profile');
this.storage.remove('access_token');
this.storage.remove('expires_at');
this.accessToken = null;
this.user = null;
this.loggedIn = false;
}
isLoggedIn() :boolean{
return this.loggedIn;
}
}
i'm using ionic3 and auth0 authentication, previously i think that was my fault to not use public identifier on my property. but when i change the property to public, or create getter method that returned the property it still not working at all.
This is due to when the chrome console evaluates the object. If you open the object in your console, you'll see a tiny blue info icon. This will say:
Value was evaluated just now
Basically what happens is that the object content changed between the time you logged it, and the time you opened it in your console.
The login action is asynchronous, which means that the loggedIn property on the auth object will be set after the ionViewDidLoad is called. Perhaps a good thing would be to set the auth inside an APP_INITIALIZER provider, or have some Observable on your auth on which you can listen for auth changes
1.you have colling this.loggedIn before assign so that it's undefined.
in your case write console.log(this.auth.loggedIn) after login. please check that scenario.
2.for now, assign some value for loggedIn variable then print it will print a value
loggedIn: boolean; => loggedIn: boolean=false;
then print a value it will work
in some other component
ngOnInit() {
console.log(this.auth.loggedIn)
}

Angular2: Router Event: NavigationCancel before Route Guard has resolved

I have an application with routes guarded by an AuthGuard that implements CanActivate. Can activate checks to see if user is logged in and then checks if configuration variables are set before returning true or false. If the user is signed in but configuration variables have not been sent, AuthGuard makes an http call to retrieve configuration setup and returns true once the http call has been resolved without error (false otherwise).
The issue is that the Router is cancelling the navigation to the requested route before the configuration call has been resolved.
Below is the AuthGuard canActivate method:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
let authenticated = this.isAuthenticated();
if (authenticated) {
console.log("User authenticated, checking for configs...");
if (this.config.defined) {
console.log("Config defined!");
return true;
} else {
***** HERE ******
console.log("Config not defined, setting configs...");
this.authService.setConfig()
.take(1)
.subscribe(
config => {
// SET CONFIG VARIABLES
console.log("Config variables set");
// allow route access
return true;
},
err => {
console.error(err);
this.router.navigate(['/Login']);
return false;
}
);
this.router.navigate(['/Login']);
return false;
}
} else {
console.log("User not authenticated, back to login");
this.router.navigate(['/Login']);
return false;
}
}
So when I am logged in and the config variables are not set when I try to access a page (i.e. I'm in logical block denoted by **** HERE ****), I see in the console:
Setting config...
NavigationCancel {id: 1, url: "/", reason: ""}
NavigationStart {id: 2, url: "/Login"}
RoutesRecognized {id: 2, url: "/Login", urlAfterRedirects: "/Login", state: RouterStateSnapshot}
NavigationEnd {id: 2, url: "/Login", urlAfterRedirects: "/Login"}
Config variables set
Before the AuthGuard config http call has a chance to resolve, the navigation is cancelled and the router redirects as if the AuthGuard had returned false. I would like to find a way to have AuthGuard return its result on resolution of the http call within.
If anyone else if having this problem, I solved the issue by replacing the contents of the else block (starting with *****HERE******) with the following:
return this.authService.setConfig()
.map(
config => {
// SET CONFIG VARIABLES
console.log("Config variables set");
// allow route access
return true;
})
.catch(err => {
console.error(err);
this.router.navigate(['/Login']);
return Observable.of(false);
});

How can I fire an app-wide event in Angular2 or observe a global variable

I have the following architecture:
Navbar is a component with:
loggedIn = false;
constructor(private authService: AuthService) {
this.loggedIn = authService.isAuthenticated();
}
displaying different links depending on the variable
the methods in authService :
isAuthenticated() : boolean {
return tokenNotExpired();
}
authenticate(email: string, password: string) : Promise<void> {
const headers = new Headers({
'Content-Type': 'application/json'});
const body = JSON.stringify({ email, password });
return this.http.post(`${apiUrl}/auth/login`, body, { headers })
.toPromise()
.then(response => {
const data = response.json();
this.user = data.user;
localStorage.setItem('id_token',data.token);
});
}
I want to get notified in navbar when isAuthenticated() returns another value.
Should I use an observable value in AuthService instead of just checking for the valid token? Should I emit an event in authenticate method's success?
I could only find info about parent-children event emmiters with #input.
Note: I am using angular2-jwt and the isAuthenticated() method is called from the auth.guard for protected routes.
You should definitely use an Observable subject in your AuthService. Take a look at this link from angular.io

Categories