Guide: How to use Pinia with Quasar and persist the state when page refreshes + bonus Firebase user init example #12539
Replies: 1 comment
-
|
BenJackGill, I'm not a firebase expert, but I'll try to convert what i learned with other SSO integrations. First of all, u would not persist the token in a non secure cookie or even in local storage, just keep it in memory. Said that, let's configure u firebase auth to persist the session in a secure cookie.: create a middleware to auth.: yarn add firebase-admin
quasar new ssrMiddleware authsrc-ssr/middlewares/auth.ts /* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { initializeApp, applicationDefault } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import { ssrMiddleware } from 'quasar/wrappers';
export default ssrMiddleware(async ({ app /*, resolveUrlPath, publicPath, render */ }) => {
const fbApp = initializeApp({
credential: applicationDefault(),
databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
});
const fbAuth = getAuth(fbApp);
app.post('/sessionLogin', async (req, res) => {
const idToken: string = req.body.idToken.toString();
const csrfToken: string = req.body.csrfToken.toString();
if (csrfToken !== req.cookies.csrfToken) {
res.status(401).send('UNAUTHORIZED REQUEST!');
return;
}
const expiresIn = 60 * 60 * 24 * 5 * 1000;
try {
const decodedIdToken = await fbAuth.verifyIdToken(idToken)
if (new Date().getTime() / 1000 - decodedIdToken.auth_time < 5 * 60) {
const sessionCookie = await fbAuth.createSessionCookie(idToken, { expiresIn })
res.cookie('session', sessionCookie, {
path: '/',
maxAge: expiresIn,
httpOnly: true,
secure: true,
sameSite: 'lax'
});
res.end(JSON.stringify({ status: 'success' }));
} else {
res.status(401).send('Recent sign in required!');
}
} catch (error) {
res.status(401).send('UNAUTHORIZED REQUEST!');
}
});
app.post('/profile', async (req, res) => {
const sessionCookie: string = req.cookies.session || '';
try {
const decodedClaims = await fbAuth.verifySessionCookie(sessionCookie, true /** checkRevoked */)
res.json(decodedClaims);
} catch (error) {
res.status(401).send('Recent sign in required!');
}
});
app.post('/sessionLogout', async (req, res) => {
const sessionCookie: string = req.cookies.session || '';
res.clearCookie('session');
try {
const decodedClaims = await fbAuth.verifySessionCookie(sessionCookie);
await fbAuth.revokeRefreshTokens(decodedClaims.sub);
res.status(200).send()
} catch (error) {
res.status(200).send()
}
});
})so create a both to retrive the profile: import { boot } from 'quasar/wrappers';
import { FirebaseApp, initializeApp } from 'firebase/app';
import { getAuth, Auth } from 'firebase/auth';
import useAuthStore from 'src/stores/auth';
import { storeToRefs } from 'pinia';
import axios from 'axios';
import Cookies from 'quasar';
declare module 'pinia' {
export interface PiniaCustomProperties {
$fbApp: FirebaseApp;
$fbAuth: Auth;
}
}
export const serverUserKey: InjectionKey<Record<string, never>> = Symbol('server-user');
export const fbAppKey: InjectionKey<FirebaseApp> = Symbol('fb-app-key');
export const fbAuthKey: InjectionKey<Auth> = Symbol('fb-auth-key');
export default boot(async ({ store, app, ssrContext }) => {
const firebaseConfig = {/*...*/}
const client = initializeApp(firebaseConfig);
const auth = getAuth(client);
app.provide(fbAppKey, client);
app.provide(fbAuthKey, auth);
store.use(() => ({
$fbApp: client,
$fbAuth: auth,
}));
if (process.env.CLIENT) {
auth.onAuthStateChanged((user) => {
const authStore = useAuthStore(store);
const authState = storeToRefs(authStore);
authState.user.value = user;
});
}
if (process.env.SERVER) {
const cookies = Cookies.parseSSR(ssrContext);
const session = cookies.get('session');
if (session) {
const { user } = await axios.post('/profile', null, {
headers: {
Cookie: `session=${session}`
}
})
if (user) {
app.provide(serverUserKey, user)
}
}
}
})and finally, in u App.vue import { defineComponent, inject } from 'vue';
import { serverUserKey } from 'src/boot/firebae';
import useAuthStore from 'src/stores/auth';
import { storeToRefs } from 'pinia';
export default defineComponent({
setup () {
const serverUser = inject(serverUserKey);
if (serverUser) {
// if u aren't using plugins at all, u can do that in the boot
const authStore = useAuthStore(store);
const authState = storeToRefs(authStore);
authState.user.value = user;
}
}
})just don't forget to send your refreshToken token to the endpoint |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I had a lot of trouble setting up Pinia with Quasar.
Finally got it working with the amazing help of @yusufkandemir and @TobyMosque.
I'm documenting what I did here in case anyone else needs help.
I'm no expert, so if you have questions I probably won't be able to answer them. But ask anyway and hopefully someone else can give you an answer.
All code below is in TypeScript, but can be easily edited for plain JavaScript.
There is also some "bonus" parts about persisting the state during page refresh, and an example of how to init a Firebase user into the Pinia store when the app loads.
Preparation:
Uninstall Vuex if you already have it installed.
If you installed using NPM:
npm uninstall vuexIf you installed using Yarn:
yarn remove vuexInstallation:
Install Pinia with your preferred package manager.
Using NPM:
npm install piniaUsing Yarn:
yarn add piniaSetup:
1) Create an
index.tsfile at this locationsrc/store/index.ts.The code examples below were taken from here.
Use this code for
index.tsif your app is not using SSR:Use this code for
index.tsif your app is using SSR:You may also need this file in
src/store/ssr-config.tsif you use SSR. I'm not using SSR so I'm not sure if that file was added manually or automatically created.And on that note, my app does not use SSR. So take all my SSR advice with a grain of salt. I'm just adding all those parts here for the sake of completeness.
Done! Pinia is set up and ready to use. You can now access pinia from anywhere (boots, route, preFetch, etc).
Let's continue by creating your first User store.
2) Create a User store at
src/store/user.ts.This code example creates a store with a User and a FirebaseUser state.
I have included the FirebaseUser state because I find it convenient to use in my Firebase Composables. But you can leave that out if you want.
It also has some actions for setting various parts of the User state.
And we're putting the User and FirebaseUser state into LocalStorage using the Quasar LocalStorage plugin (more on that below).
3) Activate the Quasar LocalStorage plugin in your
quasar.conf.jsfile like so:4) Add this code to
App.vueto update Local Storage whenever the state of the user store changes:5) Here I am using a boot file to load the Firebase User data into the Pinia store when the app boots.
There is also a route guard that directs logged-in vs logged-out users.
This boot file code goes into
src/boot/auth.ts(but you can call it whatever you want).I admit this code doesn't look nice, but I have found using
onAuthStateChangedin the boot file is the best method for this:6) And finally we need to activate the boot file in your
quasar.conf.jsfile like so:Hope that helps anyone else who is struggling with this!
Beta Was this translation helpful? Give feedback.
All reactions