vue js router + firebase authentication - firebase

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()
}
})

Related

check required auth in vue beforeEach method with firebase v9

i want to check if user exist before go to some pages in beforeEach method
i export the user state i use firebase v9
export const userAuthState = ()=>{
let currentUser = null;
onAuthStateChanged(auth, (user) => {
if (user) {
currentUser = user;
}
});
return currentUser;
}
here where i use it
import {userAuthState} from 'src/backend/firebase-config';
...
console.log("before route");
Router.beforeEach(async (to,from,next)=>{
if(await !userAuthState() && to.meta.requiresAuth){
next({path: 'login', query:{ redirect: to.fullPath }})
}else if(await userAuthState() && to.meta.requiresAuth == 'login'){
next({path:'/'})
}else{
next()
}
})
here the problem cant navigate to any page and print the console.log many times
how i can check the user before route in correct way
thank you.
I'll give you a simple example of how can you make some decision based on user authentication.
For this, I'll use Vuex as a central store since you'll commonly use user information across all your app. I'll assume that you're building an SPA with Vue and Firebase V9.
This is a simple store for users. Register this store with Vue (with .use()) in your main.js file (your entry point file).
import { createStore } from 'vuex'
const Store = createStore({
state() {
return {
user: {
uid: '',
email: ''
}
}
},
mutations: {
setUser (state, payload) {
if (payload) {
state.user.uid = payload.uid
state.user.email = payload.email
return
}
state.user.uid = ''
state.user.email = ''
}
}
})
export Store
Now, at your App.vue (or your root component) you simple call onAuthStateChanged and run commits depending on User's state:
<template>
<div>Your wonderful template...</div>
</template>
<script>
import { onAuthStateChanged } from "firebase/auth";
import { yourAuthService } from 'yourFirebaseInit'
export default {
name: 'App',
created () {
onAuthStateChanged(yourAuthService, (user) => {
if (user) {
this.$store.commit('setUser', { uid: user.uid, email: user.email })
} else {
this.$store.commit('setUser', null)
}
})
}
}
</script>
Finally, in your routes, you could do something like:
// we import the Store that we've created above.
import { Store } from 'your-store-path'
Router.beforeEach((to,from,next)=>{
if(to.meta.requiresAuth && Store.state.user.uid === ''){
next({path: 'login', query:{ redirect: to.fullPath }})
} else{
next()
}
})
This is a simple example of how can you implement an Authentication flow with Vue and Firebase V9.

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.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.
});
}
}

Framework7, Firebase, VueJS Routes RequireAuth

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

Resources