How to save successful signed in result of Firebase auth? [duplicate] - javascript

I'm working in an Angular 10 project, I am also using firebase hosting and cloud firestore (for DB). I am using AngularFire in my project as well.
My project is already able to get documents from my firestore collection, and display them (also can edit, delete, and create them). I also set up authentication, where I use AngularFireAuth to sign in and sign out. I also have route guards to only allow users access to info after signing in.
I've discovered that Firestore also has rules, and that you should set them up to secure your collection. Currently, I basically have no rules (test mode), but I want to add a basic "only users can access anything" rule, but am running into an issue.
I think this is the issue, currently, after logging in my app will store the user in local storage. I think that I need to store this a different way so I am re-signed in from previously given creds instead of just checking if there local storage. I only get ERROR FirebaseError: Missing or insufficient permissions errors when my guard checks the local storage to ensure sign-in, if I sign-in first, I don't get the error.
So, how should I save user data so that I don't have to sign-in on every refresh, but that I can verify the auth to firebase? I know I could store the email/password to local storage and check to re sign-in, but that seems insecure to me.
I think the above is the issue, but not 100% sure.
This is my firestore rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
allow read, write: if request.auth != null
match /{document=**} {
allow read, write: if request.auth != null //should only allow users?
}
}
}
Here is my auth service (where I handle sign-in/sign-out and check if local storage has user.
export class AuthService {
constructor(private aFAuth: AngularFireAuth, public router: Router) {
//I honestly don't know if I need this
this.aFAuth.authState.subscribe((user) => {
if (user) {
localStorage.setItem('my-test-app-currentUser', JSON.stringify(user));
} else {
localStorage.setItem('my-test-app-currentUser', null);
}
});
}
async signIn(email: string, password: string) {
this.aFAuth
.signInWithEmailAndPassword(email, password).then((result) => {
localStorage.setItem('my-test-app-currentUser', JSON.stringify(result.user));
this.router.navigate(['']);
}).catch((error) => {
window.alert(error.message);
});
}
//this is the func that needs to change, if I have storage, I need to be able to sign-in with it again
isSignedIn(): boolean {
if (!localStorage.getItem('my-test-app-currentUser')) {
return false;
}
return true;
}
signOut() {
return this.aFAuth.signOut().then(() => {
localStorage.removeItem('my-test-app-currentUser');
window.alert('You have been signed-out');
});
}
}
Here my guard:
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// return true;
if (this.auth.isSignedIn()) {
return true;
} else {
this.router.navigate(["sign-in"]);
return false;
}
}
Any help would be appreciated.

Firebase already stores the user credentials in local storage, and automatically restores them when you reload the page.
Restoring them does require a check against the server though, so it happens asynchronously. For that reason, any code that depends on the user's authentication state should be inside the this.aFAuth.authState.subscribe handler, so that it runs whenever the authentication state changes.
So instead of handling the navigation when the signInWithEmailAndPassword call completes, which happens only when you actively sign the user in, the navigation should be in the auth listener, which runs both on active sign in and on a restore.
So something like:
export class AuthService {
constructor(private aFAuth: AngularFireAuth, public router: Router) {
//I honestly don't know if I need this
this.aFAuth.authState.subscribe((user) => {
if (user) {
localStorage.setItem('my-test-app-currentUser', JSON.stringify(user));
this.router.navigate(['']);
} else {
localStorage.setItem('my-test-app-currentUser', null);
}
});
}
async signIn(email: string, password: string) {
this.aFAuth
.signInWithEmailAndPassword(email, password).then((result) => {
window.alert(error.message);
});
}
...
In your canActivate you'll probably want to use the AngularFireAuthGuard. which ensures that unauthenticated users are not permitted to navigate to protected routes. I think this might replace your entire need for local storage.
Also see the AngularFire documentation on Getting started with Firebase Authentication and Route users with AngularFire guards.

Related

token validation in angular for protecting routing

can anyone let know how can we validate the token stored in local storage for routing protection
I have saw some of tutorials but all of them checking if there is any token is present in local storage or not like below4
export class AuthGuard implements CanActivate {
constructor(private routes: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable < boolean > | Promise < boolean > | boolean {
if (localStorage.getItem('token') != null) {
return true;
} else {
this.routes.navigate(['/login']);
return false;
}
}
}
and we can easily bypass this method by creating a token with random value
can anyone let me know more efficient way to validate the login token?
token validation in angular authguard
As rightly said by Heiko & Jimmy, its not the secure way to validate token at the client side.
However, assuming that you want to implement it at client side only without any server validation.
To do that in the client side
This is the best article I suggest
https://www.syncfusion.com/blogs/post/best-practices-for-jwt-authentication-in-angular-apps.aspx

Keep logged in between reloads - Firebase (Sign in with Google)

How do you keep a user logged in with Sign in with Google between reloads?
Here is my login function (Firebase):
const loginWithGoogle = async () => {
try {
const provider = new firebase.auth.GoogleAuthProvider();
const res = await firebase.auth().signInWithPopup(provider);
$user = res.user;
$isLoggedIn = true;
}
catch (error) {
console.log(error);
}
}
Although $isLoggedIn and the $user object save to LocalStorage (I'm using SvelteJS), the user does not actually stay logged in after reloading.
After reloading, admin users are no longer able to write to the database and I get the error firestore: PERMISSION_DENIED: Missing or insufficient permissions.
(Here are my firestore rules)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write: if (request.auth != null && (request.auth.uid == "someAdminID" || request.auth.uid == "otherAdminID"));
}
}
How would I stay logged in after reloading? Or is there some way to automatically log in again if the user had not previously logged out?
On most browser environments Firebase automatically persists the user credentials in local storage, and restores them when the app/page reloads. This requires it to make a call to the server however, a.o. to check if the account was disabled, and thus it isn't completed right away when the page loads.
To react to when the authentication state is restored (or otherwise changes), you'll want to use an auth state listener as shown in the first code snippet in the documentation on getting the currently signed in user:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
// 👈 This is where you can also query the database as the user for the first time
} else {
// User is signed out
// ...
}
});

Angular Universal loads login page for brief second

I recently enabled Angular Universal in my project. Everything works as expected, except for one strange issue. Whenever I refresh page or click a link in email that navigates to webpage, I see Login page for brief second then actual page loads.
Sample project created and uploaded to Github. Remember the delay may not as long as in my real project.
Github repo link: https://github.com/pavankjadda/angular-ssr-docker
Problem:
As it turns out when Ng Express Engine loads the web page, it does not have access to cookies. Hence it redirects user to Login page, but as soon as browser loads JavaScript (Angular), which checks for cookies and validates Authentication guards, redirects user to actual webpage. The ideal solution would be making cookies available on server side (sending it through request) and making sure Authentication guards passes. I tried send the cookies through server.ts, but couldn't get it working.
Work Around:
Until I figure out the solution here is the work around I followed. Whenever we check for cookies, determine if the platform is server, if yes return true. Here are the few places where you can make this change
Make sure authservice.ts returns true when the platform is server
/**
* Returns true if the 'isLoggedIn' cookie is 'true', otherwise returns false
*
* #author Pavan Kumar Jadda
* #since 1.0.0
*/
isUserLoggedIn(): boolean {
return isPlatformServer(this.platformId) || (this.cookieService.get('isLoggedIn') === 'true' && this.cookieService.check('X-Auth-Token'));
}
Do the same thing in Authentication guards
#Injectable({
providedIn: 'root'
})
export class CoreUserAuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router, #Inject(PLATFORM_ID) private platformId: any,) {
}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url: string = state.url;
return this.checkLogin(url);
}
/**
* Returns true if the user is Logged In and has Core user role
*
* #author Pavan Kumar Jadda
* #since 1.0.0
*/
private checkLogin(url: string): boolean {
// Return if the platform is server
if (isPlatformServer(this.platformId))
return true;
if (this.authService.isUserLoggedIn() && this.authService.hasCoreUserRole()) {
return true;
}
if (this.authService.isUserLoggedIn() && !this.authService.hasCoreUserRole()) {
this.router.navigate(['/unauthorized']);
}
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Navigate to the login page with extras
this.router.navigate(['/login']);
return false;
}
}
Note: Added work around here, in case if anyone has similar problem. When I have an actual solution to the problem, I will update this answer.
change your isUserLoggedIn() function in auth service to:
public async isUserLoggedIn(): Promise<boolean> {
const isLoggedIn = await this.cookieService.check("token");
return isLoggedIn;
}

Validate displayName firebase authentication

I'm currently working with Firebase Authentication and absolutely loving it. The only problem right now is that I'm trying to validate the user's display name using my own rules. What I have tried is to use Cloud Firestore to store the display name and other related info such as address, etc. On registration, the user has to declare his/her display name as well as email and password. Somewhere in my code I'm doing this:
try {
await firebase.auth().createUserWithEmailAndPassword(email.value, password.value);
await firebase
.firestore()
.collection('/users')
.doc(firebase.auth().currentUser.uid)
.set({
displayName: displayName.value,
});
} catch (err) {
setIsLoading(false);
setErrorText(err.message);
}
The problem is that the app also redirects the user when he/she finishes his/her registration to the private area like so:
useEffect(() => {
if (authUser) history.push('/dashboard');
}, [authUser, history]);
(authUser is stored in Redux and is updated inside onAuthStateChanged()) The display name won't be validated because as soon as firebase.auth().createUserWithEmailAndPassword() resolves, authUser has a value and immediately redirects the user. It's kind of annoying and would love to know whether there's a better way of organising this bit of code.
Any help is greatly appreciated!
Edit: Firestore rules:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{documentId} {
allow read: if request.auth != null
allow write: if request.auth != null &&
request.resource.data.displayName is string &&
request.resource.data.displayName.size() >= 6 &&
request.resource.data.displayName.size() <= 20
}
}
}

Firebase Current User Undefined when Web Page is Refreshed

My scenario is simple. I created a Firebase web app and I connect using a Google account. The issue is that I need to log back in every time the page is refreshed, here are the steps :
Initialize Firebase
Signup with Google
Check the current user - it is an authentified Google user
Refresh the page
Initialize Firebase
Check the current user - it is undefined
The code is straightforward:
firebase.initializeApp(config);
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider);
...
public onAuthStateChanged(context: any, user: any) {
if (user) { ...
...
//currentUser is defined
get currentUser(): any {
return firebase.auth().currentUser;
}
Refresh the page
//currentUser is undefined
get currentUser(): any {
return firebase.auth().currentUser;
}
...
if(!currentUser) {
firebase.auth().signOut();
firebase.initializeApp(config);
}
I am aware of options in the persistence of the Firebase sessions but my understanding is that this behavior is not the default. Cf. the doc:
https://firebase.google.com/docs/auth/web/auth-state-persistence
I added this line to my code just in case, it makes no difference:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
I also checked that the same happens with Anonymous authentication.
Every time you call signInWithPopup(...) it will show a pop up window and ask the user to sig in. For that reason you should only call this method once you detect that the user isn't signed in. The easiest way to do this, is to call it from your onAuthStateChanged callback:
firebase.initializeApp(config);
var provider = new firebase.auth.GoogleAuthProvider();
...
public onAuthStateChanged(context: any, user: any) {
if (user) {
console.log(user);
...
}
else {
firebase.auth().signInWithPopup(provider);
}
...
}

Categories

Resources