While trying to fetch all images from the Galleries template I am receiving the following error/warn notification: "Property "token" was accessed during render but is not defined on instance". instead of displaying the images I receive the following: <img src="function link() { [native code] }"> What am I missing?
Galleries.vue
<template>
<div>
<img v-for="image in allImages" :src="image.link" :key="image.id" />
</div>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
export default {
name: "Galleries",
computed: mapGetters(["allImages"]),
methods: mapActions(["fetchImages"]),
created() {
this.fetchImages();
}
};
</script>
imgur.js
import qs from 'qs';
import axios from 'axios';
const CLIENT_ID = 'e28971925a8d43c';
const ROOT_URL = 'https://api.imgur.com';
export default {
login() {
const querystring = {
client_id: CLIENT_ID,
response_type: 'token',
};
window.location = `${ROOT_URL}/oauth2/authorize?${qs.stringify(querystring)}`
},
fetchImages(token) {
return axios.get(`${ROOT_URL}/3/account/me/image/`, {
headers: {
Authorization: `Bearer ${token}`
}
});
},
uploadImages(images, token) {
const promises = Array.from(images).map(image => {
const formData = new FormData();
formData.append('image', image);
return axios.post(`${ROOT_URL}/3/image`, formData, {
headers: {
Authorization: `Bearer ${token}`
}
});
});
return Promise.all(promises);
}
};
images.js
import api from '../../api/imgur';
import { router } from '../../main';
const state = {
images: []
};
const getters = {
allImages: state => state.images
};
const mutations = {
setImages: (state, images) => {
state.images = images;
}
};
const actions = {
async fetchImages({ rootState, commit }) {
const { token } = rootState.auth;
const response = await api.fetchImages(token);
commit('setImages', response.data.data);
},
async uploadImages({ rootState }, images) {
// Get the access token
const { token } = rootState.auth;
// Call our API module to do the upload
await api.uploadImages(images, token);
// Redirect use to the gallery page
router.push('/galleries');
}
};
export default {
state,
getters,
mutations,
actions
}
auth.js
import api from '../../api/imgur';
import qs from 'qs';
import { router } from '../../main';
const state = {
token: window.localStorage.getItem('imgur_token')
};
const getters = {
isLoggedIn: state => !!state.token // turn a value into boolean
};
const actions = {
login: () => {
api.login();
},
finalizeLogin({ commit }, hash) {
const query = qs.parse(hash.replace('#', ''));
commit('setToken', query.access_token);
window.localStorage.setItem('imgur_token', query.access_token);
router.push('/');
},
logout: ({ commit }) => {
commit('setToken', null);
window.localStorage.removeItem('imgur_token');
router.push('/');
}
};
const mutations = {
setToken: (state, token) => {
state.token = token;
}
};
export default {
state,
getters,
actions,
mutations
};
// Galaries.vue
// You didn't pass token when calling function `fetchImages`.
created() {
this.fetchImages(); // missing token here
}
I recommend use token as environment variable for security reason. Never public your token. Should NOT pass it as an argument of a function. Store your token in a .env file. You can rewrite your fetchImages as below.
fetchImages(token) {
return axios.get(`${ROOT_URL}/3/account/me/image/`, {
headers: {
Authorization: `Bearer ${process.env.TOKEN}`
}
});
},
Related
I have been messing around with Vue and trying to learn it. On the first click of the button in LoginForm.vue token and user_data are both null. On the second click it finally gets updated. How can I get the live reactive state of the variables?
I am new to Vue so if there are better common practices please let me know.
store/login.js
import { defineStore } from 'pinia'
import axios from 'axios'
export const useUsers = defineStore('users', {
state: () => ({
token: null,
user_data: null
}),
actions: {
async loginUser(data) {
try {
let response = await axios.post("users/login", data)
// Object.assign(this.token, response.data.token)
this.token = response.data.token
axios.defaults.headers.common['user-token'] = this.token
} catch (error) {
return error
}
},
async logout() {
// Object.assign(this.token, null)
// Object.assign(this.user_data, null)
this.token = null
this.user_data = null
// localStorage.removeItem('user');
delete axios.defaults.headers.common['user-token']
},
async login_and_get_user_data(data) {
axios.post("users/login", data).then(response => {
this.token = response.data.token
axios.defaults.headers.common['user-token'] = this.token
axios.get("users/user").then(response2 => {
this.user_data = response2.data.user
})
})
},
async get_user_data() {
console.log(JSON.parse(localStorage.getItem('user'))['token'])
axios.defaults.headers.common['user-token'] = JSON.parse(localStorage.getItem('user'))['token']
let response = await axios.get("users/user")
// Object.assign(this.user_data, response.data.user)
this.user_data = response.data.user
}
}
})
components/LoginForm.vue
<script>
import { useUsers } from '#/stores/login'
import { mapActions } from 'pinia'
import { storeToRefs } from 'pinia'
import { isProxy, toRaw } from 'vue';
export default {
setup() {
const store = useUsers()
store.$subscribe((mutation, state) => {
localStorage.setItem('user', JSON.stringify(state))
})
},
data() {
return {
email: "",
password: ""
}
},
methods: {
...mapActions(useUsers, ['loginUser']),
...mapActions(useUsers, ['get_user_data']),
...mapActions(useUsers, ['logout']),
on_click() {
var data = new FormData();
data.append('email', this.email);
data.append('password', this.password);
const store = useUsers()
this.loginUser(data)
this.get_user_data()
const { token } = storeToRefs(store)
const { user_data } = storeToRefs(store)
console.log(token.value)
console.log(toRaw(user_data.value))
},
logout_click() {
this.logout().then(
console.log(JSON.parse(localStorage.getItem('user')))
)
}
}
}
</script>
<template>
<input type="email" v-model="email" placeholder="youremail#mail.com">
<br>
<input type="password" v-model="password">
<br>
<button #click="on_click">Submit</button>
<br>
<button #click="logout_click">Logout</button>
</template>
Your method on_click is calling async methods like loginUser or get_user_data without waiting them to be finished.
So by the time your are logging console.log(token.value) your http request is probably not finished yet and your token is still null.
You need to await the methods that are doing those requests.
async on_click() {
var data = new FormData();
data.append('email', this.email);
data.append('password', this.password);
const store = useUsers()
await this.loginUser(data)
await this.get_user_data()
const { token } = storeToRefs(store)
const { user_data } = storeToRefs(store)
console.log(token.value)
console.log(toRaw(user_data.value))
},
Keep in mind that you will probably need to display a loader to give the user a feedback because the on_click is now asynchronous and take a bit more time
I am new to vue.js, I have a simple web application(Vue frontend connected to a FastAPI backend) that a user can create an account and login, All of this works so far but when I refresh the page the user is logged out.
And console show an error:
Uncaught (in promise) TypeError: Cannot read property '$store' of undefined
What am I doing wrong? How to keep user logged in even after page refresh. Can anyone please help me?? thanks
store/index.js
import Vuex from 'vuex';
import Vue from 'vue';
import createPersistedState from "vuex-persistedstate";
import auth from './modules/auth';
// Load Vuex
Vue.use(Vuex);
// Create store
const store = new Vuex.Store({
modules: {
auth
},
plugins: [createPersistedState()]
});
export default store
store/modules/auth.js
import { postUserLogInAPI } from "../../service/apis.js";
const state = {
token: "",
expiration: Date.now(),
username: ""
};
const getters = {
getToken: (state) => state.token,
getUsername: (state) => state.username,
getFullname: (state) => state.fullname,
isAuthenticated: (state) => state.token.length > 0 && state.expiration > Date.now()
};
const actions = {
async LogIn({ commit }, model) {
await postUserLogInAPI(model).then(function (response) {
if (response.status == 200) {
commit("LogIn", response.data)
}
})
},
async LogOut({ commit }) {
commit('LogOut')
}
};
const mutations = {
LogIn(state, data) {
state.username = data.username
state.fullname = data.fullname
state.token = data.token
state.expiration = new Date(data.expiration)
},
LogOut(state) {
state.username = ""
state.fullname = ""
state.token = ""
state.expiration = Date.now();
},
};
export default {
state,
getters,
actions,
mutations
};
service/http.js
import axios from 'axios'
import { Loading, Message } from 'element-ui'
import router from '../router/index.js'
import store from '../store';
let loading
function startLoading() {
loading = Loading.service({
lock: true,
text: 'Loading....',
background: 'rgba(0, 0, 0, 0.7)'
})
}
function endLoading() {
loading.close()
}
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'http://0.0.0.0:80/';
axios.interceptors.request.use(
(confing) => {
startLoading()
if (store.getters.isAuthenticated) {
confing.headers.Authorization = "Bearer " + store.getters.getToken
}
return confing
},
(error) => {
return Promise.reject(error)
}
)
axios.interceptors.response.use(
(response) => {
endLoading()
return response
},
(error) => {
Message.error(error.response.data)
endLoading()
const { status } = error.response
if (status === 401) {
Message.error('Please Login')
this.$store.dispatch('LogOut')
router.push('/login')
}
return Promise.reject(error)
}
)
export default axios
components/NavBar.vue
<script>
export default {
name: "NavBar",
computed: {
isLoggedIn: function () {
return this.$store.getters.isAuthenticated;
},
username: function () {
return this.$store.getters.getUsername;
},
fullname: function () {
return this.$store.getters.getFullname;
},
},
methods: {
async logout() {
await this.$store.dispatch("LogOut");
this.$router.push("/login");
},
},
};
</script>
I cannot correctly set my jwt token from my cookie to my Headers for an authenticaed gql request using apollo client.
I believe the problem is on my withApollo.js file, the one that wraps the App component on _app.js. The format of this file is based off of the wes bos advanced react nextjs graphql course. What happens is that nextauth saves the JWT as a cookie, and I can then grab the JWT from that cookie using a custom regex function. Then I try to set this token value to the authorization bearer header. The problem is that on the first load of a page with a gql query needing a jwt token, I get the error "Cannot read property 'cookie' of undefined". But, if I hit browser refresh, then suddenly it works and the token was successfully set to the header.
Some research led me to adding a setcontext link and so that's where I try to perform this operation. I tried to async await setting the token value but that doesn't seem to have helped. It just seems like the headers don't want to get set until on the refresh.
lib/withData.js
import { ApolloClient, ApolloLink, InMemoryCache } from '#apollo/client';
import { onError } from '#apollo/link-error';
import { getDataFromTree } from '#apollo/react-ssr';
import { createUploadLink } from 'apollo-upload-client';
import withApollo from 'next-with-apollo';
import { setContext } from 'apollo-link-context';
import { endpoint, prodEndpoint } from '../config';
import paginationField from './paginationField';
const getCookieValue = (name, cookie) =>
cookie.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`)?.pop() || '';
let token;
function createClient(props) {
const { initialState, headers, ctx } = props;
console.log({ headers });
// console.log({ ctx });
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError)
console.log(
`[Network error]: ${networkError}. Backend is unreachable. Is it running?`
);
}),
setContext(async (request, previousContext) => {
token = await getCookieValue('token', headers.cookie);
return {
headers: {
authorization: token ? `Bearer ${token}` : '',
},
};
}),
createUploadLink({
uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
fetchOptions: {
credentials: 'include',
},
headers,
}),
]),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// TODO: We will add this together!
// allProducts: paginationField(),
},
},
},
}).restore(initialState || {}),
});
}
export default withApollo(createClient, { getDataFromTree });
page/_app.js
import { ApolloProvider } from '#apollo/client';
import NProgress from 'nprogress';
import Router from 'next/router';
import { Provider, getSession } from 'next-auth/client';
import { CookiesProvider } from 'react-cookie';
import nookies, { parseCookies } from 'nookies';
import Page from '../components/Page';
import '../components/styles/nprogress.css';
import withData from '../lib/withData';
Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());
function MyApp({ Component, pageProps, apollo, user }) {
return (
<Provider session={pageProps.session}>
<ApolloProvider client={apollo}>
<Page>
<Component {...pageProps} {...user} />
</Page>
</ApolloProvider>
</Provider>
);
}
MyApp.getInitialProps = async function ({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
pageProps.query = ctx.query;
const user = {};
const { req } = ctx;
const session = await getSession({ req });
if (session) {
user.email = session.user.email;
user.id = session.user.id;
user.isUser = !!session;
// Set
nookies.set(ctx, 'token', session.accessToken, {
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
}
return {
pageProps,
user: user || null,
};
};
export default withData(MyApp);
api/auth/[...nextAuth.js]
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from 'axios';
const providers = [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
Providers.Credentials({
name: 'Credentials',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
const user = await axios
.post('http://localhost:1337/auth/local', {
identifier: credentials.username,
password: credentials.password,
})
.then((res) => {
res.data.user.token = res.data.jwt;
return res.data.user;
}) // define user as res.data.user (will be referenced in callbacks)
.catch((error) => {
console.log('An error occurred:', error);
});
if (user) {
return user;
}
return null;
},
}),
];
const callbacks = {
// Getting the JWT token from API response
async jwt(token, user, account, profile, isNewUser) {
// WRITE TO TOKEN (from above sources)
if (user) {
const provider = account.provider || user.provider || null;
let response;
let data;
switch (provider) {
case 'google':
response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/google/callback?access_token=${account?.accessToken}`
);
data = await response.json();
if (data) {
token.accessToken = data.jwt;
token.id = data.user._id;
} else {
console.log('ERROR No data');
}
break;
case 'local':
response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/local/callback?access_token=${account?.accessToken}`
);
data = await response.json();
token.accessToken = user.token;
token.id = user.id;
break;
default:
console.log(`ERROR: Provider value is ${provider}`);
break;
}
}
return token;
},
async session(session, token) {
// WRITE TO SESSION (from token)
// console.log(token);
session.accessToken = token.accessToken;
session.user.id = token.id;
return session;
},
redirect: async (url, baseUrl) => baseUrl,
};
const sessionPreferences = {
session: {
jwt: true,
},
};
const options = {
providers,
callbacks,
sessionPreferences,
};
export default (req, res) => NextAuth(req, res, options);
Can anyone tell me from where I can import apolloClient so that I can make requests to apollo?
I usually get an error either mutate is not a function (even if I pass in this.$apollo from a Vue component)
I am just trying to get into the way of things in Vue. If any hints on code and structure I would appreciate that
signIn component
<template>
<div class="signIn-component">
<form #submit.prevent="signInUser()">
<input
type="email"
placeholder="Enter your email"
v-model="formInput.email"
/>
<input
type="password"
placeholder="Enter your password"
v-model="formInput.password"
/>
<button>Sign In</button>
</form>
</div>
</template>
<script>
import { createNamespacedHelpers } from "vuex";
const { mapActions } = createNamespacedHelpers("auth");
export default {
data() {
return {
formInput: {
email: null,
password: null
}
};
},
methods: {
// Vuex Actions
...mapActions(["signIn"]),
signInUser: function() {
// eslint-disable-next-line no-unused-vars
this.signIn(this.formInput, this.$apollo).then(_ =>
this.$route.push("/")
);
}
}
};
</script>
<style></style>
Vuex.auth
import { apolloClient } from 'vue-cli-plugin-apollo/graphql-client';
import SignInGQL from "#/graphql/signIn.gql";
export default {
namespaced: true,
state: {
token: null,
user: {},
authStatus: false
},
getters: {
isAuthenticated: state => !!state.token,
authStatus: state => state.authStatus,
user: state => state.user
},
actions: {
async signIn({ commit, dispatch }, formInput) {
console.log('here');
try {
const { data } = await apollo.mutate({
mutation: SignInGQL,
variables: { ...formInput }
})
const { token } = data.signIn;
console.log(token);
commit('setToken', token);
localStorage.setItem('auth-token', token);
dispatch('setUser', token);
} catch (e) {
console.error(e)
}
},
setUser({ commit }, token) {
const encodedPayload = token.split('.')[1];
const { payload } = JSON.parse(atob(encodedPayload));
console.log('payload: ', payload);
// TODO: Set User information
commit('signInUser', payload);
}
},
mutations: {
setToken(state, token) {
state.token = token
},
signInUser(state, user) {
state.authStatus = true
state.user = { ...user }
},
// logOutUser(state) {
// state.authStatus = ''
// state.token = '' && localStorage.removeItem('auth-token')
// }
}
}
This question explains adding headers to apollo client
solution repo
import { setContext } from "apollo-link-context";
import { ApolloClient, InMemoryCache, HttpLink } from "apollo-boost";
import VueApollo from "vue-apollo";
Vue.use(VueApollo);
const httpLink = new HttpLink({
uri: "http://sebapi.com/graphql"
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from ApplicationSettings if it exists
const token = ApplicationSettings.getString("token");
// return the headers to the context so HTTP link can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null
}
};
});
// update apollo client as below
const apolloClient = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
and LOGIN.VUE
<script lang="ts">
export default {
data() {
return {
jwt: "",
user: {
identifier: "test",
password: "123123",
},
};
},
methods: {
handleLogin() {
request({
url: "http://sebapi.com/auth/local",
method: "POST",
headers: { "Content-Type": "application/json" },
content: JSON.stringify({
identifier: this.user.identifier,
password: this.user.password,
}),
})
.then(
(response) => {
const result = response.content.toJSON();
console.log("Result from Server: ", result);
//ignore applicationsettings it's just a kind of localstore in nativescript
ApplicationSettings.setString("token", result.jwt);
},
(e) => {
console.error(e);
//ignore nativateto its routing in nativescript
this.$navigateTo(routes.login);
}
)
.then(() => {
this.$navigateTo(routes.app);
});
},
},
};
</script>
This is how I did it, in case somebody is looking for the same answer:
import Vue from 'vue'
import VueApollo from 'vue-apollo'
import { createApolloClient } from 'vue-cli-plugin-apollo/graphql-client'
// Install the vue plugin
Vue.use(VueApollo)
// Name of the localStorage item
const AUTH_TOKEN = 'auth-token'
// Http endpoint
const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:3000/graphql';
// Config
const defaultOptions = {
// You can use `https` for secure connection (recommended in production)
httpEndpoint,
// You can use `wss` for secure connection (recommended in production)
// Use `null` to disable subscriptions
wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || null,
// wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:3000/graphql',
// LocalStorage token
tokenName: AUTH_TOKEN,
// Enable Automatic Query persisting with Apollo Engine
persisting: false,
// Use websockets for everything (no HTTP)
// You need to pass a `wsEndpoint` for this to work
websocketsOnly: false,
// Is being rendered on the server?
ssr: false,
// Override default apollo link
// note: don't override httpLink here, specify httpLink options in the
// httpLinkOptions property of defaultOptions.
// httpLinkOptions: {
// headers: {
// Authorization: authHeader
// }
// }
// Override default cache
// cache: myCache
// Override the way the Authorization header is set
// getAuth: (tokenName) => getUserToken(),
// Additional ApolloClient options
// apollo: { ... }
// Client local data (see apollo-link-state)
// clientState: { resolvers: { ... }, defaults: { ... } }
}
export const { apolloClient, wsClient } = createApolloClient({
...defaultOptions,
// ...options,
})
// Call this in the Vue app file
export function createProvider() {
// Create apollo client
apolloClient.wsClient = wsClient
// Create vue apollo provider
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
$query: {
fetchPolicy: 'cache-and-network',
},
},
errorHandler(error) {
// eslint-disable-next-line no-console
console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
},
})
return apolloProvider
}
This is the source I found the solution
If you have any recommendations, feel free to leave them here, please!
I have problem to get token stored by Vuex in ~/plugins/axios.js. Hope your guys take a look for me please.
My Vuex: ~/store/index.js
export const state = () => ({
authUser: null,
token: null
})
export const mutations = {
SET_USER: function (state, user) {
state.authUser = user
},
SET_TOKEN: function (state, token) {
state.token = token
instance.defaults.headers = { Authorization: 'Bearer ' + token }
}
}
export const actions = {
async nuxtServerInit ({ commit }, { req }) {
...
},
async login ({ commit }, { username, password }) {
...
}
}
const store = () => new Vuex.Store({
state,
mutations,
actions,
modules: {
feed,
users,
notification,
messenger
}
})
export default store
const VuexStore = () => {
return store
}
export { VuexStore }
~/plugins/axios, which VuexStore.state is not data!
import axios from 'axios'
import {VuexStore} from '~/store'
var api = axios.create({
baseURL: 'http://localhost:8000/api/v1/'
})
console.log(VuexStore.state.token) // Null data
export default api
Console log is:
ƒ VuexStore() {
return store;
}
Not tested but you could use something like:
~/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = () => ({
authUser: null,
token: null
})
const mutations = {
SET_USER: function (state, user) {
state.authUser = user
},
SET_TOKEN: function (state, token) {
state.token = token
instance.defaults.headers = { Authorization: 'Bearer ' + token }
}
}
const getters = {
...
}
const actions = {
async nuxtServerInit ({ commit }, { req }) {
...
},
async login ({ commit }, { username, password }) {
...
}
}
export default new Vuex.Store({
state,
actions,
mutations,
getters,
modules: {
...
}
});
~/plugins/axios.js
import axios from 'axios'
import VuexStore from '~/store'
var api = axios.create({
baseURL: 'http://localhost:8000/api/v1/'
})
console.log(VuexStore.state.token)
export default api