Check if token still valid before request - vue.js

I am using an index.js file to make api calls in a vue app. Is there a way to add a catch or a before each call to see if my token is still good and have the user redirected to login if it isnt?
import axios from 'axios'
const client = axios.create({
baseURL : 'http://myapi.com/api/',
json: true
})
export default {
async execute(method, resource, data) {
const token = localStorage.getItem('token')
return client({
method,
url: resource,
data,
crossdomain: true ,
headers: { "Authorization": `Bearer ${token}` }
}).then(req => {
return req.data
})
},
getResponses() {
return this.execute('get', 'GetResponses')
},
getAll(){
return this.execute('get', 'GetAll')
},

You can use an interceptor, where you can pass a function to be called before each request:
const client = axios.create({ baseURL: 'http://myapi.com/api/', json: true });
client.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (isTokenGood(token)) {
return config;
} else {
logout();
}
});

If anyone is interested i ended up going with an interceptor as per #thanksd
My code index.js file in my api folder now looks like this
import axios from 'axios'
import router from '../router'
const client = axios.create({
baseURL : 'http://myapi.com/api/',
json: true
})
client.interceptors.response.use(function (response) {
return response
}, function (error) {
if (error.response.status === 401) {
router.push('/Login')
console.info('auth token expired')
localStorage.clear()
sessionStorage.clear()
} else if (error.response.status === 403) {
router.push('/Login')
} else {
return Promise.reject(error)
}
})
export default {
async execute(method, resource, data) {
const token = localStorage.getItem('token')
return client({
method,
url: resource,
data,
crossdomain: true ,
headers: { "Authorization": `Bearer ${token}` }
}).then(req => {
return req.data
})
},
getResponses() {
return this.execute('get', 'GetResponses')
},
getAll(){
return this.execute('get', 'GetAll')
},

Related

NextJs/ Apollo Client/ NextAuth issue setting authorization Bearer Token to headers correctly

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);

apiplatform - react admin: token in localstorage null on redirect, have to refresh page

I'm using the stack apiplatform and react admin.
My JWT authentification works fine on apiplatform.
I try to use it on my react admin backoffice.
I followed those documentations:
https://api-platform.com/docs/admin/authentication-support/
https://marmelab.com/react-admin/doc/2.9/Authentication.html
The authentification/authorization works but on login success, I am redirected to the backoffice and I have a server communication error because the token send to the api is null.
I see it in the localstorage and if I refresh the page, everything works fine.
It's like the redirection on success happen before the token was store.
Here is my code:
App.js
import React from "react";
import { HydraAdmin, ResourceGuesser } from "#api-platform/admin";
import authProvider from "./components/authProvider";
import parseHydraDocumentation from "#api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation";
import {
dataProvider as baseDataProvider,
fetchHydra as baseFetchHydra
} from "#api-platform/admin";
import { Redirect } from "react-router-dom";
const entrypoint = "http://localhost:8089/api";
const fetchHeaders = {
Authorization: `Bearer ${window.localStorage.getItem("token")}`
};
const fetchHydra = (url, options = {}) =>
baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders)
});
const apiDocumentationParser = entrypoint =>
parseHydraDocumentation(entrypoint, {
headers: new Headers(fetchHeaders)
}).then(
({ api }) => ({ api }),
result => {
switch (result.status) {
case 401:
return Promise.resolve({
api: result.api,
customRoutes: [
{
props: {
path: "/",
render: () => <Redirect to={`/login`} />
}
}
]
});
default:
return Promise.reject(result);
}
}
);
const dataProvider = baseDataProvider(
entrypoint,
fetchHydra,
apiDocumentationParser
);
export default () => (
<HydraAdmin
apiDocumentationParser={apiDocumentationParser}
dataProvider={dataProvider}
authProvider={authProvider}
entrypoint={entrypoint}
>
<ResourceGuesser name="resource" />
</HydraAdmin>
);
authProvider.js
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_CHECK, AUTH_ERROR } from "react-admin";
export default (type, params) => {
if (type === AUTH_LOGIN) {
const { email, password } = params;
const request = new Request("http://localhost:8089/api/login_check", {
method: "POST",
body: JSON.stringify({ email, password }),
headers: new Headers({ "Content-Type": "application/json" })
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(({ token }) => {
localStorage.setItem("token", token);
});
}
if (type === AUTH_LOGOUT) {
localStorage.removeItem("token");
return Promise.resolve();
}
if (type === AUTH_ERROR) {
console.log("AUTH_ERROR");
//localStorage.removeItem("token");
return Promise.resolve();
}
if (type === AUTH_CHECK) {
return localStorage.getItem("token")
? Promise.resolve()
: Promise.reject({ redirectTo: "/login" });
}
return Promise.resolve();
};
I don't know if it's the right solution because like you, I didn't find anything about this problem.
But for me, if I just call window.location.reload(); after localStorage.setItem('token', token); it solves the problem for me, because after login, it reloads admin and at that moment, it can recognize token. Maybe it's not the cleanest solution ever but it works well.
By the way, I think, this is not a problem related with the HydraAdmin component, I tried the classic React Admin component and the problem is still there, so it's related to React Admin.

Using axios instance with vuex

I am building a Vue frontend with Rails backend.
On frontend I am using Axios and I have set these interceptors for authentication:
import axios from 'axios'
const API_URL = 'http://localhost:3000'
const securedAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
const plainAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
securedAxiosInstance.interceptors.request.use(config => {
const method = config.method.toUpperCase()
if (method !== 'OPTIONS' && method !== 'GET') {
config.headers = {
...config.headers,
'X-CSRF-TOKEN': localStorage.csrf
}
}
return config
})
securedAxiosInstance.interceptors.response.use(null, error => {
if (error.response && error.response.config && error.response.status === 401) {
// If 401 by expired access cookie, we do a refresh request
return plainAxiosInstance.post('/refresh', {}, { headers: { 'X-CSRF-TOKEN': localStorage.csrf } })
.then(response => {
localStorage.csrf = response.data.csrf
localStorage.signedIn = true
// After another successfull refresh - repeat original request
let retryConfig = error.response.config
retryConfig.headers['X-CSRF-TOKEN'] = localStorage.csrf
return plainAxiosInstance.request(retryConfig)
}).catch(error => {
delete localStorage.csrf
delete localStorage.signedIn
// redirect to signin if refresh fails
location.replace('/')
return Promise.reject(error)
})
} else {
return Promise.reject(error)
}
})
export { securedAxiosInstance, plainAxiosInstance }
On main.js I am making them available this way:
import VueAxios from 'vue-axios'
import { securedAxiosInstance, plainAxiosInstance } from './axios'
Vue.use(VueAxios, {
secured: securedAxiosInstance,
plain: plainAxiosInstance
})
new Vue({
el: '#app',
router,
store,
securedAxiosInstance,
plainAxiosInstance,
render: h => h(App)
})
And in components I can successfully use them like:
this.$http.secured.get('/items')
The problem is that I am unable to use them in store where I get:
Cannot read property 'secured' of undefined"
I tried in store among others:
import { securedAxiosInstance, plainAxiosInstance } from '../axios'
const store = new Vuex.Store({
secured: securedAxiosInstance,
plain: plainAxiosInstance,
.....
What is the correct way to do it?
You can use this._vm inside the store which refers to the Vue instance of the current application.
So in your case:
this._vm.$http.secured.get('/items')
As alternative you can pass the Vue instance as payload to your mutation/action, like:
this.$store.commit('myMutation',this)

How to use Async Storage Axios

Problem:
I have create react-native application.And there I am using AsyncStorage with axios in like this to handle my API calls.This is how that looks like.
import axios from "axios";
import { AsyncStorage } from "react-native";
// TODO: Replace this with actual JWT token from Keycloak
axios.defaults.headers.post["Content-Type"] = "application/json";
// Create axios instance for api calls
var instance = null;
export const setAuth = async () => {
const user = await AsyncStorage.getItem("jwt");
AsyncStorage.getItem("jwt").then(token => {
instance = axios.create({
baseURL: "",
timeout: 150000,
headers: {
Authorization: "Bearer " + token,
"Content-Type": "application/json"
}
});
instance.interceptors.response.use(
function(response) {
return response;
},
async function(error) {
if (error.response.status) {
return error;
}
}
);
});
};
export const Get = (route, data) => {
instance || setAuth();
return instance.get(
route,
data == null ? { data: {} } : { data: JSON.stringify(data) }
);
};
export const Post = (route, data) => {
instance || setAuth();
return instance.post(route, JSON.stringify(data));
};
export const Put = (route, data) => {
debugger;
instance || setAuth();
return instance.put(route, JSON.stringify(data));
};
export const AddAdmin = (route, data) => {};
Becuase of the asynchronus property of AsyncStorage it is not creating the axios instanceinstance = axios.create({.The problem is with after this line .So can someone help me with this.I do not have any idea to find out what is wrong with this. Thank you.
You can give a try to this.
export const setAuth = async () => {
const token = await AsyncStorage.getItem('jwt');
instance = axios.create({
baseURL: '',
timeout: 150000,
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
}
});
// remaining Code
};
export const Get = (route, data) => {
function getData(){
return instance.get(
route,
data == null ? { data: {} } : { data: JSON.stringify(data) }
)
}
if(instance) return getData()
return setAuth().then(getData)
}

How to add header to get request in vue js?

I am taking data from api, how to send get request with token after login?
It shows error createError.js?2d83:16 Uncaught (in promise) Error: Request failed with status code 401
export default new Vuex.Store({
state: {
users: []
},
mutations: {
setUsers(state, args){
state.users = args;
}
},
actions: {
login({ }, arg) {
axios.post('login/', { username: arg.username, password: arg.password })
.then((response) => {
console.log(response);
let accessToken = response.token.data;
localStorage.setItem('token', accessToken);
localStorage.setItem('user', response.data.user);
axios.defaults.headers.common['Authorization'] = accessToken;
window.isSignedIn = true;
router.push('/');
})
.catch((error) => {
console.log(error);
})
},
getUsers({ commit }){
let { data } = axios.get('v1/user/list/');
commit('setUsers', data);
}
}
})
Depends which authentication you are using. Try with:
axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
Other HTTP authentication schemes.