vue3 async setup() make my template not showing - vuejs3

I cannot figure out why my data is not showing when I use async front of setup().
My component call a module that handle my api calls. From that api I receive a token that I want to display in my client, as a test.
I can display the token i'm requesting in the console.log but can't see it in the template area.
Login.vue :
<template>
<h1>LOGIN</h1>
<Suspense>
<template #default>
<div>My token: {{ token }}</div>
</template>
<template #fallback>
<div>Loading</div>
</template>
</Suspense>
</template>
<script>
import useLogin from '../modules/users';
export default {
async setup() {
const { login, token } = useLogin();
await login();
return { token };
},
};
</script>
My module user.js
import api from '../services/Api';
import { ref } from 'vue';
export default function useLogin() {
const token = ref(null);
const login = async () => {
try {
token.value = await api.userLoginToken({
email: 'user#test.com',
password: '123123',
});
console.log(token.value);
} catch (error) {
console.log('error:', error);
return new Error();
}
};
return { login, token };
}
my api call is handled from api.js with axios:
import axios from 'axios';
const apiClient = axios.create({
baseURL: process.env.VUE_APP_API_URL,
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
export default {
async userLoginToken(credentials) {
const token = await apiClient.post('/users/login/token', credentials);
return token.data.token;
},
};
The whole Login.vue is blank
IMPORTANT: the data is showing if I remove async from of setup() and await front of login().
But I don't understand why i cannot use async.

Have to make the Suspense in the parent compoenent and move the fetch logic into a dedicated document to make it work.

Related

Storing Firebase accessToken in Pinia store - Vue3

I'm fairly new to Vue and it's my first time using Pinia. I'm following this guide to set up Firebase, Pinia and Axios. The app I'm building uses FirebaseUI to sign a user in, via an email link - this all happens in the LoginPage component below:
(Please ignore all incorrectly types variables/functions - I'm just trying to get this working in the first place)
<script setup lang="ts">
import { onMounted } from "vue";
import { EmailAuthProvider } from "firebase/auth";
import { auth } from "firebaseui";
import { auth as firebaseAuth } from "../firebase/config";
import { useUserStore } from "../stores/user"
onMounted(async () => {
const uiConfig: auth.Config = {
signInSuccessUrl: "/",
signInOptions: [
{
provider: EmailAuthProvider.PROVIDER_ID,
signInMethod: EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD,
forceSameDevice: true,
},
],
callbacks: {
signInSuccessWithAuthResult: function (authResult) {
const store = useUserStore();
store.user = authResult;
return true;
},
},
};
const ui = new auth.AuthUI(firebaseAuth);
ui.start("#firebaseui-auth-container", uiConfig);
});
</script>
<template>
<div id="firebaseui-auth-container"></div>
</template>
When the user successfully signs in, the app updates the Pinia store user object, with the AuthResult return object from the signInSuccessWithAuthResult function. When debugger, I can see that the object being stored looks like the following:
{
additionalUserInfo: {...}
operationType: "signIn"
user: {
accessToken: "eyJhbGciOiJSUzI1N..."
auth: {...}
displayName: null
...
}
}
I.e. the accessToken is being stored. The user store is below:
import { defineStore } from 'pinia'
export const useUserStore = defineStore("userStore", {
state: () => ({
user: null as any
}),
getters: {
getUser(state) {
return state.user
}
}
})
In the app I have set up an axios interceptor, that appends the accessToken to any Axios request made by the app:
axiosInstance.interceptors.request.use((config) => {
const userStore = useUserStore();
if (userStore) {
debugger;
// accessToken is undefined
config.headers.Authorization = 'Bearer ' + userStore.user.user.accessToken;
}
return config;
});
When attempting the retrieve the accessToken from the user store at this point, it's gone. Most (if not all) of the other properties from the user object still exist, but not the access token, therefore I'm pretty sure I'm using the store correctly:
{
additionalUserInfo: {...}
credential: null
operationType: "signIn"
user: {
// accessToken is gone
apiKey: "..."
appName: "[DEFAULT]"
email: "..."
emailVerified: true
....
}
}
Can anybody explain where I'm going wrong with this, and why the accessToken is being removed from the store? It looks to me as though I'm using the Pinia store correctly, and I'm pretty sure that the interceptor is also correct. However it's likely that I'm going about storing the access token in the wrong way. I'd appreciate any help/advice about how to setup Firebase authentication correctly with Vue.
Edited to include value of the user store when debugging inside the interceptor.
It looks like accessToken might be in userStore.user.user.accessToken?
Im just finishing the same battle that you are in... IMO there are many ways that this setup can be configured... This is similar to why you might use callbacks in one place, and async await in another it depends on your project structure.
Heres a simple example that might help you clarify it.
first
create a firebase file to hold the config put this where ever your organization habits tells you to put it. Just remember so we can use it later.
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: "",
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
//initialize firebase auth
export const auth = getAuth(app);
Second - userStore
The user store does the legwork. We will use the actions when we want to interact with userauth from our ui.
import {
createUserWithEmailAndPassword,
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
} from "firebase/auth";
import { auth } from "../firebase"; // the file we made above
import router from "../router";
export const useUserStore = defineStore("userStore", {
state: () => ({
userData: null,
loadingUser: false,
loadingSession: false,
}),
actions: {
async registerUser(email, password) {
this.loadingUser = true;
try {
const { user } = await createUserWithEmailAndPassword(
auth,
email,
password
);
this.userData = { email: user.email, uid: user.uid };
router.push("/");
} catch (error) {
console.log(error);
} finally {
this.loadingUser = false;
}
},
async loginUser(email, password) {
this.loadingUser = true;
try {
const { user } = await signInWithEmailAndPassword(
auth,
email,
password
);
this.userData = { email: user.email, uid: user.uid };
router.push("/");
} catch (error) {
console.log(error);
} finally {
this.loadingUser = false;
}
},
async logOutUser() {
try {
await signOut(auth);
this.userData = null;
router.push("/login");
} catch (error) {
console.log(error);
}
},
currentUser() {
return new Promise((resolve, reject) => {
const unsuscribe = onAuthStateChanged(
auth,
(user) => {
if (user) {
this.userData = { email: user.email, password: user.password };
} else {
this.userData = null;
}
resolve(user);
},
(e) => reject(e)
);
unsuscribe();
});
},
},
});
*** step3 setup the login / reg components in vue. ***
<div>
<form #submit.prevent="login">
<label>
Email:
<input type="email" v-model="email" required />
</label>
<br />
<label>
Password:
<input type="password" v-model="password" required />
</label>
<br />
<button type="submit">Login</button>
</form>
</div>
</template>
<script>
import { useUserStore } from "../stores/user";
export default {
data() {
return {
email: "",
password: "",
};
},
methods: {
async login() {
try {
await this.userStore.loginUser(this.email, this.password); //
} catch (error) {
console.error(error);
}
},
},
// because of below setup you can access this.userStore() singleton
setup() {
const userStore = useUserStore();
return {
userStore,
};
},
};
</script>
register is going to be simailar
<div>
<form #submit.prevent="register">
<label>
Email:
<input type="email" v-model="email" required />
</label>
<br />
<label>
Password:
<input type="password" v-model="password" required />
</label>
<br />
<button type="submit">Register</button>
</form>
</div>
</template>
<script>
import { useUserStore } from "../stores/user";
export default {
data() {
return {
email: "",
password: "",
};
},
methods: {
async register() {
try {
await this.userStore.registerUser(this.email, this.password);
} catch (error) {
console.error(error);
}
},
},
setup() {
const userStore = useUserStore();
return {
userStore,
};
},
};
</script>
now whenever you want to access the user it is in userStore.userData
if you dont have the userStore up yet just use the useUserStore() method and access it the same way you do from the setup in login / register view

Get supabase `user` server side in next.js

I am attempting to get the current logged in supabase user while server side.
I have attempted to use const user = supabase.auth.user(); but I always get a null response.
I have also attempted const user = supabase.auth.getUserByCookie(req) but it also returns null. I think because I am not sending a cookie to the api when calling it from the hook.
I have tried passing the user.id from the hook to the api but the api is not receiving the parameters.
I also attempted this approach but the token is never fetched. It seems to not exist in req.cookies.
let supabase = createClient(supabaseUrl, supabaseKey);
let token = req.cookies['sb:token'];
if (!token) {
return
}
let authRequestResult = await fetch(`${supabaseUrl}/auth/v1/user`, {
headers: {
'Authorization': `Bearer ${token}`,
'APIKey': supabaseKey
}
});
`
Does anyone know how to get the current logged in user in server side code?
If you need to get the user in server-side, you need to set the Auth Cookie in the server using the given Next.js API.
// pages/api/auth.js
import { supabase } from "../path/to/supabaseClient/definition";
export default function handler(req, res) {
if (req.method === "POST") {
supabase.auth.api.setAuthCookie(req, res);
} else {
res.setHeader("Allow", ["POST"]);
res.status(405).json({
message: `Method ${req.method} not allowed`,
});
}
}
This endpoint needs to be called every time the state of the user is changed, i.e. the events SIGNED_IN and SIGNED_OUT
You can set up a useEffect in _app.js or probably in a User Context file.
// _app.js
import "../styles/globals.css";
import { supabase } from '../path/to/supabaseClient/def'
function MyApp({ Component, pageProps }) {
useEffect(() => {
const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
handleAuthChange(event, session)
if (event === 'SIGNED_IN') {
// TODO: Actions to Perform on Sign In
}
if (event === 'SIGNED_OUT') {
// TODO: Actions to Perform on Logout
}
})
checkUser()
return () => {
authListener.unsubscribe()
}
}, [])
return <Component {...pageProps} />;
}
async function handleAuthChange(event, session) {
await fetch('/api/auth', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ event, session }),
})
}
export default MyApp;
You can now handle this user with a state and pass it to the app or whichever way you'd like to.
You can get the user in the server-side in any Next.js Page
// pages/user_route.js
import { supabase } from '../path/to/supabaseClient/def'
export default function UserPage ({ user }) {
return (
<h1>Email: {user.email}</h1>
)
}
export async function getServerSideProps({ req }) {
const { user } = await supabase.auth.api.getUserByCookie(req)
if (!user) {
return { props: {}, redirect: { destination: '/sign-in' } }
}
return { props: { user } }
}
Here's a YouTube Tutorial from Nader Dabit - https://www.youtube.com/watch?v=oXWImFqsQF4
And his GitHub Repository - https://github.com/dabit3/supabase-nextjs-auth
supabase have a library of helpers for managing auth for both client- and server-side auth and fetching in a couple of frameworks including Next.js: https://github.com/supabase/auth-helpers and appears to be the recommended solution for similar problems based on this thread: https://github.com/supabase/supabase/issues/3783
This is how I'm using it in an API handler, but provided you have access to req, you can access the user object this way:
import { supabaseServerClient } from '#supabase/auth-helpers-nextjs';
const { user } = await supabaseServerClient({ req, res }).auth.api.getUser(req.cookies["sb-access-token"]);
Note that you will need to use the helper library supabaseClient and supabaseServerClient on the client and server side respectively for this to work as intended.
I was following a tutorial today and was having a similar issue and the below is how i managed to fix it.
I've got this package installed github.com/jshttp/cookie which is why i'm calling cookie.parse.
Supabase Instance:
`//../../../utils/supabase`
import { createClient } from "#supabase/supabase-js";
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_KEY
);
In my case this was my API page:
import { supabase } from "../../../utils/supabase";
import cookie from "cookie";
import initStripe from "stripe";
const handler = async (req, res) => {
const { user } = await supabase.auth.api.getUserByCookie(req);
if (!user) {
return res.status(401).send("Unathorized");
}
const token = cookie.parse(req.headers.cookie)["sb-access-token"];
supabase.auth.session = () => ({
access_token: token,
});`
const {
data: { stripe_customer },
} = await supabase
.from("profile")
.select("stripe_customer")
.eq("id", user.id)
.single();
For anyone who tries to figure out how to get the user server side with the new #supabase/auth-helpers-nextjs, Michele gave the answer.
Just a note: If you're trying to get the user on nextJs's Middleware, instead of:
... req.cookies["sb-access-token"]
You have to use: req.cookies.get('sb-access-token')
For example:
import { supabaseServerClient } from '#supabase/auth-helpers-nextjs';
const { user } = await supabaseServerClient({ req, res }).auth.api.getUser(req.cookies.get('sb-access-token'))
UPDATE: 2023. Available now on Supabase Docs here
import { createServerSupabaseClient } from '#supabase/auth-helpers-nextjs'
export default function Profile({ user }) {
return <div>Hello {user.name}</div>
}
export const getServerSideProps = async (ctx) => {
// Create authenticated Supabase Client
const supabase = createServerSupabaseClient(ctx)
// Check if we have a session
const {
data: { session },
} = await supabase.auth.getSession()
if (!session)
return {
redirect: {
destination: '/',
permanent: false,
},
}
return {
props: {
initialSession: session,
user: session.user,
},
}
}

Fetch secret from Vault when initializing next.js

I'm using next.js with the library https://github.com/auth0/nextjs-auth0/
To initialise that library I need to fetch a secret from Vault using async/await but I get a Promise { <pending> }
I would have thought the following would work:
// utils/auth0.js
import { initAuth0 } from '#auth0/nextjs-auth0';
const vault = require('./vault');
async function getSecretFromVault() {
const res = await vault.fetchSecret();
console.log(res); // shows my secret correctly
return res;
}
const secret = getSecretFromVault();
console.log(secret); // shows Promise { <pending> }
export default initAuth0({
clientId: "my_ID",
clientSecret: secret // this will be invalid: UI shows "client_id is required"
....
});
What is the right way to do this?
An async method returns a promise, you should use await in order to get the async data.
Since module export is a sync it is better to export an async method which will make your call to Vault and return the initialization of Auth0.
// utils/auth0.js
import { initAuth0 } from '#auth0/nextjs-auth0';
const vault = require('./vault');
async function getSecretFromVault() {
const res = await vault.fetchSecret();
console.log(res); // shows my secret correctly
return res;
}
let instance;
async function getAuth0() {
if(instance) {
return Promise.resolve(instance);
}
const secret = await getSecretFromVault();
// -------------^
instance = initAuth0({
clientId: 'my_ID',
clientSecret: secret, // this will be invalid: UI shows "client_id is required"
});
return instance;
}
export default getAuth0;
// usage
import getAuth0 from './utils/auth0';
export default async function login(req, res) {
const auth0 = await getAuth0();
// --------------------^ get instance
try {
await auth0.handleLogin(req, res);
} catch (error) {
console.error(error);
res.status(error.status || 400).end(error.message);
}
}

Calling a redux action inside another action is not working

i have console log the error and the error is visible in console but the setAlert action is not dispatching , why is that ?
This is the Auth action code
import axios from "axios"
import {setAlert} from "../actions/alert";
import { REGISTER_FAIL } from "./conts"
export const register = ({ name, username, email, password }) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
}
const body = JSON.stringify({ name, username, email, password });
try {
const res = await axios.post("http://localhost:8080/signup",body,config);
setAlert("Email has been sent..! Please Confirm Your email","success",10000) //not working
}
catch (err) {
console.log(err.response.data.error);
const error = err.response.data.error;
setAlert(error,"error",5000); //not working
dispatch({
type:REGISTER_FAIL
});
}
}
this is Alert action
import {SET_ALERT,REMOVE_ALERT} from "./conts";
import {v4 as uuidv4} from "uuid";
export const setAlert =(msg,alertType,timeOut =5000)=> dispatch=>{
const id= uuidv4();
dispatch({
type: SET_ALERT,
payload:{msg,alertType,id}
});
setTimeout(()=> dispatch({type:REMOVE_ALERT,payload:id}),timeOut)
}
ok it seems , i just needed to dispatch the setAlerts

Typescript variable being used before assigned

As per instructions followed here, I'm trying to cache my endpoint URL and token from Auth0 before constructing my Apollo client:
import React from 'react';
import { ApolloClient, ApolloProvider, from, HttpLink, InMemoryCache } from '#apollo/client';
import { setContext } from '#apollo/link-context';
import { useAuth0 } from './auth/AuthContext';
const App: React.FC = () => {
const { isLoading, getTokenSilently, getIdTokenClaims } = useAuth0();
if (isLoading) return <Loader />;
let endpoint: string;
let token: string;
const contextLink = setContext(async () => {
if (!token) {
token = await getTokenSilently();
}
if (!endpoint) {
endpoint = await getIdTokenClaims()['https://example.com/graphql_endpoint'];
}
return { endpoint, token };
});
/**
* TODO: check for autorization error and remove token from cache
* See: https://www.apollographql.com/docs/react/v3.0-beta/api/link/apollo-link-context/
*/
const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: from([
contextLink,
new HttpLink({
uri: endpoint || '',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
])
});
return (
<ApolloProvider client={apolloClient}>
<div />
</ApolloProvider>
);
};
export default App;
I'm getting the error TS2454 (variable is used before being assigned) for both endpoint and token above. Any idea how I can get around this?
You're declaring both endpoint and token as variables, but not initializing them to anything before checking them inside of setContext.
let endpoint: string;
let token: string;
const contextLink = setContext(async () => {
if (!token) {
token = await getTokenSilently();
}
if (!endpoint) {
endpoint = await getIdTokenClaims()['https://example.com/graphql_endpoint'];
}
return { endpoint, token };
});
Try setting default values:
let endpoint: string = "";
let token: string = "";

Resources