Framework7, Firebase, VueJS Routes RequireAuth - firebase

This is my main.js
import Vue from 'vue';
import Framework7 from 'framework7/dist/js/framework7.js';
import Framework7Vue from 'framework7-vue/dist/framework7-vue.js';
import Routes from './routes.js';
import App from './app.vue';
import * as firebase from 'firebase';
Vue.use(Framework7Vue, Framework7);
var config = {
};
firebase.initializeApp(config);
new Vue({
el: '#app',
template: '<app/>',
framework7: {
id: 'io.framework7.testapp',
name: 'Framework7',
theme: 'auto',
routes: Routes,
},
components: {
app: App
}
});
console.log(Routes)
Routes.beforeEach((to, from, next) => {
// let currentUser = firebase.auth().currentUser;
console.log()
})
I tried for a few hours now to implement that a user needs to be Authenticated before he can enter the Home View. Otherwise he will be redirected to the login View.
Maybe anyone can help me.

The following changes to your main.js file should normally do the trick. (I don't know however, if the framework7 framework interacts with the router and may cause problems).
You use router.beforeEach() to check if the "target" needs the user to be authenticated (based on requiresAuth meta). If the user is not authenticated you need to redirect her/him to the signin page. For that you can use firebase.auth().currentUser. See the corresponding Firebase doc here.
import Vue from 'vue';
import VueRouter from 'vue-router';
import Framework7 from 'framework7/dist/js/framework7.js';
import Framework7Vue from 'framework7-vue/dist/framework7-vue.js';
import Routes from './routes.js';
import App from './app.vue';
import * as firebase from 'firebase';
Vue.use(Framework7Vue, Framework7);
Vue.use(VueRouter);
var config = {
};
firebase.initializeApp(config);
const router = new VueRouter({
Routes,
mode: 'history'
});
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
const currentUser = firebase.auth().currentUser
if (requiresAuth && !currentUser) {
next('/signin')
} else if (requiresAuth && currentUser) {
next()
} else {
next()
}
})
new Vue({
el: '#app',
template: '<app/>',
framework7: {
id: 'io.framework7.testapp',
name: 'Framework7',
theme: 'auto',
routes: VueRouter,
},
components: {
app: App
}
});

I don't know anything about Vue Router but the firebase way to do this is to wait for onAuthStateChanged method to trigger and track user state with this function.
To wait and get user state, i wrote a piece of code that you can call before changing route.
var user, authPromiseResolver, authPromiseResolved
var authPromise = new Promise(function(resolve, reject){
authPromiseResolver = resolve
})
var waitForUser = function(){
return new Promise(function(resolve, reject){
if(!authPromiseResolved){
authPromise.then(resolve)
} else {
resolve(user)
}
})
}
firebaseApp.auth().onAuthStateChanged(function(firebaseUser) {
user = firebaseUser
if(!authPromiseResolved){
authPromiseResolver(user)
authPromiseResolved = true
}
})
waitForUser returns a promise who wait for first trigger of onAuthStateChanged (which will determine if an user is already logged in or not at page refresh), then resolve with user data or null it no user is logged in. So you just have to call this function before accessing each page :
waitForUser().then(function(user){
if(user) {
// An user is logged in
} else {
// Page unauthorized
}
})
Each time the user state change (user logout/login), user variable will be set to null or with firebaseUser object and waitForUser() will always be resolved with the current user state

Related

Protect pages from not logged in user in Nextjs

I am creating a login page and dashboard for the admin panel using NExtjS and react-redux. Below is the code I have tried. If I login using Id and password I can login and get all the values from the state and everything works fine.
The problem is if I tried to access the dashboard URL directly it says
Cannot read properties of null (reading 'name') how can I redirect the user to the login page instead of getting up to return statement ???
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
const Dashboard = () => {
const { auth } = useSelector((state) => state);
const router = useRouter();
console.log(auth)
// I can get all the objects from state and cookies are set as state for browser reload so everything is fine here.
useEffect(() => {
if (!auth.userInfo && auth.userInfo.role == 'user') {
router.push('/admin');
console.log('I am here');
}
}, []);
return <h1>{auth.userInfo.name}</h1>;
};
export default dynamic(() => Promise.resolve(Dashboard), { ssr: false });
Finally I find the correct way of solving this issue. The correct way was:
export const getServerSideProps = async (context) => {
const session = await getSession({ req: context.req });
if (session) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
return {
props: {
session,
},
};
};

Vue + Pinia + Firebase Authentication: Fetch currentUser before Route Guard

Recently I started to use Pinia as a global store for my Vue 3 Project. I use Firebase for the user authentication and am trying to load the current user before Vue is initialized. Ideally everything auth related should be in a single file with a Pinia Store. Unfortunately (unlike Vuex) the Pinia instance needs to be passed to the Vue instance before I can use any action and I believe that is the problem. On first load the user object in the store is empty for a short moment.
This is the store action that is binding the user (using the new Firebase Web v9 Beta) in auth.js
import { defineStore } from "pinia";
import { firebaseApp } from "#/services/firebase";
import {
getAuth,
onAuthStateChanged,
getIdTokenResult,
} from "firebase/auth";
const auth = getAuth(firebaseApp);
export const useAuth = defineStore({
id: "auth",
state() {
return {
user: {},
token: {},
};
},
actions: {
bindUser() {
return new Promise((resolve, reject) => {
onAuthStateChanged(
auth,
async (user) => {
this.user = user;
if (user) this.token = await getIdTokenResult(user);
resolve();
},
reject()
);
});
},
// ...
}})
and this is my main.js file
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
import { useAuth } from "#/store/auth";
(async () => {
const app = createApp(App).use(router).use(createPinia());
const auth = useAuth();
auth.bindUser();
app.mount("#app");
})();
How can I set the user before anything else happens?
I figured it out. Had to register the router after the async stuff
//main.js
(async () => {
const app = createApp(App);
app.use(createPinia());
const { bindUser } = useAuth();
await bindUser();
app.use(router);
app.mount("#app");
})();

Vue.js: How to protect login component from authenticated user

I'm designing an application where auth is implemented using firebase-auth. I have already protected the user component from unauthenticated users. But I now want to protect the login component from authenticated users. I've tried this so far. Here is my router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import {
fb
} from "#/firebase/init";
import Home from '#/views/Home'
Vue.use(VueRouter)
const routes = [{
path: '/',
name: "home",
component: Home
},
{
path: '/login',
name: 'login',
component: () => import('#/components/auth/Login'),
meta: {
alreadyAuth: true
}
},
{
path: '/signup',
name: 'signup',
component: () => import('#/components/auth/Signup'),
},
{
path: '/user/:id',
name: 'user',
component: () => import('#/components/user/User'),
meta: {
requiresAuth: true
}
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
router.beforeEach((to, from, next) => {
let user = fb.auth().currentUser
if (to.matched.some(rec => rec.meta.requiresAuth)) {
if (user) {
next()
} else {
next({
name: 'login'
})
}
} else if (to.matched.some(rec => rec.meta.alreadyAuth)) {
if (user) {
next({
name: 'user'
})
} else {
next()
}
} else {
next()
}
})
export default router
And one more problem is that when I'm authenticated and refresh the user component it redirects me back to the login component. Why so?
Thanks in advance
Since I don't know your main.js this is just an assumption, but I think currentUser is undefined. You should log user and check if it is defined on page load. Firebase acts async, so in order to have everything prepared you need to wrap your whole application in a firebase callback.
firebase.auth().onAuthStateChanged(() => {
new Vue({
router,
render: h => h(App)
}).$mount('#app');
});
Otherwise currentUser probably is not yet initialized.
So you want to block logged-in users from hitting your login route, but you also want to stop non-logged in users from hitting certain pages in your Vue app. Pretty standard use case. Here's how I implemented it:
First, my routes have meta tags requiresAuth and forVisitors. If the user is logged in and tries to hit a forVisitors route, I return the user to the homepage (routeObject.path = '/' in my example below). If the user is not logged in and tries to hit a requiresAuth path, I kick them to /login. I found this approach is pretty flexible for route construction.
My router needs to know what constitutes a logged-in user. Using Firebase's auth module, I can subscribe to onAuthStateChanged and figure out whether the user is logged in from the first emission.
Note, I store the current user in the VueX store (store.state.userModule.user), you can ignore that bit if you do not. Also I store the Firebase Auth object in my store as store.state.userModule.auth, so when you see that, it's the same as Firebase auth.
Additionally, you can find more details on why I wrap my onAuthStateChanged subscription in a Promise here: Getting 'Uncaught' after catching error in firebase.auth() with async
const router = new Router({
/* example of forVisitors path */
{
path: '/Login',
name: 'Login',
component: Login,
meta: {
requiresAuth: false,
forVisitors: true
}
},
/* example of requiresAuth path */
{
path: '/Submit/:mode?/:primary?/:secondary?',
name: 'Submit',
component: Submit,
props: true,
meta: {
requiresAuth: true
}
}
});
router.beforeEach(async (to, from, next) => {
/* Check for a user in my store, or fallback to Firebase auth() user */
let currentUser =
store.state.userModule.user ||
(await new Promise((resolve) => {
store.state.userModule.auth().onAuthStateChanged(user => {
resolve(user);
});
}));
const requiresAuth = to.meta.requiresAuth;
const forVisitors = to.meta.forVisitors;
const routeObject = {};
if (forVisitors && currentUser) {
routeObject.path = '/';
} else if (requiresAuth && !currentUser) {
routeObject.path = '/login';
}
next(routeObject);
});

How do I redirect the vue router after a firebase sign out?

I'm using Firebase, FirebaseUI, Vue, vue router, and vuex.
I have a home view ('/') that shows a "Log In" button. At this point, the vuex state has a user variable set to null. When I click on it, it takes me to my login view ('/login'). Suspicious thing: Looking at Vue DevTools, home remains the active view.
I use FirebaseUI to handle my logging in stuff. Once successfully logged in, my site redirects to a console view ('/console') which has a "Log Out" button, and the user variable in vuex is set to an object. Suspicious thing: Looking at Vue DevTools, login is now the active view.
Clicking the "Log Out" button still keeps me in the '/console/ vue. Vuex has a user variable set to null.
Refreshing the page automatically redirects me to the '/login' view.
I'm wondering if the fact that the active view isn't correct is the root of this issue. I found other people with the same problem saying they weren't using the webpack correctly by importing their App.vue file as a js, but I'm importing it as a .vue file in my main.js file.
I'm also suspicious of the FirebaseUI uiConfig in my Login.vue file. I have to set signInSuccessUrl: '#/console', (note the #). If I don't have the #, it redirects me to home (url = http://localhost:8081/console#/).
App.Vue
<template>
<v-app>
<v-content>
<router-view></router-view>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'App',
methods: {
setUser: function() {
this.$store.dispatch('setUser');
}
},
data: () => ({
//
}),
created () {
// when the app is created run the set user method
// this uses Vuex to check if a user is signed in
// check out mutations in the store/index.js file
this.setUser();
},
};
</script>
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify'
import { store } from './store/index';
// Firebase App (the core Firebase SDK) is always required and
// must be listed before other Firebase SDKs
import firebase from "firebase/app";
// Add the Firebase services that you want to use
import "firebase/auth";
import "firebase/firestore";
var firebaseConfig = {
// I'm editing this part out
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
// Check before each page load whether the page requires authentication/
// if it does check whether the user is signed into the web app or
// redirect to the sign-in page to enable them to sign-in
router.beforeEach((to, from, next) => {
const currentUser = firebase.auth().currentUser;
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
if (requiresAuth && !currentUser) {
next('/login');
} else if (requiresAuth && currentUser) {
next();
} else {
next();
}
});
// Wrap the vue instance in a Firebase onAuthStateChanged method
// This stops the execution of the navigation guard 'beforeEach'
// method until the Firebase initialization ends
firebase.auth().onAuthStateChanged(function (user) {
user;
new Vue({
el: '#app',
store,
router,
vuetify,
render: h => h(App)
});
});
router\index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: () => import('../views/About.vue')
},
{
path: '/tryit',
name: 'tryit',
component: () => import('../views/TryIt.vue')
},
{
path: '/pricing',
name: 'pricing',
component: () => import('../views/Pricing.vue')
},
{
path: '/login',
name: 'login',
component: () => import('../views/Login.vue')
},
{
path: '/console',
name: 'console',
component: () => import('../views/Console.vue'),
meta: { requiresAuth: true }
}
]
const router = new VueRouter({
routes
})
export default router
store\index.js
import Vue from 'vue';
import Vuex from 'vuex';
import Firebase from 'firebase';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
user: null
},
getters: {
getUser: state => {
return state.user;
}
},
mutations: {
setUser: state => {
state.user = Firebase.auth().currentUser;
},
logoutUser: state => {
state.user = null;
}
},
actions: {
setUser: context => {
context.commit('setUser');
},
logoutUser: context => {
context.commit('logoutUser');
}
}
});
Login.vue
<template>
<div class="login">
<NavbarAnon />
<!-- The surrounding HTML is left untouched by FirebaseUI.
Your app may use that space for branding, controls and other customizations.-->
<h1>Welcome to My Awesome App</h1>
<div id="firebaseui-auth-container"></div>
<div id="loader">Loading...</div>
</div>
</template>
<script>
// # is an alias to /src
import NavbarAnon from '#/components/layout/NavbarAnon';
import 'firebaseui/dist/firebaseui.css';
// Firebase App (the core Firebase SDK) is always required and
// must be listed before other Firebase SDKs
var firebase = require("firebase/app");
// Add the Firebase products that you want to use
require("firebase/auth");
require("firebase/firestore");
var firebaseui = require('firebaseui');
export default {
name: 'login',
components: {
NavbarAnon
},
mounted() {
// Initialize the FirebaseUI Widget using Firebase.
let ui = firebaseui.auth.AuthUI.getInstance();
if (!ui) {
ui = new firebaseui.auth.AuthUI(firebase.auth());
}
var uiConfig = {
callbacks: {
signInSuccessWithAuthResult: function(authResult, redirectUrl) {
// User successfully signed in.
// Return type determines whether we continue the redirect automatically
// or whether we leave that to developer to handle.
authResult + redirectUrl;
return true;
},
uiShown: function() {
// The widget is rendered.
// Hide the loader.
document.getElementById('loader').style.display = 'none';
}
},
// Will use popup for IDP Providers sign-in flow instead of the default, redirect.
signInFlow: 'popup',
signInSuccessUrl: '#/console',
signInOptions: [
// Leave the lines as is for the providers you want to offer your users.
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.EmailAuthProvider.PROVIDER_ID
],
// Terms of service url.
tosUrl: '<your-tos-url>',
// Privacy policy url.
privacyPolicyUrl: '<your-privacy-policy-url>'
};
// The start method will wait until the DOM is loaded.
ui.start('#firebaseui-auth-container', uiConfig);
}
};
</script>
In the HeaderBar.vue (which has the logout button):
methods: {
signOutUser() {
//this.$emit("trying to sign out user");
firebase.auth().signOut().then(function() {
// Sign-out successful.
this.$store.dispatch('logoutUser');
this.$route.push('/');
}).catch(function(error) {
//this.$emit("signout error", error);
error;
// An error happened.
});
}
}

vue js router + firebase authentication

I use vue js 2.4.2 , vue router 2.7.0 , and firebase 4.3.0. I can't get the route authentication stuff to work. This is my current route.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
import Firebase from './firebase'
import Dashboard from './components/Dashboard.vue'
import Auth from './components/Auth.vue'
let router = new Router({
mode: 'history',
routes: [
{
path: '/',
component: Dashboard,
meta: {
auth: true
}
},
{
path: '/login',
component: Auth
}
]
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.auth)) {
if (!Firebase.auth().currentUser) {
next({
path: '/login'
})
} else {
next()
}
} else {
next()
}
})
export default router
But now everytime I go to / it redirects me back to /login, probably because the Firebase.auth().currentUser is null, even though I am in fact logged in. How do I fix this?
try using Firebase.auth().onAuthStateChanged instead of Firebase.auth().currentUser; it is the recommended way to get the current user.
Getting the user by currentUser may cause a problem similar to what you are seeing. This is from the official doc of firebase.
Note: currentUser might also be null because the auth object has not finished initializing. If you use an observer to keep track of the user's sign-in status, you don't need to handle this case.
Try to set you authentication logic like this:
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.auth)) {
Firebase.auth().onAuthStateChanged(function(user) {
if (!user) {
next({
path: '/login'
})
} else {
next()
}
}
} else {
next()
}
})

Resources