Possible to return 401 instead of 403 for expired token? - swagger-tools

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.

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.

how to go from axios interceptor to then

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

Refreshing access token with multiple requests

Im struggling with getting axios interceptors to work.
When my token expires, i need it to refresh the access token and retry the original request once the token is refreshed.
I have this part working.
The problem is if i have concurrent api calls it will only retry the first request when the token was first invalid.
Here is my interceptor code:
export default function execute() {
let isRefreshing = false
// Request
axios.interceptors.request.use(
config => {
var token = Storage.getAccessToken() //localStorage.getItem("token");
if (token) {
console.log('Bearer ' + token)
config.headers['Authorization'] = 'Bearer ' + token
}
return config
},
error => {
return Promise.reject(error)
}
)
// Response
axios.interceptors.response.use(
response => {
return response
},
error => {
const originalRequest = error.config
// token expired
if (error.response.status === 401) {
console.log('401 Error need to reresh')
originalRequest._retry = true
let tokenModel = {
accessToken: Storage.getAccessToken(),
client: 'Web',
refreshToken: Storage.getRefreshToken()
}
//Storage.destroyTokens();
var refreshPath = Actions.REFRESH
if (!isRefreshing) {
isRefreshing = true
return store
.dispatch(refreshPath, { tokenModel })
.then(response => {
isRefreshing = false
console.log(response)
return axios(originalRequest)
})
.catch(error => {
isRefreshing = false
console.log(error)
// Logout
})
} else {
console.log('XXXXX')
console.log('SOME PROBLEM HERE') // <------------------
console.log('XXXXX')
}
} else {
store.commit(Mutations.SET_ERROR, error.response.data.error)
}
return Promise.reject(error)
}
)
}
I'm not sure what i need in the else block highlighted above.
EDIT:
When I do
return axios(originalRequest)
in the else block it works, however im not happy with the behaviours. It basically retries all the requests again and again until the token is refreshed.
I would rather it just retried once after the token had been refreshed
Any ideas
Thanks
You can just have additional interceptor which can refresh token and execute your pending requests.
In this, countDownLatch class can help.
Here is sample Interceptor code,
class AutoRefreshTokenRequestInterceptorSample() : Interceptor {
companion object {
var countDownLatch = CountDownLatch(0)
var previousAuthToken = ""
const val SKIP_AUTH_TOKEN = "SkipAccessTokenHeader"
const val AUTHORIZATION_HEADER = "AUTHORIZATION_HEADER_KEY"
}
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response? {
val request = chain.request()
if (shouldExecuteRequest(request)) {
// Execute Request
val response = chain.proceed(request)
if (!response.isSuccessful) {
// Failed Case
val errorBody = response.peekBody(java.lang.Long.MAX_VALUE).string()
val error = parseErrorModel(errorBody)
// Gives Signal to HOLD the Request Queue
countDownLatch = CountDownLatch(1)
handleError(error!!)
// After updating token values, execute same request with updated values.
val updatedRequest = getUpdatedRequest(request)
// Gives Signal to RELEASE Request Queue
countDownLatch.countDown()
//Execute updated request
return chain.proceed(updatedRequest)
} else {
// success case
return response
}
}
// Change updated token values in pending request objects and execute them!
// If Auth header exists, and skip header not found then hold the request
if (shouldHoldRequest(request)) {
try {
// Make this request to WAIT till countdown latch has been set to zero.
countDownLatch.await()
} catch (e: Exception) {
e.printStackTrace()
}
// Once token is Updated, then update values in request model.
if (previousAuthToken.isNotEmpty() && previousAuthToken != "newAccessToken") {
val updatedRequest = getUpdatedRequest(request)
return chain.proceed(updatedRequest)
}
}
return chain.proceed(request)
}
private fun handleError(error: ErrorDto) {
// update your token as per your error code logic
//Here it will make new API call to update tokens and store it in your local preference.
}
/***
* returns Request object with updated token values.
*/
private fun getUpdatedRequest(request: Request): Request {
var updateAuthReqBuilder: Request.Builder = request.newBuilder()
var url = request.url().toString()
if (url.contains(previousAuthToken.trim()) && previousAuthToken.trim().isNotEmpty()) {
url = url.replace(previousAuthToken, "newAccessToken")
}
updateAuthReqBuilder = updateAuthReqBuilder.url(url)
// change headers if needed
return updateAuthReqBuilder.build()
}
private fun shouldExecuteRequest(request: Request) =
shouldHoldRequest(request) && isSharedHoldSignalDisabled()
/**
* If count down latch has any value then it is reported by previous request's error signal to hold the whole pending chain.
*/
private fun isSharedHoldSignalDisabled() = countDownLatch.count == 0L
private fun shouldHoldRequest(request: Request) = !hasSkipFlag(request) && hasAuthorizationValues(request)
private fun hasAuthorizationValues(request: Request) = isHeaderExist(request, AUTHORIZATION_HEADER)
private fun hasSkipFlag(request: Request) = isHeaderExist(request, SKIP_AUTH_TOKEN)
private fun isHeaderExist(request: Request, headerName: String): Boolean {
return request.header(headerName) != null
}
private fun parseErrorModel(errorBody: String): Error? {
val parser = JsonParser()
// Change this logic according to your requirement.
val jsonObject = parser.parse(errorBody).asJsonObject
if (jsonObject.has("Error") && jsonObject.get("Error") != null) {
val errorJsonObj = jsonObject.get("Error").asJsonObject
return decodeErrorModel(errorJsonObj)
}
return null
}
private fun decodeErrorModel(jsonObject: JsonObject): Error {
val error = Error()
// decode your error object here
return error
}
}
This is how I do:
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
axios.interceptors.response.use(
response => response,
error => {
const originalRequest = error.config;
if (error.response.status === 400) {
// If response is 400, logout
store.dispatch(logout());
}
// If 401 and I'm not processing a queue
if (error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// If I'm refreshing the token I send request to a queue
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
})
.then(() => {
originalRequest.headers.Authorization = getAuth();
return axios(originalRequest);
})
.catch(err => err);
}
// If header of the request has changed, it means I've refreshed the token
if (originalRequest.headers.Authorization !== getAuth()) {
originalRequest.headers.Authorization = getAuth();
return Promise.resolve(axios(originalRequest));
}
originalRequest._retry = true; // mark request a retry
isRefreshing = true; // set the refreshing var to true
// If none of the above, refresh the token and process the queue
return new Promise((resolve, reject) => {
// console.log('REFRESH');
refreshAccessToken() // The method that refreshes my token
.then(({ data }) => {
updateToken(data); // The method that sets my token to localstorage/Redux/whatever
processQueue(null, data.token); // Resolve queued
resolve(axios(originalRequest)); // Resolve current
})
.catch(err => {
processQueue(err, null);
reject(err);
})
.then(() => {
isRefreshing = false;
});
});
}
return Promise.reject(error);
},
);
I don't know what is the schema of your token (after decrypted) but one of the attributes which is a good practice to keep is the exp "expiration_date".
Said so, having the expiration date you can know when you should refresh your token.
Without understanding your architecture is hard to inform the right solution. But let's say you are doing everything manually, usually onIdle/onActive is when we check if the user session is still ok, so at this time you could use the token info to know if you should refresh its value.
It is important to understand this process because the token should be refreshed only if the user is constantly active and it is about to expire (like 2min before).
Please refer to angular version of the code for which i was facing the same problem and after changing many approaches this was my final code which is working at its best.
Re Initaite the last failed request after refresh token is provided

Sails.js can't access data model in the middleware: Unexpected token

sails.js newbie here.
I can't access my User model within my middleware. It says unexpected token.
Here's my middleware,
isAuthenticated: (function(){
return function authHandler(req, res, next) {
let payload;
try {
payload = decode(req);
let expTime = moment.tz(payload.exp, 'GMT').toDate();
let currentTIme = moment.tz('GMT').toDate();
if (currentTIme > expTime) {
return res.status(401).json({message: 'JWT token expired.'});
} else {
>> const user = await User.findOne({id: payload.id});
if (user) {
req.payload = {
userId: user.id
};
return next()
} else {
return res.status(401).json({message: 'User doesn\'t exist.'});
}
}
} catch (err) {
return res.serverError();
}
}
})()
}
I am trying to setup a authentication middleware. In my global settings models is set to true.
I tried, sails.models.user but even for that I get unexpected token.
You need to put the async keyword, async function(..){....await....}.
The await keyword is only valid inside async functions.

Angular2 Stripe integration stripeResponseHandler cannot access this

I'm integrating Stripe payments with Angular2 (actually Ionic but the code is the same)
the call to Stripe.card.createToken is successful and returns a token
but in stripeResponseHandler which is an async callback, I cannot access any of the "this" variables. for example I cannot set this.amount = 10 and I cannot call this._http.post
how can I access the "this" variables ? I'm trying to http post the token and the amount to an API to make the payment
constructor(private _navController: NavController,
private _http: Http) { }
submitPayment() {
Stripe.setPublishableKey(this.key);
this.card = new Card();
this.card.number = this.cardNumber;
this.card.cvc = this.cardCVC;
this.card.exp_month = this.cardExpMonth;
this.card.exp_year = this.cardExpYear;
this.card.address_zip = this.cardAddressZip;
try {
Stripe.card.createToken(this.card, this.stripeResponseHandler);
}
catch (e) {
alert(e.message);
}
// Prevent the form from being submitted:
return false;
}
stripeResponseHandler(status, response) {
if (response.error) { // Problem!
alert(response.error);
} else { // Token was created!
// Get the token ID:
alert(response.id);
try {
this.amount = 10;
let payment = new Payment();
payment.token = response.id;
payment.amount = this.amount;
let body = JSON.stringify(payment);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
this._http.post(this.url, body, options)
.map(res => res.json())
.catch(this.handleError);
}
catch (e) {
alert(e.message);
}
}
}
handleError(error: Response) {
// may send the error to some remote logging infrastructure
// instead of just logging it to the console
console.error(error);
alert('error' + error.text + " " + error.statusText);
return Observable.throw(error.json().error || 'Server error');
}
If you just pass the function reference, then JavaScript doesn't keep the this reference. You have to take care of this explicitely:
Instead of
Stripe.card.createToken(this.card, this.stripeResponseHandler);
use
Stripe.card.createToken(this.card, (status, person) => this.stripeResponseHandler(status, person));
See also https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions
or
Stripe.card.createToken(this.card, this.stripeResponseHandler.bind(this));