Using axios interceptor in Vue project - javascript

I have components that are making get requests in their created methods. I am using oidc client for authorization. I would like to set the each request header with the token that I get from oidc. I have made a http.js file in the root of the project, that looks like this:
import axios from 'axios';
import AuthService from "./AuthService";
const authService = new AuthService();
let token;
axios.interceptors.request.use(async function (config) {
await authService.getUser().then(res => {
if (res) {
token = res.id_token;
config.headers['Authorization'] = `Bearer ${token}`;
}
});
// eslint-disable-next-line no-console
console.log('interceptor', config);
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
I am not sure if this is the way to set the interceptors and how to actually use them, because on each request I see that they are not being set and nothing is being logged in the console. How is this suppose to be set up?

Related

Error: This is caused by either a bug in Node.js or incorrect usage of Node.js internals

I was creating authentication mechanism for my service. And at some moment I had problem with cookies. More you can find here, so I solved this.
The problem was that I was trying to send cookie through 2 requests. My Next.js front-end sends request to its internal API, and only then, internal API sends this request to back-end.
The solution of this problem was very easy, what I had to do - is to set cookie on back-end and return it in headers. Here is how flow looks, like.
This is how it looks like, endpoint in Next.js front-end. Except of data in response, it receives header, where cookie is set (response from back-end) and send it in header of response, that will be send on front-end, where cookie will be set:
import { NextApiRequest, NextApiResponse } from "next";
import { AxiosError } from "axios";
import { api } from "../../../api";
export default async (
req: NextApiRequest,
res: NextApiResponse
) => {
try {
const { data, headers } = await api.post('/user/sign-in', req.body)
if (headers["set-cookie"]) {
res.setHeader("Set-Cookie", headers["set-cookie"]);
}
return res.json(data)
} catch (error) {
return res
.status((error as AxiosError).response?.status as number)
.json((error as AxiosError).response?.data);
}
}
And endpoint on back-end:
import { Response as Res } from 'express';
import * as dayjs from 'dayjs';
...
async signIn(#Body() signInUserDto: SignInUserDto, #Response() res: Res) {
const { _at, _rt } = await this.userService.signIn(signInUserDto);
res.cookie('_rt', _rt, {
httpOnly: true,
expires: dayjs().add(7, 'days').toDate()
});
return res.send(_at);
}
And here is the problem, because of this Response class of express I keep getting this warning:
Error: This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
Please open an issue with this stack trace at https://github.com/nodejs/node/issues
at new NodeError (node:internal/errors:371:5)
at assert (node:internal/assert:14:11)
at ServerResponse.detachSocket (node:_http_server:249:3)
at resOnFinish (node:_http_server:819:7)
at ServerResponse.emit (node:events:390:28)
at onFinish (node:_http_outgoing:830:10)
at callback (node:internal/streams/writable:552:21)
at afterWrite (node:internal/streams/writable:497:5)
at afterWriteTick (node:internal/streams/writable:484:10)
at processTicksAndRejections (node:internal/process/task_queues:82:21)
It is definitely because of how this signIn function looks like, because I was trying to return just like this - return this.userService.signIn(signInUserDto) - and it worked, but I can't cookie in this case.
So, my question is - what is this error? Can I just ignore it? If not, then how can I fix it?
Thanks in advance!
TL;DR
Finally, I was able to fix this error, first of all, as I said, my goes through 2 API's, from back-end to front-end API, and only then, this front-end API sends this request to actual front-end.
So, what I did, is just returned 2 tokens - refresh and access - as body.
#ApiOperation({ summary: 'Resource for sign in user.' })
#ApiResponse({ status: 200, type: TokensDto })
#Post('/sign-in')
async signIn(#Body() signInUserDto: SignInUserDto) {
return this.userService.signIn(signInUserDto);
}
Then, on front-end, I installed cookie and #types/cookie and in this front-end endpoint, in headers, I just serialized this refresh token from body payload, and removed from it.
import { NextApiRequest, NextApiResponse } from "next";
import { AxiosError } from "axios";
import { api } from "../../../api";
import { serialize } from 'cookie';
export default async (
req: NextApiRequest,
res: NextApiResponse
) => {
try {
const { data } = await api.post('/user/sign-in', req.body)
res.setHeader('Set-Cookie', serialize(
'_rt',
data._rt,
{ path: '/', httpOnly: true })
);
delete data._rt
return res.json(data)
} catch (error) {
return res
.status((error as AxiosError).response?.status as number)
.json((error as AxiosError).response?.data);
}
}
And it works perfectly fine, I don't have this Node.js error any more because of response with Express response class, and I'm able to set cookie.
EDIT
I have improved this code in even better way by using fastify and in the whole pipeline cookie is set in header. First of all, on back-end install #fastify/cookie and #nestjs/platform-fastify. Then, add this in file, where you start you Nest.js app:
import {
FastifyAdapter,
NestFastifyApplication
} from '#nestjs/platform-fastify';
import { fastifyCookie } from '#fastify/cookie';
async function bootstrap() {
const PORT = process.env.PORT || 3002;
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
);
await app.register(fastifyCookie, {
secret: 'my-secret'
});
This will allow you to use FastifyReply from fastify, this will eliminate this Node.js error as response class:
import { FastifyReply } from 'fastify';
#ApiTags('User')
#Controller('user')
export class UserController {
constructor(private userService: UserService) {}
#Post('/sign-in')
async signIn(
#Body() signInUserDto: SignInUserDto,
#Res({ passthrough: true }) res: FastifyReply
) {
const { _at, _rt } = await this.userService.signIn(signInUserDto);
res.setCookie('_rt', _rt);
return res.send(_at);
}
...
And the last step, on front-end endpoint, using cookie, parse this cookie and send it to front.
const { data, headers } = await api.post('/user/sign-in', req.body)
if (headers["set-cookie"]) {
const refreshToken = headers["set-cookie"][0].split('=')[1];
res.setHeader('Set-Cookie', serialize(
'_rt', refreshToken, { path: '/', httpOnly: true })
);
}
return res.json(data)
And this is the best way, that I've found, because it allows you to send cookie in header though all pipeline, not in body and then delete it, and this solution eliminates this strange Node.js error.

Vue.js - Export an axios interceptor

Good evening everyone, here I have a problem with my interceptor in VueJS. I don't understand where my problem comes from, and I'm pulling my hair out...
I've watched several tutorials, I've watched several topics on stackoverflow, but I don't understand what's going on at all.
When I put a debugger on, it's triggered, but when I switch to "axios.interceptors" it tells me that axios is undefined, it's incomprehensible...
import axios from 'axios';
debugger;
axios.interceptors.response.use(function (response) {
console.log(response);
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
console.log(error);
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
const token = localStorage.getItem('token');
export default axios.create({
baseURL: process.env.VUE_APP_URL_API,
headers: {
Authorization: `Bearer ${token}`
}
})
The code above is called in my VueX Store.
import Http from "../../api/http";
export default {
state: {
customers: {},
customer: {},
},
getters: {
customers: state => state.customers,
},
mutations: {
SET_CUSTOMERS(state, customers) {
state.customers = customers;
}
},
actions: {
loadCustomers({commit}) {
Http.get('/customers').then(result => {
commit('SET_CUSTOMERS', result.data.data );
}).catch(error => {
throw new Error(`API ${error}`);
});
}
}
};
I want to trigger http code 401 to logout my user and destroy the token in the browser.
If anyone could help me, I would be delighted, thank you very much.
Regards,
Christophe
As shown in the interceptor docs, just below the example interceptors, if you use an instance, you have to add the interceptor to it:
import axios from 'axios';
const token = localStorage.getItem('token');
const instance = axios.create({
baseURL: process.env.VUE_APP_URL_API,
headers: {
Authorization: `Bearer ${token}`
}
})
instance.interceptors.response.use(function (response) {
console.log(response);
// Any status code within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
console.log(error);
// Any status codes outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
export default instance;
For people which are wondering how the issue has been solved, there is my code :)
success.js
export default function (response) {
return response
}
failure.js
import router from 'vue-router'
export default function (error) {
switch (error.response.status) {
case 401:
localStorage.removeItem('jwt.token')
router.push({
name: 'Login'
})
break
}
return Promise.reject(error)
}
adding this to main.js
const token = localStorage.getItem('jwt.token')
if (token) {
axios.defaults.headers.common.Authorization = token
}
create api.js which is my client for all the request, so my request are always passing by this.
import axios from 'axios'
import success from '#/interceptors/response/success'
import failure from '#/interceptors/response/failure'
const api = axios.create({
baseURL: process.env.VUE_APP_URL_API
})
api.interceptors.request.use((config) => {
const token = localStorage.getItem('jwt.token')
config.headers.Authorization = `Bearer ${token}`
return config
})
api.interceptors.response.use(success, failure)
export default api
I hope it will be usefull :)

How to test header, axios.defaults.headers, with Jest?

In my application, I have a middleware to check for a token in a req in order to access private routes:
const config = require('../utils/config');
const jwt = require('jsonwebtoken');
module.exports = function (req, res, next) {
let token = req.header('x-auth-token');
if (!token) {
return res.status(401).json({ msg: 'No token. Authorization DENIED.' });
}
try {
let decoded = jwt.verify(token, config.JWTSECRET);
req.user = decoded.user;
next();
} catch (err) {
return res.status(401).json({ msg: 'Token is invalid.' });
}
};
In order to send a req with the correct token in my program's Redux actions, I call the following function, setAuthToken(), to set the auth token:
import axios from 'axios';
const setAuthToken = token => {
if (token) {
axios.defaults.headers.common['x-auth-token'] = token;
} else {
delete axios.defaults.headers.common['x-auth-token'];
}
};
export default setAuthToken;
My Redux action using axios and the setAuthToken() function:
export const addPost = (formData) => async (dispatch) => {
try {
//set the token as the header to gain access to the protected route POST /api/posts
if (localStorage.token) {
setAuthToken(localStorage.token);
}
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const res = await axios.post('/api/posts', formData, config);
// ...
} catch (err) {
// ...
}
};
How can I write a test to test setAuthToken()? The following is my attempt:
import axios from 'axios';
import setAuthToken from '../../src/utils/setAuthToken';
describe('setAuthToken utility function.', () => {
test('Sets the axios header, x-auth-token, with a token.', () => {
let token = 'test token';
setAuthToken(token);
expect(axios.defaults.headers.common['x-auth-token']).toBe('test token');
});
});
The following is the error I get:
TypeError: Cannot read property 'headers' of undefined
Looking up this error, it sounds like it is because there is no req in my test. If that is the case, how can I re-write my test to send a req? If not, what am I doing wrong?
Here is my case, there is a __mocks__ directory in my project. There is a mocked axios. More info about __mocks__ directory, see Manual mock
__mocks__/axios.ts:
const axiosMocked = {
get: jest.fn()
};
export default axiosMocked;
When I run the test you provide, got the same error as yours. Because mocked axios object has no defaults property. That's why you got the error.
import axios from 'axios';
import setAuthToken from './setAuthToken';
jest.unmock('axios');
describe('setAuthToken utility function.', () => {
test('Sets the axios header, x-auth-token, with a token.', () => {
let token = 'test token';
setAuthToken(token);
expect(axios.defaults.headers.common['x-auth-token']).toBe('test token');
});
});
So I use jest.unmock(moduleName) to use the real axios module instead of the mocked one.
After that, it works fine. Unit test result:
PASS src/stackoverflow/64564148/setAuthToken.test.ts (10.913s)
setAuthToken utility function.
✓ Sets the axios header, x-auth-token, with a token. (5ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 13.828s
Another possible reason is you enable automock. Check both command and jest.config.js file.

ReactJS Axios Interceptor Response/Request

I have a problem on using the axios interceptor in my react app. I want to achieve putting the header token just once in my react app. So thats why Im putting it in the interceptor. At the same time, i also want to have just one declaration to get the error. So i dont need to show the error in every page. I’m wondering if i’m using it correctly in my code below? Is there a way that i can shorten it cause i’m declaring it twice for response and request?
export function getAxiosInstance() {
if (axiosInstance === null) {
axiosInstance = axios.create({
baseURL: API_URL,
});
}
axiosInstance.interceptors.request.use(
(config) => {
if (config.baseURL === API_URL && !config.headers.Authorization) {
const token = store.getState().auth.access_token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
console.log(config);
}
}
return config;
},
(error) => {
console.log(error);
store.dispatch(setAPIErrorMessage(error.message));
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.use(
(config) => {
if (config.baseURL === API_URL && !config.headers.Authorization) {
const token = store.getState().auth.access_token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
console.log(config);
}
}
return config;
},
(error) => {
console.log(error);
store.dispatch(setAPIErrorMessage(error.message));
return Promise.reject(error);
}
);
return axiosInstance;
}
You don't need to set authorization header in interceptors.response, you only need this in request interceptor.
You could declare your error handling in a closure function (with the action dispatch) to avoid repeating yourself.
I would also suggest to avoid handling errors directly in axios instance. You could define async redux actions using https://github.com/reduxjs/redux-thunk, and handle network errors at redux level (using fetchBegin, fetchSuccess, fetchFailure actions pattern). Then axios setup and redux setup would not be coupled anymore, which will allow you to change these tools in the future.

Async Headers in Ember.js

I need to insert the headers into the adapter asynchronously because the token function should check if the token is not expired and refresh it (per Ajax) if it is expired and then return the new token. But it seems the adapter can't handle the returned promises. Can anybody help me out with that issue?
import DS from 'ember-data';
import config from '../config/environment';
export default DS.JSONAPIAdapter.extend({
// Application specific overrides go here
host: config.APP.api_endpoint,
headers: Ember.computed(function() {
return this.auth.getToken().then(
(accessToken) => {
if (accessToken) {
const auth = `Bearer ${accessToken}`;
return {
Authorization: auth
};
} else {
return {
};
}
});
}).volatile()
});
You returning promise from computed property, this will never work. There is a stable solution for authorization/authentication, ember-simple-auth. It will handle tasks with saving and refreshing tokens.

Categories

Resources