Role-Based Authentication vue js firebase - firebase

I'm use VueJS with Firebase and I have doctors, admins, patients. Patient users cannot access the doctor's router. I followed the source code here
https://github.com/softauthor/vuejs-firebase-role-based-auth?files=1
I can't get an error message but the patient can access the router doctor. is there anyone who can give me a solution for this
I corrected it so it doesn't work either
//router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import firebase from 'firebase'
import Login from '#/views/Login'
import Register from '#/views/Register'
import Admin from '#/views/Admin'
import Driver from '#/views/Doctor'
import Customer from '#/views/Patient'
import Home from '#/views/Home'
Vue.use(Router)
let router = new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
meta: {
guest: true
}
},
{
path: '/register',
name: 'register',
component: Register,
meta: {
guest: true
}
},
{
path: '/login',
name: 'login',
component: Login,
meta: {
guest: true
}
},
{
path: '/admin',
name: 'admin',
component: Admin,
meta: {
auth: true
}
},
{
path: '/doctor',
name: 'doctor',
component: Doctor,
meta: {
auth: true
}
},
{
path: '/patient',
name: 'patient',
component: Patient,
meta: {
auth: true
}
},
],
})
router.beforeEach((to, from, next) => {
firebase.auth().onAuthStateChanged(userAuth => {
if (userAuth) {
firebase.auth().currentUser.getIdTokenResult()
.then(then((idTokenResult) =>
{
if (!!idTokenResult.claims.patient) {
if (to.path !== '/patient')
return next({
path: '/patient',
})
} else if (!!idTokenResult.claims.admin) {
if (to.path !== '/admin')
return next({
path: '/admin',
})
} else if (!!idTokenResult.claims.driver) {
if (to.path !== '/doctor')
return next({
path: '/doctor',
})
}
})
} else {
if (to.matched.some(record => record.meta.auth)) {
next({
path: '/login',
query: {
redirect: to.fullPath
}
})
} else {
next()
}
}
})
next()
})
export default router
//functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp()
exports.AddUserRole = functions.auth.user().onCreate(async (authUser) => {
if (authUser.email) {
const customClaims = {
customer: true,
};
try {
var _ = await admin.auth().setCustomUserClaims(authUser.uid, customClaims)
return admin.firestore().collection("roles").doc(authUser.uid).set({
email: authUser.email,
role: customClaims
})
} catch (error) {
console.log(error)
}
}
});
exports.setUserRole = functions.https.onCall(async (data, context) => {
if (!context.auth.token.admin) return
try {
var _ = await admin.auth().setCustomUserClaims(data.uid, data.role)
return admin.firestore().collection("roles").doc(data.uid).update({
role: data.role
})
} catch (error) {
console.log(error)
}
});

firebase.auth().onAuthStateChanged is asynchronous, so next() at the end of your router guard gets invoked without waiting for firebase.auth().onAuthStateChanged to resolve, meaning your router guard lets everyone through.

Related

Vue3 + Firebase Authentication (onAuthStateChanged) Problem

I'm working on vue3 and firebase. I'm having a problem with the onAuthStateChanged.
If I make all the router without the children it works but, if I add /admin and then put the children routes there I cannot stay logged in anymore.
This is my main.js file:
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { auth } from './plugins/firebase'
import { onAuthStateChanged } from 'firebase/auth'
import App from './App.vue'
import router from './router'
import './assets/styles/index.css'
import { useAuthenticationStore } from './stores/authentication'
let newApp
onAuthStateChanged(auth, (user) => {
if (!newApp) {
newApp = createApp(App)
newApp.use(createPinia())
newApp.use(router)
const authStore = useAuthenticationStore()
authStore.user = user
newApp.mount('#app')
}
})
This is my router. You can see that I need to validate if the user is logged in inside /admin
//router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthenticationStore } from '../stores/authentication'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: () => import('../views/PublicView.vue'),
children: []
},
{
path: '/admin',
name: 'admin',
children: [
{
path: '',
name: 'dashboard',
component: () => import('../views/admin/HomeView.vue'),
meta: {
requiresAuth: true,
layout: 'PrivateLayout'
}
},
{
path: 'login',
name: 'login',
component: () =>
import('../views/admin/authentication/LoginView.vue'),
meta: {
requiresAuth: false,
layout: 'AuthenticationLayout'
}
},
{
path: 'password',
children: [
{
path: 'recovery',
name: 'password-recovery',
component: () =>
import(
'../views/admin/authentication/PasswordRecoveryView.vue'
),
meta: {
requiresAuth: false,
layout: 'AuthenticationLayout'
}
},
{
path: 'reset',
name: 'password-reset',
component: () =>
import('../views/admin/authentication/PasswordResetView.vue'),
meta: {
requiresAuth: false,
layout: 'AuthenticationLayout'
}
}
]
}
]
},
{
path: '/404',
component: () => import('../views/NotFound.vue')
},
{ path: '/:catchAll(.*)', redirect: '/404' }
]
})
router.beforeEach((to, from, next) => {
const authenticationStore = useAuthenticationStore()
const requiresAuth = to.matched.some((record) => record.meta.requiresAuth)
const isAuthenticated = authenticationStore.user !== null
if (to.path === '/admin/login' && isAuthenticated) {
next('/admin/dashboard')
}
if (requiresAuth && !isAuthenticated) {
next('/admin/login')
}
next()
})
export default router
//plugins/firebase.js
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
// Firebase configuration
const firebaseConfig = {
apiKey: import.meta.env.VITE_API_KEY,
authDomain: import.meta.env.VITE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_PROJECT_ID,
storageBucket: import.meta.env.VITE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_APP_ID,
measurementId: import.meta.env.VITE_MEASUREMENT_ID
}
// Initalize Firebase
const firebaseApp = initializeApp(firebaseConfig)
const auth = getAuth(firebaseApp)
export { auth }

Redirect function in Nuxt middleware is making state null

I have a Nuxt app in which everything works fine in middleware except when I use redirect.
When I comment the redirect('/admin') line it works fine even the state data is present when console logged. As soon as I uncomment the redirect line it makes the state null.
Please help if someone knows this issue. This exact code works in my other projects but not here.
This is my auth.js file in the middleware folder.
export default function ({ store, route, redirect }) {
const user = store.getters['user/user']
const blockRouteAdmin = /\/admin\/*/g
const blockRouteManager = /\/manager\/*/g
const path = ['/signup', '/login']
let value = path.includes(route.path)
if (user) {
if (user.isAdmin) {
if (!route.path.match(blockRouteAdmin)) {
redirect('/admin')
}
}
if (user.isManager) {
if (!route.path.match(blockRouteManager)) {
redirect('/manager')
}
}
if (user.isUser) {
if (
route.path.match(blockRouteAdmin) ||
route.path.match(blockRouteManager) ||
value
) {
console.log('isUser', user.isUser)
redirect('/')
}
}
}
if (!user) {
if (
route.path.match(blockRouteAdmin) ||
route.path.match(blockRouteManager)
) {
redirect('/')
} else {
redirect()
}
}
}
Here is my nuxt.config.js
export default {
// Target: https://go.nuxtjs.dev/config-target
target: 'static',
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'aitl',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' },
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: ['~/plugins/firebaseConfig.js'],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/buefy
'nuxt-buefy',
// https://go.nuxtjs.dev/pwa
'#nuxtjs/pwa',
// https://go.nuxtjs.dev/content
'#nuxt/content',
],
// PWA module configuration: https://go.nuxtjs.dev/pwa
pwa: {
manifest: {
lang: 'en',
},
},
// Content module configuration: https://go.nuxtjs.dev/config-content
content: {},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {},
}
My index.js inside store.
import { vuexfireMutations } from 'vuexfire'
import { getUserFromCookie } from '../helper/index.js'
export const mutations = {
...vuexfireMutations,
}
export const actions = {
async nuxtServerInit({ dispatch, commit }, { req }) {
try {
const user = getUserFromCookie(req)
if (user) {
await dispatch('user/setUSER', {
email: user.email,
isAdmin: user.admin,
isManager: user.manager,
isUser: user.user,
uid: user.user_id,
name: user.name,
})
}
} catch (err) {
console.log(err)
}
},
}
User.js in store folder
import { auth } from '../plugins/firebaseConfig'
import Cookies from 'js-cookie'
export const state = () => ({
user: null,
})
export const getters = {
user(state) {
return state.user
},
}
export const actions = {
async userlogin({ dispatch }, user) {
try {
const token = await auth.currentUser.getIdToken(true)
const userInfo = {
email: user.email,
isAdmin: user.admin,
isManager: user.manager,
isUser: user.user,
uid: user.uid,
name: user.displayName,
}
Cookies.set('access_token', token)
await dispatch('setUSER', userInfo)
} catch (err) {
console.log(err)
}
},
setUSER({ commit }, user) {
commit('setUSER', user)
},
}
export const mutations = {
setUSER(state, user) {
state.user = user
},
}
The issue was solved by going from target: 'static' to target: 'server', aka mirroring the settings of another working project.

NextAuth Credential provider with apollo client?

Using NextAuth for GraphQL authentication with Apollo client in Next.js encounter the error
Hooks can only be called inside of the body of a function.
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import { useMutation, useApolloClient } from '#apollo/client';
import { LOGIN_MUTATION } from '../../../graphql/mutations';
import { getErrorMessage } from '../../../lib';
export default (req, res) =>
NextAuth(req, res, {
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
identifier: { label: "Email", type: "text" },
password: { label: "Password", type: "password" }
},
authorize: async (credentials) => {
const client = useApolloClient();
const [errorMsg, setErrorMsg] = useState();
const [login] = useMutation(LOGIN_MUTATION);
try {
await client.resetStore();
const { data: { login: { user, jwt } } } = await login({
variables: {
identifier: credentials.identifier,
password: credentials.password
}
});
if (user) {
return user;
}
} catch (error) {
setErrorMsg(getErrorMessage(error));
}
}
})
],
site: process.env.NEXTAUTH_URL || "http://localhost:3000",
session: {
jwt: true,
maxAge: 1 * 3 * 60 * 60,
updateAge: 24 * 60 * 60,
},
callbacks: {},
pages: {
signIn: '/auth/signin'
},
debug: process.env.NODE_ENV === "development",
secret: process.env.NEXT_PUBLIC_AUTH_SECRET,
jwt: {
secret: process.env.NEXT_PUBLIC_JWT_SECRET,
}
});
I am wondering is there anyway to make this work with apollo?
Thank you for the helps.
As in the comments rightfully pointed out, you can't use hooks in server-side code. You would have to create a new ApolloClient like this:
const client = new ApolloClient()
Then you can do queries like this for example:
const { data } = await client.query({
query: "Your query",
variables: { someVariable: true }
});
Best would be the to move the creation of the client to a separate external file as a function and import it in your server-side code whenever needed. Like done here for example.
Edit:
As #rob-art correctly remarks in the comments, for a [mutation][2], the code should look more like this:
const { data } = await client.mutate({
mutation: "Your query",
variables: { someVariable: true }
});

How to create a route guard with Vue + Firebase (Google Identity Platform)

I've implemented Firebase (aka. Google Identity Platform) into my Vue project. I want to protect specific routes, so I've added the following:
// router/index.js
{
path: '/profile',
name: 'Profile',
component: Profile,
beforeEnter: (to, from, next) => {
if (firebase.auth().currentUser) {
next()
} else {
next({
path: '/login',
})
}
}
},
This works! However, it would become unmanageable if I did that for every route.
To make it tidy, I tried putting it into a function (within the route file and tried externally) but it won't work because Firebase hasn't been initialized at the time it is parsed so it throws an error saying to initialize Firebase first.
Firebase is initialised in my main.js file:
// main.js
// Firebase configuration
var firebaseConfig = {
// Config details redacted
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
Ideally what I am after is something similar to what Auth0 provides in there SDK example:
// router/index.js
//...some route
beforeEnter: authGuard()
Then authGuard would be in an external file. This file would hold the function that checks if a user is authenticated or not. Then I can add it to routes as needed.
Use beforeEach router hook and check for route metadata. Here is a sample code from one of my apps
let router = new Router({
routes: [
{path: '*', redirect: "/"},
{
path: "/",
name: 'login',
component: Login
},
{
path: "/register",
name: 'register',
component: Register,
},
{
path: "/home",
name: "home",
component: Home,
meta: {
requiresAuth: true
}
}
]
},
],
mode: 'history'
})
router.beforeEach((to, from, next) => {
let currentUser = firebase.auth().currentUser;
console.log("firebasedata",currentUser);
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!currentUser) {
next({
path: '/login',
query: {redirect: to.fullPath}
})
} else {
if(to.matched.some(record => record.name==='login')){
next({
path: '/home',
query: {redirect: to.fullPath}
})
}
else {
next();
}
}
} else {
next();
}
})
export default router
Import firebase Auth from your firebase config file, check if there is a current authenticated user in the cache. is there is then all routes can be accessed, if not use the "requiresAuth" variable to restrict access
import { auth } from '../plugins/firebase.js' //import auth from firebase config file
const routes = [
{
path: '/',
component: () => import('#/layouts/default/Default.vue'),
children: [
{
path: '',
name: 'login',
component: () => import('#/views/auth/Login.vue'),
},
{
path: '/register',
name: 'register',
component: () => import('#/views/auth/Register.vue'),
},
{
path: '/forgotPassword',
name: 'forgotPassword',
component: () => import('#/views/auth/ForgotPassword.vue'),
},
{
path: '/app',
name: 'app',
component: () => import('#/views/app/Dashboard.vue'),
meta: {
requiresAuth: true // the route you want to protect
},
},
],
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
//Protection code
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(x => x.meta.requiresAuth)
const user = auth.currentUser
if (requiresAuth && !user) next('/')
else if (requiresAuth && user) next()
else next()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

How to fix reloading issue of authentication on page reload reload, Firebase and Vuejs

Currently when I reload dashboard first its redirect to /login then /dashboard if user already login. Its look quite wired. How Can I fix so that its land directly to /dashboard if user logged in.
Created function in main.js
created: function() {
try {
firebase.initializeApp(firebaseConfig);
}
catch(error){
return;
}
const store = this.$store;
firebase.auth().onAuthStateChanged(function(user){
if(typeof user !== 'undefined' && user !== null){
store.dispatch('loginUserOnLoad', user);
}
});
}
LoginUserOnlOad action
loginUserOnLoad: function({ commit }, user){
commit('authUser',{
email: user.email,
fullname: 'Guest'
})
},
Here is complete router configuration,
Vue.use(Router);
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Welcome',
component: Welcome
},
{
path: '/tasks',
name: 'Tasks',
component: Layout,
meta: {
requireAuth: true
}
},
{
path: '/login',
name: 'Login',
component: Signin,
meta: {
guestAuth: true
}
},
{
path: '/register',
name: 'Signup',
component: Signup,
meta: {
guestAuth: true
}
},
{
path: '*',
name: 'NotFound',
component: NotFound
}
]
});
router.beforeEach((to, from, next) => {
const currentUser = firebase.auth.currentUser;
const requireAuth = to.matched.some(record => record.meta.requireAuth);
if(requireAuth && !currentUser){
next({ name: 'Login'});
}
else if(!requireAuth && currentUser){
next({ name: 'Tasks'});
}
else {
next();
}
});
export default router;
I don't know how you configure and export Firebase, but I think that you should modify your router code as follows (see comments in the code):
router.beforeEach((to, from, next) => {
//Instead of const currentUser = firebase.auth.currentUser; do
const currentUser = firebase.auth().currentUser;
const requireAuth = to.matched.some(record => record.meta.requireAuth);
if(requireAuth && !currentUser){
next({ name: 'Login'});
}
else if(!requireAuth && currentUser){
next({ name: 'Tasks'});
}
else {
next();
}
});
I did a mistake to initialize vue instance, I found solution from https://medium.com/#anas.mammeri/vue-2-firebase-how-to-build-a-vue-app-with-firebase-authentication-system-in-15-minutes-fdce6f289c3c

Resources