how to go from axios interceptor to then - axios

this is my code
axios
.post("/api/addcart", body)
.then((response) => {
if (response.data.success) {
alert("success");
} else {
alert(response.data.message);
console.log(response.data);
}
}) .catch(() => {
console.log("catch");
});
Created a logic that re-requests when JWT expires with axios interceptor
axios.interceptors.response.use(
function (response) {
return response;},
async function (error) {
const originalRequest = error.config;
console.log(error.response.status);
if (error.response.status === 406) {
axios.post("/api/reissue").then((response) => {
console.log(response.data.success);
if (response.data.success) {
originalRequest._retry = true;
return axios(originalRequest);
} //Removed irrelevant code for readability
return;
// return axios(error.config);
}
return Promise.reject(error);
}
);
The problem I'm having here is after the reissue(jwt token renewal request) is successful.
406 is the response code given when the token expires (I set it temporarily)
I want the alert(success) to be generated by going back to the code above after the access token is updated But this request seems to go to catch
Going back to the beginning, I want to renew if the token has expired and go back to then if the renewal request is successful, but I don't know how. Please let me know if there is a better way

Related

Axios response interceptor for refreshing token keeps firing in Vue 3

I'm trying to implement a refresh token with Vue 3 and Java for backend. It is working but interceptor keeps firing.
The logic: On every request there's a JWT Authorization header that authenticates the user. If that expires, there's a cookie endpoint in place ready to refresh the JWT.
I am using axios and interceptor response to check if the client gets a 401 to try and refresh the JWT. The cookie may be valid or not.
The problem is that the interceptor to refresh the JWT never stops firing, and I think I have something wrong with the synchronization of the requests. Below is my code:
Api.js:
import axios from "axios";
const instance = axios.create({
baseURL: "MY_URL",
});
export default instance;
token.service.js:
class TokenService {
getLocalRefreshToken() {
const user = JSON.parse(localStorage.getItem("user"));
return user?.refreshToken;
}
getLocalAccessToken() {
const user = JSON.parse(localStorage.getItem("user"));
return user?.accessToken;
}
updateLocalAccessToken(token) {
let user = JSON.parse(localStorage.getItem("user"));
user.accessToken = token;
localStorage.setItem("user", JSON.stringify(user));
}
getUser() {
return JSON.parse(localStorage.getItem("user"));
}
setUser(user) {
// eslint-disable-next-line no-console
console.log(JSON.stringify(user));
localStorage.setItem("user", JSON.stringify(user));
}
removeUser() {
localStorage.removeItem("user");
}
}
export default new TokenService();
setupInterceptors.js:
import axiosInstance from "./api";
import TokenService from "./token.service";
const setup = (store) => {
axiosInstance.interceptors.request.use(
(config) => {
const token = TokenService.getLocalAccessToken();
if (token) {
config.headers["Authorization"] = 'Bearer ' + token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.eject()
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const originalConfig = err.config;
if (originalConfig.url !== "/auth/login" && err.response) {
// Access Token was expired
if (err.response.status === 401 && !originalConfig._retry) {
originalConfig._retry = true;
try {
const rs = await axiosInstance.post("/auth/refreshtoken", {
refreshToken: TokenService.getLocalRefreshToken(),
});
const { accessToken } = rs.data;
store.dispatch("auth/refreshToken", accessToken);
TokenService.updateLocalAccessToken(accessToken);
return axiosInstance(originalConfig);
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
}
);
};
export default setup;
try this out and make sure you use another instance of Axios for the refresh token request
// to be used by the interceprot
firstAxiosInstance = axios.create({ baseURL: MY_URL });
//to be used by the refresh token API call
const secondAxiosInstance = axios.create({ baseURL: MY_URL});
firstAxiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
// this is the original request that failed
const originalConfig = err.config;
// decoding the refresh token at this point to get its expiry time
const decoded = jwt.decode(localStorage.getItem('refreshToken'));
// check if the refresh token has expired upon which logout user
if (decoded.exp < Date.now() / 1000) {
store.commit('logout');
router.push('/');
}
// get new access token and resend request if refresh token is valid
if (decoded.exp > Date.now() / 1000) {
if (err.response.status === 401) {
originalConfig._retry = true;
try {
const rs = await requestService.post('/api-v1/token/refresh/', {
refresh: localStorage.getItem('refreshToken'),
});
store.commit('update_aceess_token', rs.data);
err.config.headers.Authorization = `Bearer ${rs.data.access}`;
return new Promise((resolve, reject) => {
requestService
.request(originalConfig)
.then((response) => {
resolve(response);
})
.catch((e) => {
reject(e);
});
});
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
},
);
try clean el token authorization before send request refresh, by example
in mutations(vuex)
clearAccessToken(state) {
state.access_token = ''
TokenService.removeAccessTokenApi();
},
For me it was fixed by not using the same axios instance for the refresh token request.

Axios- redirect in issue

I'm having trouble getting redirects to work after accepting a get request from Axios. I do know that the request is being sent and that it at least gets some response from the URL route,
when i console response.data.redirect it return undefined
const onSubmitHandler = (e) => {
e.preventDefault()
axios.get('/get/user')
.then(function (response) {
if (response.data.redirect == '/' || response.data.redirect == '/login' ) {
window.location = "/login"
} else {
console.log(response.data)
}
})
.catch(function(error) {
window.location = "/login"
})
}
I received status code 302 but it automatically redirects to the page without page refresh shows the redirected page on the same page in a div section.
Anyone knows how can I use interceptor with Axios such that if the status code fall in 200 series then does something else page refresh. I checked the Axios docs but there is no implementation
Try this for interceptors
axios.interceptors.response.use(
response => {
return response.data;
},
err => {
//return new Promise((resolve, reject) => {
//err.response.status for getting error status
// }
throw err;
});
}
);

Possible to return 401 instead of 403 for expired token?

I want to be able to return a 401 on JWT token expiry (which I believe is the correct response?) but it returns a 403 no matter what
here is where I tell swagger tools to use my bearer token verification logic:
swaggerTools.initializeMiddleware(swaggerObject, (middleware) => {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
//enable cors
app.use(
cors({
methods: ["GET", "POST", "PATCH", "DELETE"],
origin: "*",
preflightContinue: false,
optionsSuccessStatus: 204,
})
);
//verify bearer token for protected apis
app.use(
middleware.swaggerSecurity({
Bearer: (req, authOrSecDef, token, callback) =>
//if this is a protected endpoint then token is verified to allow or deny access
securityService.verifyToken(
req,
authOrSecDef,
token,
callback
),
})
);
and here is my verifyToken function..
verifyToken: (req, authOrSecDef, token, callback) => {
const sendError = (error) => {
if (error) {
if (error.name === "TokenExpiredError") {
return new Problem(401, error.message);
}
} else {
return new Problem(403);
}
};
if (token && token.toLowerCase().indexOf("bearer ") === 0) {
var tokenString = token.split(" ")[1];
let completeDecodedToken = jwt.decode(tokenString, { complete: true });
if (!completeDecodedToken) {
return callback(sendError());
}
//use header.kid to find correct cognito jwk to use
let jwk = jwks.keys.find(
(jwk) => jwk.kid == completeDecodedToken.header.kid
);
if (!jwk) {
//must have passed in a token obtained from somewhere else
return callback(sendError());
}
const pem = jwkToPem(jwk);
jwt.verify(
tokenString,
pem,
{ algorithms: ["RS256"] },
(verificationError, decodedToken) => {
if (verificationError === null) {
// check if the issuer matches
var issuerMatch =
decodedToken.iss ===
`https://cognito-idp.${process.env.AWS_REGION}.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}`;
if (issuerMatch) {
//add the token to the request so that we
//can access it downstream in endpoint if we need
req.auth = decodedToken;
req.tokenString = tokenString;
//if there is no error, just return null in the callback
return callback(null);
} else {
console.error("Issuer did not match");
//return the error in the callback if there is one
return callback(sendError());
}
} else {
//return the error in the callback if the JWT was not verified
return callback(sendError(verificationError));
}
}
);
} else {
return callback(sendError());
}
},
but actually when I look in the swagger-tools source (swagger-security.js) I only see 403 in there.. any advice ?
You see 403 always because that is the else part and your error object is null, If you are able to debug then you can console log the error
Also your assumption is that error is returned from below line, which is highly likely wrong because error is null.
//return the error in the callback if the JWT was not verified
return callback(sendError(verificationError));
In my opinion, error is returned from
else {
return callback(sendError());
}
If that is the case then you can send your custom "UnauthorizedError" object from the desired place.

I need help to verify that jwt is valid or otherwise create a new one and if the token sent is not correct return an error in nestjs middleware

I have already implemented the jwt and it works correctly but when creating a middleware that verifies that the token is still active and that it is valid, if the token has already expired, you must create a new one in case you can not create a new one, return a 404 error with a message, So far I have only been able to obtain the token and decode it, I need to be able to verify it and return an error response or let it continue.
this is my middleware code code:
import { JwtService } from '#nestjs/jwt';
import { Injectable, NestMiddleware } from '#nestjs/common';
#Injectable()
export class JwtMiddleware implements NestMiddleware {
valid = null;
decode = null;
cleanToken = null;
constructor(private readonly jwtServ: JwtService) {}
use(req: Request, res: Response, next: Function) {
const token = req.headers['authorization'];
try {
this.cleanToken = token.replace('Bearer','').trim();
this.decode = this.jwtServ.decode(this.cleanToken);
} catch (error) {
// console.log(error);
}
try {
this.valid = this.jwtServ.verify(this.cleanToken);
} catch (error) {
console.log(error.name);
console.log(error.message);
console.log(error.expiredAt);
}
next();
}
}
up to here I could only print in console the error of verifying jwt but it is not the correct way to do it besides that I can not return a valid answer to the client
console print:
TokenExpiredError
jwt expired
2019-03-27T00:18:56.000Z
I searched the jwt documentation to see how to validate the token and found it:
https://github.com/auth0/node-jsonwebtoken
// verify a token symmetric
jwt.verify(token, 'shhhhh', function(err, decoded) {
console.log(decoded.foo) // bar
});
but in nestjs it does not work that way. The function "function (err, decoded)" can not be implemented like this, so it marks me errors so I had to put it in a trycatch
I also tried this:
this.jwtServ.verify(token,(err, decoded) => {
if (err) {
return res.status(401).json({
ok: false,
message: 'Invalid Token',
errors: err
});
}
req.user = decoded.user;
next();
});
in the nestjs documentation he says:
The Nest middleware, by default, are equal to express middleware. Here's a great list of the middleware capabilities copied from the official express documentation
https://docs.nestjs.com/middleware
I've already tried this and it does not work
return res.status(401).json({
ok: false,
message: 'Invalid Token',
errors: err
});
Any help is welcome, thanks!
My solution this:
try {
const validated = jwt.verify(token, {publicKey: myPublicKey})
return validated
} catch(error) {
// here comes if token invalid or expired
}
try this.. use 'jsonwebtoken' package
import * as jwt from 'jsonwebtoken';
jwt.verify(token, 'shhhhh', function(err, decoded){
console.log(decoded.foo) // bar
});
At present, through your description, I think your main problem is that you want to implement jwt authentication and interrupt the request in the middleware and return the expected return value, you can refer to my example.
// jwt.middleware.ts
import { Injectable, NestMiddleware } from '#nestjs/common'
import { Request, Response, NextFunction } from 'express'
import jwt from 'jsonwebtoken'
#Injectable()
export class JwtMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
if (!req.headers.authorization) {
res.writeHead(401, { 'content-type': 'application/json' })
res.write(JSON.stringify({
msg: 'Authorization is required',
}))
res.end()
} else {
const token = req.headers.authorization.replace('Bearer','').trim()
const validated = jwt.verify(token, 'secret_key')
// Other requests using this middleware can get the parsed value in the
// parameter, you can also analyze the parsed value and return res as
// above for those that do not match
req.body._validated = validated
}
next()
}
}
As above, you can indicate the completion of middleware execution by executing the next function, and the processed content will be passed to the next layer, or you can call res.end to interrupt the request and describe the content of res.
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(JwtMiddleware).exclude('api/users/login').forRoutes('api')
}
}
Refer to:
https://docs.nestjs.com/middleware
How to send response from middleware created in a Nest fastify server?
https://www.npmjs.com/package/jsonwebtoken

How to get axios error response INTO the redux saga catch method

With axios the code is:
export const createBlaBla = (payload) => {
return axios.post('/some-url', payload)
.then(response => response)
.catch(err => err);
}
And then I'm using this with redux-saga like this:
function* createBlaBlaFlow(action) {
try {
const response = yield call(createBlaBla, action.payload);
if (response) {
yield put({
type: CREATE_BLA_BLA_SUCCESS
});
}
} catch (err) {
// I need the error data here ..
yield put({
type: CREATE_BLA_BLA_FAILURE,
payload: 'failed to create bla-bla'
});
}
}
In case of some error on the backend - like invalid data send to the backend - it returns a 400 response with some data:
{
"code":"ERR-1000",
"message":"Validation failed because ..."
"method":"POST",
"errorDetails":"..."
}
But I don't receive this useful data in the catch statement inside the saga. I can console.log() the data in the axios catch statement, also I can get it inside the try statement in the saga, but it never arrives in the catch.
Probably I need to do something else? ... Or the server shouldn't return 400 response in this case?
So, I came up with two solutions of this problem.
===
First one - very dump workaround, but actually it can be handy in some specific cases.
In the saga, right before we call the function with the axios call inside, we have a variable for the errors and a callback that sets that variable:
let errorResponseData = {};
const errorCallback = (usefulErrorData) => {
errorResponseData = usefulErrorData;
};
Then - in the axios method we have this:
export const createBlaBla = (payload, errCallback) => {
return axios.post('/some-url', payload)
.then(response => response)
.catch(err => {
if (err && err.response.data && typeof errCallback === 'function') {
errCallback(err.response.data);
}
return err;
});
}
This way, when we make request and the backend returns errors - we'll call the callback and will provide the errors from the backend there. This way - in the saga - we have the errors in a variable and can use it as we want.
===
However, another solution came to me from another forum.
The problem I have is because in the method with the axios call I have catch, which means that the errors won't bubble in the generator. So - if we modify the method with the axios call like this:
export const createBlaBla = (payload) => {
return axios.post('/some-url', payload)
}
Then in the catch statement in the saga we'll have the actual backend error.
Hope this helps someone else :)
In your API call you can do the following:
const someAPICall = (action) => {
return axios.put(`some/path/to/api`, data, {
withCredentials: true,
validateStatus: (status) => {
return (status == 200 || status === 403);
}
});
};
Please note the validateStatus() part - this way when axios will encounter 200 or 403 response, it will not throw Error and you will be able to process the response after
const response = yield call(someAPICall, action);
if (response.status === 200) {
// Proceed further
} else if (response.status === 403) {
// Inform user about error
} else {
...
}