Vue JWT token is only attached after refresh - vue.js

Token is null right after login, It stores only if I refresh the page. It should store just after login without need to reload.
src/stores/index.ts:
const initialUser = JSON.parse(sessionStorage.getItem('Project:token') || '{}');
const useProject = defineStore('project-store', {
state: () => ({
token: initialUser as string | null,
status: initialUser ? { loggedIn: false } : { loggedIn: true },
}),
actions: {
async login(user: User) {
const token = await grantAuth(user);
this.loginSuccess = token;
},
loginSuccess(token: string) {
this.token = token;
this.status.loggedIn = !!token;
},
src/services/entryPoint.js:
import store from '../stores/index';
export default async function entryPoint() {
const getStore = async () => {
if (!store) {
store = await import('../stores/index');
} else {
if (store) {
const { token } = store;
if (token) {
return token;
}
}
}
};
}
src/services/api.js:
import axios from 'axios';
import entryPoint from './entryPoint';
const api = axios.create({ baseURL: import.meta.env.VITE_APP_API_URL });
const entry = entryPoint();
if (entry) {
api.defaults.headers.Authorization = `Bearer ${entry}`;
}
export default api;
src/services/auth.js:
import api from './api';
const requestToken = encodedData => {
return api.post('/projects/login', null, {
headers: {
Authorization: `Basic ${encodedData}`,
},
});
}
const registerToken = data => {
if (data && data.token) {
sessionStorage.setItem('Project:token', JSON.stringify(data.token));
}
};
export const grantAuth = async user => {
const flatData = `${user.username.toUpperCase()}:${user.password}`;
const encodedData = btoa(flatData);
const response = await requestToken(encodedData);
registerToken(response.data);
return response.data ? response.data.token : null;
};
A solution would be to give the command window.location.reload(), however It is not viable for a SPA, It is preferable that the token to be available right after login

Related

change toolkit redux state inside of fetch

I just want to update state outside of component after server give me failed response.
the project is react native .
After a lot of online search on website like this , I learn that i need to import my store and use dispatch method. but when i import storage and dispatch it, it give me this error: Property 'crypto' doesn't exist .
this is my feachService.js code:
import { URL } from 'react-native-url-polyfill';
import { store } from '../store/index'
import { errorSliceActions } from '../store/slices/global/errorSlice'
export default (url, isGet) => {
let headers = {
Referer: new URL(url).origin,
}
headers['X-Requested-With'] = 'XMLHttpRequest'
return fetch(
url,
{
method: (isGet ? 'GET' : 'POST'),
headers: headers
})
.then(function (res) {
return res.json();
})
.then(function (res)
{
if(res && res.isSuccess == false && res.message) {
store.dispatch(errorSliceActions.add(res.message));
}
return {json: function() { return res; }};
})
.catch(function (error) { console.log(error.message); })
};
this is my storage js file:
import { configureStore } from '#reduxjs/toolkit';
import errorSliceReducer from './slices/global/errorSlice';
export const store = configureStore({
reducer: {
errorSlice: errorSliceReducer
}
});
this is my error slice.js file:
import { createSlice } from "#reduxjs/toolkit"
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
const errorSlice = createSlice({
name: "errorSlice",
initialState: {
errors: [
]
},
reducers:
{
add: (state, action) => {
const newItem = {
id: uuidv4(),
message: action.payload
};
return [...state, newItem];
},
remove: (state, action) => {
return { errors: state.errors.filter(function(item) { return item.id != action.payload; }) }
}
}
})
export const errorSliceActions = errorSlice.actions;
export default errorSlice.reducer
and this is my code for executing fetch request
async function loadInfo (inputSearch, inputPage) {
if(config.dataurl) {
console.log(inputSearch);
setIsLoading(true);
const hasQuestionMark = config.dataurl.indexOf('?') > 0;
let targetUrl = '/test/test' + config.dataurl + (!hasQuestionMark ? '?' : '&');
targetUrl += 'page=' + inputPage;
targetUrl += '&search=' + inputSearch;
console.log(targetUrl);
const response = await feachService(appJSon.baseUrl + targetUrl , true);
const responseData = await response.json();
setIsLoading(false);
if(responseData.pagination)
setPagination(responseData.pagination);
if(!responseData.results)
setItems([]);
else
setItems(responseData.results);
}
}
useEffect(() => {
loadInfo('', 1).catch(function (error) { console.log(error.message); });
}, [])
what am i doing wrong ?

Connect.sid not in cookie even with withCredentials:true in axios (rtk-query / react-native)

I am using rtk-query and an axiosbasequery set up like this:
export const apiSlice = createApi({
reducerPath: "apiSlice",
baseQuery: baseQueryWithErrorHandling,
endpoints: (builder) => ({
//endpoints
})
})
baseQueryWithErrorHandling just logs out the app in the event of a 401 error.
export const baseQueryWithErrorHandling = async (
args: IBaseQuery,
api: BaseQueryApi,
extraOptions: {}
) => {
const result = await rawBaseQuery(args, api, extraOptions);
if (result.error) {
if (result.error.status == 401) {
api.dispatch(logOut());
return result;
}
}
return result;
};
rawbaseQuery:
export const rawBaseQuery: BaseQueryFn<
IBaseQuery,
unknown,
FetchBaseQueryError
> = async ({ url, method, data, params }, api, extraOptions) => {
const query = axiosBaseQuery({
baseUrl: Globals.URLS.BASE_URL,
headers: (headers) => {
headers["Accept"] = "application/json";
headers["Content-Type"] = "application/json";
return headers;
},
});
return query({ url, method, data, params }, api, extraOptions);
};
And finally, the actual axios query. I put withCredentials in two spots.
import type { BaseQueryFn, FetchBaseQueryError } from "#reduxjs/toolkit/query";
import axios from "axios";
import type { AxiosRequestConfig, AxiosError } from "axios";
import Globals from "../globals/Globals";
axios.defaults.withCredentials = true; // Here (Spot #1)
export interface IAxiosBaseQuery {
baseUrl?: string;
headers?: (headers: { [key: string]: string }) => { [key: string]: string };
}
export interface IBaseQuery {
url: string;
params?: { [key: string]: string | number | Boolean };
method: AxiosRequestConfig["method"];
data?: AxiosRequestConfig["data"];
error?: {
status: number;
data: unknown;
};
}
export const axiosBaseQuery = ({
baseUrl = "",
headers,
}: IAxiosBaseQuery): BaseQueryFn<
IBaseQuery,
unknown,
FetchBaseQueryError
> => async ({ url, method, data, params }, api, extraOptions) => {
try {
const result = await axios({
url: params?.skipBaseURL ? url : baseUrl + url,
method,
...(params && { params: params }),
...(headers && { headers: headers({}) }),
...(data && { data: data }),
responseType: "json",
withCredentials: true, // And here (Spot #2)
});
return { data: result.data };
} catch (axiosError) {
let err = axiosError as AxiosError;
// error logic
}
};
Yet, it seems sometimes the cookie is passed, and sometimes not.
Here's my flipper log of a successful request with a connect.sid in the cookie.
Yet, the next request results in an error because the session id doesn't exist.
Any suggestions? I've been having this problem for a week.

react native fetch hook and refresh jwt token

i have made a custom hook to fetch data from api, but the token only valid for 5 second.
so i made this hook
the problem is when i call the hooks from my page it called many time and the refresh token already expired
when i access the api i will check the response first if the token invalid i tried to refresh my token using handleRefreshToken
nb : im using useContext for my state management
import React, {useEffect, useState, useContext} from 'react';
import {View, StyleSheet} from 'react-native';
import {AuthContext} from '../Auth/Context';
import AsyncStorage from '#react-native-community/async-storage';
import {urlLogin, URLREFRESHTOKEN} from '../Configs/GlobaUrl';
const FetchData = () => {
const {loginState, authContext} = useContext(AuthContext);
const [data, setData] = useState([]);
const [message, setMessage] = useState('');
const [loading, setIsLoading] = useState(false);
const {dispatchRefreshToken} = authContext;
const handleRefreshToken = async (callbackUrl, callbackBody) => {
const refBody = {
client_id: loginState.ipAddress,
ipAddress: loginState.ipAddress,
employee_id: loginState.userData.Pegawai_Id,
jwttoken: loginState.userToken,
refresh_tokenn: loginState.refreshToken,
};
console.log('======refreshtokencalled==========');
console.log(refBody.refresh_tokenn, '<=refresh token');
console.log(refBody.jwttoken, '<=jwt token');
let response = await fetch(URLREFRESHTOKEN, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(refBody),
redirect: 'follow',
});
let result = await response.json();
console.log(result, ' ini result');
if (
result.item3 !== 'refresh token gagal' &&
result.item3 !== 'refresh token sudah tidak berlaku'
) {
let refresh = result.item2;
let token = result.item1;
// the backend doesnt send any succes / error code only item1 for token, //item2 refresh token and item3 for error
dispatchRefreshToken(token, refresh);
await AsyncStorage.setItem('refreshToken', refresh);
await AsyncStorage.setItem('token', token);
return getData(callbackUrl, callbackBody);
} else {
return null;
}
};
const getData = async (url, body) => {
setIsLoading(true);
let result;
try {
let response = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${loginState.userToken}`,
},
body: JSON.stringify(body),
redirect: 'follow',
});
if (response.status == '401') {
let refreshResult = await handleRefreshToken(url, body);
console.log(refreshResult);
} else {
result = await response.json();
console.log(result);
console.log(loginState.refreshToken);
if (result.code == '1') {
setData(result.data);
setIsLoading(false);
} else {
throw result;
}
}
} catch (err) {
setData([]);
console.log(err, 'masuk error usefetchbybutton');
console.log(err.message, err.code);
setIsLoading(false);
setMessage(err);
}
};
return {
data: data,
message: message,
loading: loading,
getData: getData,
};
};
export default FetchData;
this is my dispatch refresh token
const authContext = useMemo(
() => ({
logIn: async (token, userData, refreshToken) => {
console.log(token, '<>', refreshToken, 'ini memoisa');
dispatch({
type: 'LOGIN',
token: token,
userData: userData,
refreshToken: refreshToken,
});
},
logOut: () => {
AsyncStorage.clear((error) => {
console.log(error);
});
dispatch({type: 'LOGOUT'});
},
dispatchRefreshToken: (userToken, refreshToken) => {
console.log(refreshToken, '=refresh dispatch=');
console.log(userToken, '=userToken dispatch=');
dispatch({
type: 'REFRESHTOKEN',
userToken: userToken,
refreshToken: refreshToken,
});
},
}),
[],
);
my reducer function
const loginReducer = (prevState, action) => {
switch (action.type) {
some case ...
case 'REFRESHTOKEN':
return {
...prevState,
userToken: action.userToken,
refreshToken: action.refreshToken,
};
}
};
Use recursion. The pseudo code is as follows
const getData = async (args, times) => {
// try to fetch data
const data = await Api.fetch(args);
// if token need to be refreshed.
if (check401(data)) {
// Use variable times to prevent stack overflow.
if (times > 0) {
// refresh the token
await refreshToken()
// try again
return getData(args, times - 1);
} else {
throw new Error("The appropriate error message")
}
}
return dealWith(data)
}
The logical above can be encapsulated to all your api. Like this
const wrapApi = (api) => {
const wrappedApi = async (args, times) => {
const data = await api(args);
// if token need to be refreshed.
if (check401(data)) {
// Use variable times to prevent stack overflow.
if (times > 0) {
// refresh the token
await refreshToken()
// try again
return wrappedApi(args, times - 1);
} else {
throw new Error("The appropriate error message")
}
}
return dealWith(data)
}
return wrappedApi;
}

nuxtjs page render the expressions before nuxtServerInit finish

I am using nuxtjs to develop my app, I want the page rendering userinfo if request already had the cookie['token']. I found that the page render the expressions before nuxtServerInit finish.
How can I fix this problem?
store/index.js
export const state = () => ({
user: null,
token: null
});
export const mutations = {
saveUser(state, userValue) {
console.log("store.user.save = " + JSON.stringify(userValue));
state.user = userValue;
},
saveToken(state, token) {
console.log("store.token.save = " + token);
state.token = token;
}
};
export const actions = {
nuxtServerInit({ commit }, app) {
const token = app.$cookies.get("token") && app.$cookies.get("token").indexOf("Bearer ") == 0 ? app.$cookies.get("token") : null;
console.log('nuxtserver init token = ', token)
if (token) {
commit("saveToken", app.$cookies.get("token"));
this.$axios.post("/api/me").then(res => {
commit("saveUser", res.data.data);
});
}
}
};
pages/index.vue
<template>
<div>{{user}}</div>
</template>
<script>
export default {
computed: {
user() {
console.log("computed " + this.$store.state.token)
return this.$store.state.user;
},
},
}
</script>
<style>
</style>
output
nuxtserver init token = Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0OWYxMjgwZTkxODRjZmMyIiwiZXhwIjoxNjA1NTE2OTAyfQ.ozioLM26fGVO8SfqfZllwp8j6Gy_PWoDsntPzlvXor0 14:25:53
store.token.save = Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0OWYxMjgwZTkxODRjZmMyIiwiZXhwIjoxNjA1NTE2OTAyfQ.ozioLM26fGVO8SfqfZllwp8j6Gy_PWoDsntPzlvXor0 14:25:53
Making request to /api/me 14:25:53
computed null 14:25:53
store.user.save = {"id":"49f1280e9184cfc2","nickname":"test","avatar":"https://vip1.loli.net/2020/11/15/Eqlr5T7JcmOuDWk.jpg"} 14:25:53
Yes because an axios call is asynchronous. You can use async / await to make it "syncrhonouse"
export const actions = {
async nuxtServerInit({ commit }, app) {
const token = app.$cookies.get("token") && app.$cookies.get("token").indexOf("Bearer ") == 0 ? app.$cookies.get("token") : null;
console.log('nuxtserver init token = ', token)
if (token) {
commit("saveToken", app.$cookies.get("token"));
let {data} = await this.$axios.post("/api/me")
commit("saveUser", data.data);
}
}
};

Multiple API providers with nuxt-axios and a plugin for default headers

I have a Nuxt app with many request to the same API, but also i need to make request to different providers apart of my main API and i don't know how to manage the default headers.
This is my working setup create a plugin to add the headers to all the request like this:
plugins/axios.js
export default function({ $axios, store, redirect }) {
$axios.onRequest(config => {
config.headers.common.Authorization = 'token 123';
config.headers.common["Custom-header"] = 'blablabla';
}
}
nuxt.config.js
module.exports = {
plugins: ["#/plugins/axios"],
axios: {
baseURL: process.env.API_URL,
}
}
store.js
async changeKeyVersionOnline({ commit }) {
const response = await this.$axios.get(
`users/1`
);
return response;
},
This works great for the main API but the problem is i need also to make request to other endpoints of third party service provider and of course the headers should be different.
How can i do that, i read about the proxy option of the nuxt-axios package but what i understand is this only changes the request base URL, i cant find how to set different headers to a specific request.
My final solution was based on create some actions in a central store so the axios requests are made trough this actions.
central.js (Where the axios related actions live)
import qs from "qs";
export const state = () => ({
accessToken: "",
clientId: 0
});
export const getters = {
getHeadersWithAuth: state => {
const config = {
headers: {
Authorization: "Bearer " + state.accessToken
}
};
return config;
},
getHeadersWithAuthClient: state => {
const config = {
headers: {
Authorization: "Bearer " + state.accessToken,
Client: state.clientId
}
};
return config;
}
};
export const mutations = {};
export const actions = {
async getWithAuth({ getters }, { path, params }) {
const config = getters.getHeadersWithAuth;
config.params = params;
config.paramsSerializer = function(params) {
return qs.stringify(params, { encode: false });
};
const result = await this.$axios.get(path, config);
return result;
},
async getWithAuthClient({ getters }, { path, params }) {
const config = getters.getHeadersWithAuthClient;
config.params = params;
config.paramsSerializer = function(params) {
return qs.stringify(params, { encode: false });
};
const result = await this.$axios.get(path, config);
return result;
},
async putWithAuthClient({ getters }, { path, body, params }) {
const config = getters.getHeadersWithAuthClient;
config.params = params;
config.paramsSerializer = function(params) {
return qs.stringify(params, { encode: false });
};
const result = await this.$axios.put(path, body, config);
return result;
}
};
test.js Other store which use the custom axios requests
async updateProductDetailsAction({ commit, dispatch, state }, productData) {
const request = {
path: `endpoints/` + productData.id + `/details`,
body: {
length: 123,
name: 'The product name'
},
params: {}
};
const result = await dispatch("auth/putWithAuthClient", request, {
root: true
});
await commit("setProductDetails", productData.id);
return result;
}