After reading through multiple JWT refresh token tutorials I am still unclear on how the flow of API calls is supposed to work.
Here is my understanding:
1) Client is in posession of access token and refresh token. It submits the access token to api/getCustomerData.
2) Let's say the access token is expired. Server responds with a 401.
3) Client responds to the 401 request with the refresh token to api/token.
4) Server responds with new access token since the refresh token is still valid.
5) Client then makes a new request to api/getCustomerData with the new access token.
My impression is that this is an excessive number of API calls, but I am not seeing any tutorial that clarifies a way to do this more efficiently. As it stands it seems like if I am following this pattern, each API request will look like this:
const getCustomers = async () => {
const config = {
data: body,
withCredentials: true,
method: 'POST' as 'POST',
}
await axios(address + '/api/getCustomerData', config)
.then((response) => {
...
})
.catch((error: any) => {
const response = error.response;
if (response) {
if (response.status === 401) {
if (!failcount){
failcount++;
getCustomers();
}
else {
history.push('/login')
}
}
}
})
}
What you can do is pre-emptively get a new access token using a refresh token shortly before you know the current access token is about to expire, using the token's "exp (expiration time)" claim. That at least removes one API call - the call with the access token that causes a 401.
Related
I have a small ExpressJS server with a login feature. Some pages are secured with self-written authenticate middleware that checks if your Json WebToken is correct.
I read that just one Json WebToken isn't secure enough and that you need a refresh token as well. So I added a Refresh Token. This is all verified on the server.
Now when the token is expired, I check if the user has a refreshToken and if so I create a new token and set it as a cookie. Like this:
const jwt = require('jsonwebtoken');
// Simple array holding the issued refreshTokens
const { refreshTokens } = require('../lib/auth.js');
module.exports.authenticateToken = function(req, res, next) {
const token = req.cookies.token;
const refreshToken = req.cookies.refreshToken;
if(token === null) return res.redirect('/login');
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
if(verified) return next();
} catch(err) {
try {
const refreshInDb = refreshTokens.find(token => token === refreshToken);
const refreshVerified = refreshInDb && jwt.verify(refreshToken, process.env.REFRESHTOKEN_SECRET);
const newToken = jwt.sign({ email: refreshVerified.email }, process.env.TOKEN_SECRET, { expiresIn: 20 });
res.cookie('token', newToken, { maxAge: 900000, httpOnly: true });
return next();
} catch(err) {
return res.redirect('/login');
}
}
};
Now is this code correct & secure enough for a small webapplication? Am I missing stuff? It feels so... easy?
Seems like you are veryfing both tokens at the same endpoint. This approach is wrong.
In your login endpoint, validate the user and password against database. If credentials are correct we respond with an access token and a refresh token
router.post('/login',
asyncWrap(async (req, res, next) => {
const { username, password } = req.body
await validateUser(username, password)
return res.json({
access_token: generateAccessToken(), // expires in 1 hour
refresh_token: generateRefreshToken(), // expires in 1 month
})
})
)
In your authenticated routes, you should validate only the access token (the user should send ONLY this one)
// middleware to validate the access token
export const validateToken = asyncWrap(async (req, res, next) => {
const data = await verifyAccessToken(req.headers.authorization)
req.auth = data
next()
})
If the access token expires, the user should refresh its token. In this endpoint we will validate the refresh token and respond with two new tokens:
router.post('/refresh',
asyncWrap(async (req, res, next) => {
const { refresh_token } = req.body
await verifyRefreshToken(refresh_token)
return res.json({
access_token: generateAccessToken(), // expires in 1 hour
refresh_token: generateRefreshToken(), // expires in 1 month
})
})
)
I read that just one Json WebToken isn't secure enough and that you need a refresh token as well. So I added a Refresh Token. This is all verified on the server.
Using refresh tokens has nothing to do with security of the JWT or access token. Refresh tokens are just a UX feature. They allow you to get new access tokens without asking the user to authorize again. Having a refresh token in your app doesn't automatically make it more secure.
Now when the token is expired, I check if the user has a refreshToken and if so I create a new token and set it as a cookie. Like this:
When implemented this way the refresh token doesn't grant any more security to your application. You could as well keep the access tokens in the db and refresh them when they are expired.
Are you sure that you need JWTs at all? It looks like you're using them as you would use a session based on cookies. It should be simpler to deal with sessions. You are using http-only cookies for your tokens so you already use it pretty much like a session.
Now is this code correct & secure enough for a small webapplication?
Secure enough is a concept that depends on the data that your application has access to. If it's nothing sensitive, and you know that your app can't really be abused by an attacker, then it is fine to have only some basic security in place.
I'm working on a React native application and I need to set Authentication header on my sensitive requests.
But my accessToken (JWT) expires after 10 seconds. So before any request I have to check that if the token is expired renew it using a refreshToken and then call last request again.
I'm able to do all of these except last part (bold text).
And I'm using Axios for sending request to server.
Any idea?
So the idea is to use a response interceptor that works after the response is available, but before it is passed down in the code.
It looks for unauthenticated error, which corresponds to statusCode 401
Keep in mind that this is a pseudo-code and You have to modify some parts like
auth.setToken().
const createAxiosResponseInterceptor = () => {
const interceptor = axios.interceptors.response.use(
(response) => response,
(error) => {
// Reject promise if not 401 error
if (error.response.status !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Remove the interceptor so it doesn't loop in case
* token refresh causes the 401 response
*
* Also eject the interceptor to prevent it from working again if the REFRESH request returns 401 too
*/
axios.interceptors.response.eject(interceptor);
return axios({
url: API_REFRESH_URL,
method: "POST",
withCredentials: true,
})
.then((response) => {
auth.setToken(response.data.access_token);
error.response.config.headers["Authorization"] =
"Bearer " + response.data.access_token;
return axios(error.response.config);
})
.catch((error) => {
auth.removeToken();
return Promise.reject(error);
})
.finally(createAxiosResponseInterceptor);
}
);
};
createAxiosResponseInterceptor();
error.response.config contains all the data about the old request so we can repeat it. Keep in mind that after completing the Refresh request we again apply the interceptor in .finally(create...Interceptor)
For more details please see this question, and official Docs here
We are implementing a token-based authentication and when a user signs in we generate access and refresh tokens then save that with the timestamp on device so we can later check if the access token is expired or not.
We are currently using axios interceptor before every request and checking if the token is still valid or not with the timestamp we saved earlier when we generated the access and refresh tokens, but when the access token is expired and we are making a request to refresh the token the app goes on an infinite loop and none of the requests go through (both the original and refresh token api requests). you can see my code below:
const instance = axios.create({
baseURL: 'http://localhost:8080'
});
const refreshToken = () => {
return new Promise((resolve, reject) => {
instance
.post('/token/renew')
.then(response => {
resolve('refresh successful');
})
.catch(error => {
reject(Error(`refresh fail: ${error}`));
});
});
};
instance.interceptors.request.use(
async config => {
const timestamp = 1602155221309;
const diffMinutes = Math.floor(Math.abs(Date.now() - timestamp) / 60000);
// if diffMinutes is greater than 30 minutes
if (diffMinutes > 30) {
const tokenResponse = await refreshToken();
return config;
}
return config;
},
error => {
return Promise.reject(error);
}
);
The infinite loop is caused by your interceptor triggering another Axios request on which said interceptor will also run and trigger another Axios request, forever.
A simple solution would be to make the refresh token network request using the default axios instance which doesn't include any interceptors:
const refreshToken = () => {
// You can skip returning a new `Promise` since `axios.post` already returns one.
return axios.post("YOUR_BASE_URL/token/renew");
};
Obviously that also means you'll have to write a bit of logic to send the current refresh token along if that's included in your instance interceptors.
I'm working on login authentication with a react native app. I have login working as someone logs in for the first time through the login form. At this initial login step, I'm sending my JWT web token to my secure routes in my express server as a string and this works fine. NOTE: I have a middleware setup that my token goes through before my routes will fire completely.
The problem is when the app refreshes it needs to go to my "local storage". My express middleware doesn't like the token I'm pulling from my "local storage (I retrieve it through AsyncStorate.getItem('UserToken'). When I look at the item being stored in the header, it seems like it's the exact item I was sending it at the initial login, but I think it's my middleware on the server that doesn't like the value when it's coming from the " local storage" header. Below is my middlware.js code.
I've tried looking at the JWT value being sent in both scenarios, and it seems like it's the exact item being sent to the server in both situations.
module.exports = function(req, res, next) {
//Get Token from header
const token = req.header('x-auth-token');
// Check if no token
if(!token) {
return res.status(401).json({ msg: 'No token, authorization denied'})
}
//Verify token
try {
var decoded = jwt.verify(token, global.gConfig.jwtSecret);
req.user = decoded.user;
next();
}catch(err){
res.status(401).json({ msg: 'Token is not valid'});
}
}
This is the function I'm using to retrieve the token from AsyncStorage
export const getAsyncStorage = async () => {
try {
// pulling from header to get x-auth-token here
Value = await AsyncStorage.getItem('Usertoken');
//setting x-auth-token here
axios.defaults.headers.common['x-auth-token'] = Value;
} catch (error) {
// Error retrieving data
}
};
I'm working on a nuxt.js application with amplify providing the backend, more specifically: Cognito for user/identity management and AppSync for the API.
AppSync uses IAM as authorization on the client side and API KEY on the server side.
When the initial request is made to the server, I can parse the tokens included in the cookie and check if they are valid.
If the token is valid, everything is great: I can grab the user from my database and update my Vuex store before the initial page is sent back to the client.
If the token is expired however, I have a problem because the client/browser then receives an initial page where no user is logged in. On the client side, when the web app is initialized, the amplify library "kicks-in", sees the expired tokens and proceeds to automatically refresh them in the background. When I hit refresh, the new tokens are sent to the server and this time, are valid. Not a great experience.
Therefore, my question is how do I refresh the token on the server-side the first time when the parsed token is expired ?
nuxtServerInit(store, context) {
const cookie = context.req.headers.cookie
if (!cookie) { return console.log("no cookie received") }
const parsedCookie = cookieParser.parse(cookie)
var idToken = ""
var refreshToken = ""
var userData: Object | null = null
Object.keys(parsedCookie).forEach((key, index) => {
if (key.includes("idToken")) idToken = parsedCookie[key]
if (key.includes("refreshToken")) refreshToken = parsedCookie[key]
if (key.includes("userData")) userData = JSON.parse(parsedCookie[key])
}
return context.$axios
// validate token against cognito
.get("https://cognito-idp.us-east-1.amazonaws.com/us-east-xxx/.well-known/jwks.json")
.then(res => jwt.verify(idToken, jwkToPem(res.data.keys[0]), { algorithms: ["RS256"] }))
.then(decodedToken => {
// token is valid, proceed to grab user data
var user = { attributes: {} }
;(userData!["UserAttributes"] as Array<any>).forEach(element => {
user.attributes[element.Name] = element.Value
})
const sub = user.attributes["sub"]
return sub
})
.then(sub => {
// fetch user data from API
})
.then(res => {
// update store with user data
})
.catch(e => {
// TODO: CASE WHERE TOKEN IS INVALID OR EXPIRED
// THIS IS WHERE I WOULD NEED TO REFRESH THE TOKEN
console.log("[nuxtServerInit] error", e.message)
})
}