Express auth middleware not liking the JWT Token it receives from client side React Native AsyncStorage - react-native

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

Related

OctoKit with Auth0 (Github Login) in NextJS

I am building a Next JS app that has Github Login through Auth0 and uses the Octokit to fetch user info / repos.
In order to get the IDP I had to setup a management api in auth0. https://community.auth0.com/t/can-i-get-the-github-access-token/47237 which I have setup in my NodeJs server to hide the management api token as : GET /getaccesstoken endpoint
On the client side : /chooserepo page, I have the following code :
const chooserepo = (props) => {
const octokit = new Octokit({
auth: props.accessToken,
});
async function run() {
const res = await octokit.request("GET /user");
console.log("authenticated as ", res.data);
}
run();
And
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps({ req, params }) {
let { user } = getSession(req);
console.log("user from get session ", user);
let url = "http://localhost:4000/getaccesstoken/" + user.sub;
let data = await fetch(url);
let resData = await data.text();
return {
props: { accessToken: resData }, // will be passed to the page component as props
};
},
});
However, I keep getting Bad credentials error. If I directly put the access token in the Octokit it seems to work well, but doesn't work when it's fetching the access token from the server.
It seems like Octokit instance is created before server side props are sent. How do I fix it ?
I figured out the error by comparing the difference between the request headers when hardcoding and fetching access token from server. Turns out quotes and backslashes need to be replaced (and aren't visible when just console logging)

ExpressJS authorization with JWT - Is this correct?

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.

Vue.js (Quasar) SPA restarts Auth Code Flow on each page reload

I have a SPA built with the Quasar Framework (based on Vue.js). The SPA is registered in Auth0 and uses the auth0-spa-js library to handle the login via Auth Code Flow. While the login works and I get a token, when I reload the page the Auth Code Flow is started again and the user is redirected to the /authorize endpoint to get a new code, which is then again exchanged for a new token.
To me this does not seem like the correct behaviour. I would have expected that the Auth0 library caches/stores the token in the browser and on page reload checks if there is a valid token already, instead of restarting the Auth Code Flow every time.
Or is that actually the way it should be considering this is a SPA and token storage in the browser is not good.
The code from the boot file:
import createAuth0Client from '#auth0/auth0-spa-js';
import axios from 'axios'
export default async ({ app, router, Vue }) => {
let auth0 = await createAuth0Client({
domain: '{domain}.auth0.com',
client_id: '{client_id}',
audience: '{audience}'
});
const isAuthenticated = await auth0.isAuthenticated();
if (isAuthenticated) {
// show the gated content
await afterLogin(auth0, Vue)
return;
}
const query = window.location.search;
if (query.includes("code=") && query.includes("state=")) {
// Process the login state
await auth0.handleRedirectCallback()
await afterLogin(auth0, Vue)
// Use replaceState to redirect the user away and remove the querystring parameters
window.history.replaceState({}, document.title, "/");
return
}
await auth0.loginWithRedirect({
redirect_uri: window.location.origin
});
}
async function afterLogin(auth0, Vue) {
let user = await auth0.getUser()
Vue.prototype.$user = user
Vue.prototype.$auth = auth0
// let claims = await auth0.getIdTokenClaims()
// console.log(claims)
// setAuthHeader(claims.__raw)
let token = await auth0.getTokenSilently()
setAuthHeader(token)
}
function setAuthHeader(token) {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token
}
What am I missing?
When refreshing you should check if user exists..
const user = await auth0.getUser();
You could create an autoCheckUser function at src/boot folder, that can check if user exist every time app created...
The feedback from Auth0 is that this is the expected behaviour.
"If the token is stored in memory, then it will be erased when the page is refreshed. A new token is silently requested via a cookie session."
Here is the link to the original answer

How should I handle nuxt cookies expiration and workflow?

I have made an authentication workflow for a project using a Nuxt frontend(universal mode) and an Apollo endpoint as backend.
It is a mix of several examples I found and, with SSR, and since I do not fully anticipate what could go wrong, I wanted to make sure there is no red flag about how I proceed.
On the backend, I use an express middleware to sign JWT auth tokens, check them, and return them in the Authorization header. Here is the middleware:
import jwt from 'jsonwebtoken';
import { AuthenticationError } from 'apollo-server-express';
export const getToken = payload => {
return jwt.sign(payload, process.env.SEED, { expiresIn: process.env.EXPTOKEN });
}
export const checkToken = (req, res, next) => {
const rawToken = req.headers["authorization"]
if (rawToken) {
try {
const token = rawToken.substring(7)
// Verify that the token is validated
const { user, role } = jwt.verify(token, process.env.SEED);
const newToken = getToken({ user, role });
req.user = user;
req.role = role;
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.set("Access-Control-Expose-Headers", "authorization");
res.set("authorization", newToken);
} catch (error) {
if (error.name === "TokenExpiredError") {
res.set("Access-Control-Expose-Headers", "authorization");
res.set("authorization", false);
}
console.log("invalid token", error);
return new AuthenticationError
// Invalid Token
}
}
next();
}
Since there is a Nuxt-Apollo module, I used its methods onLogin, onLogout and getToken to store the JWT string in a cookie. As I understand it, SSR apps don't have the serverside local storage matching the client so they have to use cookies. Correct?
Here is my nuxt middleware where I check the users credentials before allowing them to visit an auth route. Is is quite messy but it gets the job done, except for the commented part.
export default function ({ app, route, error, redirect }) {
const hasToken = !!app.$apolloHelpers.getToken()
// this part does not work
/* const tokenExpireDateTime = app.$cookies.nodeCookie.parse('cookie-name', 'expires')
if (hasToken && tokenExpireDateTime < 0) {
error({ statusCode: 403, message: 'Permission denied', description: 'Sorry, you are forbidden from accessing this page.' })
app.$apolloHelpers.onLogout()
return redirect('/login')
}
*/
if (!hasToken) {
if (route.name === 'welcome-key') {
// enrollment link route
} else {
if (route.name === 'home') {
error({ errorCode: 403, message: 'You are not allowed to see this' })
return redirect('/showcase')
}
if (!['login', 'forgot_password', 'reset_password-key'].includes(route.name)) {
error({ errorCode: 403, message: 'You are not allowed to see this' })
return redirect('/login')
}
}
} else {
if (['login', 'forgot_password', 'reset_password-key'].includes(route.name)) {
redirect('/')
}
}
}
I have one issue and several points of confusion.
My issue is that I can't get the cookie expires value to redirect in the above nuxt middlware if it is necessary to login again because the JWT is expired. I used the piece of code mentioned in this issue as reference.
With this issue, my confusion is about:
The expires date on the cookie is set by the Nuxt-Apollo module, I expect, and I have to make it match the duration set on server (i.e. process.env.EXPTOKEN in the server middleware mentioned above), correct?
That expiration time alone can easily be tempered with and the real security is the lack of a valid token in headers when a request is handled by my server middleware. Its use is for client-side detection and redirect of an expired token/cookie, and serverside prefetch of user related data during SSR. Right?
The new token emitted by my express backend middleware is not taken into account in my frontend: it is not updating the cookie stored JWT and expires value client side. I mean that I can see the autorization header JWT string being updated in the response, but the cookie isn't. The following request still use the first JWT string. Am I supposed to update it at each roundtrip? What am I missing with the approach of the express middleware (that, as you can guess, I didn't write)
Please help me understand better this workflow and how I could improve it. It tried to avoid as much as possible to make this question too broad, but if I can narrow it down more, feel free to suggest an edit.

Server Side: how to refresh expired tokens?

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