I'm trying to implement authentification using firebase auth, the things is that when it comes to persistance i'm stuck.
I tried to use local storage as you can see below :
AuthService.ts :
import {HttpClient} from '#angular/common/http';
import { Injectable } from '#angular/core';
import {AngularFireAuth} from '#angular/fire/auth';
import {Router} from '#angular/router';
import firebase from 'firebase/app';
#Injectable({
providedIn: 'root'
})
export class AuthService {
userData: any;
constructor(private http: HttpClient, private firebaseAuth: AngularFireAuth, private router: Router) {
this.firebaseAuth.useEmulator('http://localhost:9099');
this.firebaseAuth.authState.subscribe(user => {
if (user) {
this.userData = user;
localStorage.setItem('user', JSON.stringify(this.userData));
JSON.parse(localStorage.getItem('user'));
} else {
localStorage.setItem('user', null);
JSON.parse(localStorage.getItem('user'));
}
});
this.firebaseAuth.onAuthStateChanged( user => {
console.log(user);
});
}
signIn(email: string, password: string): void {
this.firebaseAuth.setPersistence(firebase.auth.Auth.Persistence.LOCAL).then(() => {
this.firebaseAuth.signInWithEmailAndPassword(email, password)
.then(result => {
this.router.navigate(['/dashboard/accueil']);
console.log('Nice, it worked!');
})
.catch(error => {
console.log('Something went wrong:', error.message);
});
}).catch(error => {
console.log('Something went wrong:', error.message);
});
}
signOut(): void {
this.firebaseAuth.signOut().then(() => {
localStorage.removeItem('user');
this.router.navigate(['/dashboard/connexion']);
});
}
forgetPassword(email: string): void {
this.firebaseAuth.sendPasswordResetEmail(email).then(() => {
window.alert('Password reset email sent, check your inbox.');
}).catch((error) => {
window.alert(error);
});
}
get isLoggedIn(): boolean {
const user = JSON.parse(localStorage.getItem('user'));
return (user !== null) ? true : false;
}
}
But the problem is that firebaseAuth.onAuthStateChanged return null after refreshing the page with F5 even authState. It's like onAuthStateChange is losing his last state after refresh.
Note that i'm using firebase Emulator.
app.module.ts
Firebase module has been well import
AngularFireModule.initializeApp(environment.firebase),
AngularFireAuthModule,
You can take a look at the confing here :
environnement.ts
firebase: {
apiKey: 'xxxxxxxxxxxx',
projectId: 'xxxx'
}
Maybe it's link to this error that i'm getting in the web console when i'm refreshing the page :
POST https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo?key=xxxxxxx 400
Note that key is equal to my apiKey state in the config.
When i look closer here what the error say :
{
"error": {
"code": 400,
"message": "INVALID_ID_TOKEN",
"errors": [
{
"message": "INVALID_ID_TOKEN",
"domain": "global",
"reason": "invalid"
}
]
}
}
Any help would be greatly appreciate.
EDIT
Here what i get after refresh my page from onAuthStateChanged :
There is no 2 calls, only 1 that return false.
Firebase Auth will persist the login state by default, so you don't have to manually save the token into local storage. As Frank said, onAuthStateChanged will fire will user being null initially, but if the user is signed in, it will fire again shortly after that with the actual user value.
Following to the comment section of the accepted answer, I decided to highlight the fact that this appears to be en emulator issue.
This was driving me crazy. No matter what I did, even jumping up and down, the currentUser was always null. I saw the same behavior: onAuthStateChanged() got called only once and all I got was a null-user.
After removing my provider for the Auth-emulator, everything started to work without issues.
providers: [
// ...
{ provide: REGION, useValue: envFirebase.region},
// THE PROBLEM:
{
provide: USE_AUTH_EMULATOR,
useValue: envFirebase.useEmulators ? ['http://localhost:9099'] : undefined
},
],
Related
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)
I am trying to implement Firebase login/registration into my app using Angular and Ionic 4. I have the registration of my account working and forgetting the password working I can see the accounts in my firebase console. The issue I am having is when I try to log into that account I created. In the developer console I get https://imgur.com/a/WzRiwtn :
code: "auth/invalid-email"
message: "The email address is badly formatted."
Its saying the issue lies in my tab3.page.ts:22
Here is the code from that page
import { Component } from '#angular/core';
import { AlertController } from '#ionic/angular';
import { LoadingController, ToastController } from '#ionic/angular';
import { Router } from '#angular/router';
import { AngularFireAuth } from '#angular/fire/auth';
#Component({
selector: 'app-tab3',
templateUrl: 'tab3.page.html',
styleUrls: ['tab3.page.scss']
})
export class Tab3Page {
email: string = '';
password: string = '';
error: string = '';
constructor(private fireauth: AngularFireAuth,
private router: Router,
private toastController: ToastController,
public loadingController: LoadingController,
public alertController: AlertController) {
}
async openLoader() {
const loading = await this.loadingController.create({
message: 'Please Wait ...',
duration: 2000
});
await loading.present();
}
async closeLoading() {
return await this.loadingController.dismiss();
}
login() {
this.fireauth.auth.signInWithEmailAndPassword(this.email, this.password)
.then(res => {
if (res.user) {
console.log(res.user);
this.router.navigate(['/home']);
}
})
.catch(err => {
console.log(`login failed ${err}`);
this.error = err.message;
});
}
async presentToast(message, show_button, position, duration) {
const toast = await this.toastController.create({
message: message,
showCloseButton: show_button,
position: position,
duration: duration
});
toast.present();
}
}
I have been staring at this since Friday trying multiple different methods and guides online and every method I try I am getting this error any help would be VERY much appreciated. This code came from following this https://enappd.com/blog/email-authentication-with-firebase-in-ionic-4/38/ tutorial and even looking at his github and following it exactly I still come to this issue.
Here is your error type
https://firebase.google.com/docs/auth/admin/errors
Hopefully email which you are passing having issue. It should be proper string only.
From what you're showing here, email has an initial value of an empty string:
email: string = '';
And, from what I can see, it never changes value. So you're passing an empty string to signInWithEmailAndPassword, which isn't valid.
Firebase Authentication "auth/invalid-email" and "The email address is badly formatted."
Use an email format like:
test#test.com
test123#gmail.com
change ' ' to null.
better still update to
const [email, setEmail] = useState(null);
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)
}
I am completely new to Angular and I've created a project using SpringBoot 2.0.5.RELEASE, Angular 5 and spring data to build an end to end single page java web application. I use spring boot 1.5 to expose REST APIs and angular5 with routing to build the client that will consume the APIs exposed by the server.
I've defined this component:
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { User } from '../models/user.model';
import { UserService } from './user.service';
#Component({
templateUrl: './add-user.component.html'
})
export class AddUserComponent {
user: User = new User();
constructor(private router: Router, private userService: UserService) {
}
createUser(): void {
alert ('lala');
this.userService.createUser(this.user)
.subscribe( data => {
alert('User created successfully.');
});
}
}
in the page I can see the alert lala, but not 'User created successfully.' but I have no idea why
The link address when I create a user is this is this one http://localhost:4200/api/users
This is my proxy.config.json file:
{
"/api/*": {
"target": "http://localhost:8080/user-portal",
"secure": false
}
}
and from curl is fine :
curl -X POST -H "Content-Type: application/json" "http://localhost:8080/user-portal/api/users"
and user.service.ts:
import {Injectable} from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { User } from '../models/user.model';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
#Injectable()
export class UserService {
constructor(private http: HttpClient) {}
private userUrl = '/api/users';
public getUsers() {
return this.http.get<User[]>(this.userUrl);
}
public deleteUser(user) {
return this.http.delete(this.userUrl + '/'+ user.id);
}
public createUser(user) {
return this.http.post<User>(this.userUrl, user);
}
}
Firstly, best not to use alert. Use console.log. Secondly, you are only handling success, you are not handling failure. Do this:
createUser(): void {
console.log('lala');
this.userService.createUser(this.user)
.subscribe(data => {
console.log('User created successfully', data);
},
err => {
console.log('There was an error', err);
},
() => {
console.log('I have completed now and nothing will ever be emitted from this Observable again');
});
}
The error handler will be executed if the HTTP response is not a success response, viz if the status code of the response is not in the 2xx range.
Check your browser network tab also to see if the HTTP request is failing.
You prob also want to debug this:
public createUser(user) {
console.log('userUrl', this.userUrl)
console.log('user', user)
return this.http.post<User>(this.userUrl, user);
}
To make sure all is as expected.
In Chrome hit F12 to open the dev tools and go to the network tab. Make sure that a request is being made to the end point and that it is not throwing and error.
I want to navigate my user on another route after login.
I found out in documentation that something like this should work for it correctly:
this.router.navigate(['/users']);
My full method in compoenent looks like:
// imports on top
import { Component } from '#angular/core';
import { LoginService } from './login.service';
import { Router } from '#angular/router';
// method somewhere below
login(event, username, password) {
event.preventDefault();
let body = JSON.stringify({ username, password });
this.loginService.login(body)
.then(res => localStorage.setItem('token', res.msg))
.catch(error => this.error = error);
this.router.navigate(['/users']);
}
However it doesn't redirect me. Basically route stays without change and no error spotted in console.
What am I doing wrong?
Edit:
My routes looks like:
const appRoutes: Routes = [
{ path: 'login', component: LoginComponent},
// users route protected by auth guard
{ path: 'users', component: UsersComponent, canActivate: [AuthGuard] },
// { path: 'user/:id', component: HeroDetailComponent },
{ path: '**', redirectTo: 'users' }
];
My AuthGuard looks like:
export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate() {
if (localStorage.getItem('token')) {
// logged in so return true
return true;
}
// not logged in so redirect to login page
this.router.navigate(['/login']);
return false;
}
}
I had a very similar problem on my post. The problem is that there is a delay in authenticating your user but router.navigate executes instantly. As the users part of your site is protected and your technically still unauthenticated, it fails the redirect.
By redirecting on the .then it waits for your user to login, and then if successful it redirects your user.
Try using this:
this.loginService.login(body)
.then(
res => localStorage.setItem('token', res.msg)
this.router.navigate(['/users']);
)
.catch(error => this.error = error);
this.router.navigate(['/users']);
This should be called after localStorage.setItem(), inside the .then() function
If it still doesn't work, may you put the code of the class in your message please