Checking the validity of JWT Tokens - beforeEnter - vue.js

I've got a function that runs 'beforeEnter' in the Vue router to verify that the user has been authenticated, otherwise triggers a message.
It checks to see if a (jwt) token is saved in the localStorage - this works if the user signs out manually, as it removes the token from the localStorage. However when the token expires it still remains in the localStorage so the function thinks ((localStorage.token)) the user is logged in.
The server still blocks any requests made as the token is invalid - so is safe.
How do I check the token's validity on the server side, in the 'beforeEnter' middleware, before the page loads?
Do I need to make an endpoint that checks a tokens validity and returns the result? (I'm using fetch(), however I've seen people use axios interceptors...)
Worth nothing that I'm not using VUEX, and there seems to be more details on that?
function protectedPage(to, from, next) {
if (localStorage.token) {
next();
} else {
Vue.toasted.show("The session has ended. Please login.", {
theme: "toasted-primary",
position: "top-center",
duration: null,
action: {
text: "Login",
onClick: (e, toastObject) => {
next("/");
toastObject.goAway(0);
}
}
});
next("/");
}
}

Since exp is part of the payload, and JWT is just a base64 string, you can just decode it and check the exp time on your Vue app.
This is a function to decode JWT token and get the payload (taken from here)
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(Buffer.from(base64, "base64").toString("ascii").split("").map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
};
and check it on your beforeRouteEnter function:
beforeRouteEnter (to, from, next) {
if (localStorage.token) {
const jwtPayload = parseJwt(localStorage.token);
if (jwtPayload.exp < Date.now()/1000) {
// token expired
deleteTokenFromLocalStorage();
next("/");
}
next();
} else {
next("/");
}
},
You don't really need to check it on your backend server, since there's no security concern by decoding the JWT token payload and checking it in on the client side. Plus it saves you one HTTP request every time a user access a route.

You need a backend middleware which bound to each API call and validates user session if still exists and has same tokens.
If the session has been expired or token has been changed and doesn't match with the current user session, you can redirect user to the login page from backend and force him to create a fresh session.
I think you don't need to fetch the authentication for each route entrance, just block the backend api calls and return a message or redirect to the login page. User can still browse the pages with the expired session info but won't be able to perform any fetch or form actions.

Related

Nuxt SSR - i can't check if a user is authenticated

I'm trying to work on a Nuxt SSR frontend that uses a Django backend with Session Authentication.
I would like to have some SSR pages as well as client rendered pages in my frontend, so i'm using Universal mode.
The problem is that i did not find a working approach to check if a user is authenticated before loading a page, so i can't restrict pages to anonymous users. In order to check if a user is authenticated, Django will check if the request's headers contain a cookie, and according to that return if the user is authenticated or not.
Here is what i tried:
1) Middleware
export default async function ({context, redirect}) {
axios.defaults.withCredentials = true;
return axios({
method: 'get',
url: 'http://127.0.0.1:8000/checkAuth',
withCredentials: true,
}).then(function (response) {
//Redirect if user is authenticated
}).catch(function (error) {
console.log(error)
});
}
Here i'm sending a request to my backend to check if the user is authenticated. The problem is that the middleware is executed from server side, which means there will never be any cookie in the request, even if the user is authenticated. This means that every time i refresh the page, according to the middleware the user is always anonymous, even when the user is authenticated.
2) Plugin
export default function (context, inject) {
if (process.client){
console.log('client')
return axios({
method: 'get',
url: 'http://127.0.0.1:8000/checkAuth',
withCredentials: true,
}).then(function (response) {
//IF AUTHENTICATED, REDIRECT
context.redirect('/')
}).catch(function (error) {
console.log(error)
});
} else {
console.log('server')
}
}
Here i'm trying the same but with a plugin, and i'm "forcing" the plugin to check if the user is authenticated on the backend only when the plugin executes from client side. This works, cookies are sent in the headers and Django receives the cookie, but the problem with this solution is that Nuxt doesn't allow redirecting to other pages from a plugin (https://github.com/nuxt/nuxt.js/issues/4491).
3) Using beforeMount() in Vue
I tried to do that using beforeMount() from my Vue pages, but the problem is that since it will execute AFTER idration, the page will be loaded and after 1/2 seconds the redirect happens, which is kind of ugly.
Is it possible that there isn't a way to do this? Any kind of advice is appreciated
EDIT: the problem is not that i don't know how to code this, the problem is that when Nuxt sends a request to my backend from the server side middleware, the request will not contain any cookie, and because of this my Django backend cannot check the session cookie, which means that the backend cannot check whether or not the user is authenticated. The same code works when the middleware is executed from client side (if i navigate directly to the page instead of refreshing), because the request will contain the cookies.
I'm trying to understand if this is normal or not, but this could be an issue with Nuxt.
I know this a year old question and it was probably about nuxt 2, now nuxt 3 is out and running and I found my self with the same problem and here is how I solved it, just in case someone stumble here just like I did.
With Nuxt 3 server side you can use useFetch with the options headers: useRequestHeaders(['cookie'])
const { data, error, pending, refresh } = await useFetch(api.auth,
{
credentials: "include",
headers: useRequestHeaders(['cookie'])
}
);
There are a few issues you need to be aware of:
_ The cache, if you perform the same request with the same parameters it will return the same cached response (it won't even call the end point API). Try to use the key option with different values or the returned refresh method and check the doc "Data fetching" for more info.
_ The cookies, any cookie generate server side won't be shared with the client side, this means if the API generate a new token or session cookie on server side the browser won't receive those cookies and may generate new ones, this may get you in some 400 - bad request if you use session with CSRF, check this issue for more info.
I do have a working middleware with this
export default ({ redirect, store }) => {
if (store?.$auth?.$state?.loggedIn) {
redirect('https://secure.url')
} else {
redirect('https://login.please')
}
})

ExpressJS apply JWT for file url

So I'm trying to make authorization for routes with JWT, it all worked if used on routes.
app.get('/user/list', jwtMiddleware, action);
And the jwtMiddleware content is (more or less):
var token = req.headers.authorization;
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, process.env.SECRET_TOKEN, function(err, decoded) {
if (err) {
return res.status(401).send({
success: false,
message: 'Sign in to continue.'
});
} else {
// if everything is good, save to request for use in other routes
next();
}
});
} else {
// if there is no token
// return an error
return res.status(401).send({
success: false,
message: 'Sign in to continue.'
});
}
it works, but I have these image files in uploads/ folder which accessible by /upload/image-1.jpg and I want to prevent direct access to /upload/image-1.jpg by using wildcard routes app.get('/upload*', jwtMiddleware, action);
then I try accessing random route with upload prefix like /upload/test, the jwt middleware works. But if I explicitly type /upload/image-1.jpg the browser just show the image, it's like the middleware or wildcard route (/upload*) is not accessed (the console.log inside middleware didn't even fired).
Previously I use restify and restify-jwt-middleware, it could handle this case flawlessly but in express I can't find out why it doesn't work. Maybe because restify-jwt-middleware automatically registers all routes into jwt validation whereas express need to declare each route with jwt middleware manually.
is there anything I miss in this case? thank you.
add/modify to another route like app.get('/upload/:image', jwtMiddleware, action)
this will check all the route you mentioned /upload/*
EDIT :
put the static files(eg.uploaded files somewhere like images/upload) and route them using the serveStaticFiles plugin restify and put jwt middleware to verify the user login status.
server.get(
'/uploads/*',
jwtMiddleware,
restify.plugins.serveStaticFiles('./images/upload')
);
In case anyone still confused, here's my answer in express which is similar approach to yathomasi's
// the fake route
app.get('uploads/:name', jwtMiddleware, (req, res, next) => {
if (fs.existsSync('./realpath/' + req.params.name)) {
res.sendFile('./realpath/' + req.params.name);
} else {
res.status(404).body({status : 'ERROR', message : 'File not found'});
}
});
this way, the uploads/somefile.jpg is treated as route url not file url and will be processed by jwtMiddleware

How to send Oauth token from callback at the server to the client?

I am using expressJs and passport for authentication. I am using Google Oauth2.0 for login with standard Passport GoogleStrategy. At client I am using axios for sending a login request to the server. My login routes are :
router.get(
"/google",
passport.authenticate("google", { scope: ["profile", "email"] }));
router.get(
"/google/callback",
passport.authenticate("google", { failureRedirect: "/"}),
function(req, res) {
const token = jwt.sign({id: req.user.id}, configAuth.secretKey);
console.log("generated token: ", token);
res.json({success: true, token: 'bearer ' + token});
}
);
I am using the user information from the callback to generate the JWT which I want to sent the client.
At the client I am using axios to send request and get the JWT and store it in localstore.
axios.get('http://localhost:3001/google')
.then((result) => {
console.log("result", result);
localStorage.setItem('jwtToken', result.data.token);
})
.catch((error) => {
// if(error.response.status === 401) {
// this.setState({ message: 'Login failed. Username or password not match' });
// }
console.log("Login error", error);
});
But Axios doesn't wait for the redirect to happen and returns a HTML document with Loading... message. If you try to access the API in the browser, it returns the desired JSON object. Is there a way to wait for redirects. Should I use another library to send login request?
I tried sending the token as url parameter with
res.redirect()
but client and server are at different ports so it doesn't work.
Is there another way to do it?
Google's OAuth2 pathway redirects your browser, resulting in page reloads, a couple of times before it completes. As a result, your client-side code,
axios.get('http://localhost:3001/google')
.then((result) => {
console.log("result", result);
localStorage.setItem('jwtToken', result.data.token);
})...
will never reach the .then() block. You probably see this in the browser; you click a button or something to navigate to 'http://localhost:3001/google', and your localhost:3001 server re-directs your browser to a Google login page. Now that your browser is at the login page, it has no memory of the axios.get statement above--that webpage code is gone.
You need to handle the JWT in client-side code that your server sends in response to
axios.get('http://localhost:3001/google/callback').
This is your browser's final stop in the OAuth2 path--once you get there, you won't be re-directed again. You can put your axios.get function above inside that client-side code.
If you haven't solved the problem, there is a workaround use 'googleTokenStategy' instead of googleOAuth on passportjs. That way you can use react's GoogleLogin plugin to receive the access token from the front end and send it by axios.post to the backend link then set up the jwt. Reference here

Using node-spotify-web-api to grant user access and fetch data

So I'm new to using OAuth and I honestly got quite lost trying to make this work. I looked up the documentation for Spotify's Authorization code and also found a wrapper for node which I used.
I want to be able to log in a user through spotify and from there do API calls to the Spotify API.
Looking through an example, I ended up with this code for the /callback route which is hit after the user is granted access and Spotify Accounts services redirects you there:
app.get('/callback', (req, res) => {
const { code, state } = req.query;
const storedState = req.cookies ? req.cookies[STATE_KEY] : null;
if (state === null || state !== storedState) {
res.redirect('/#/error/state mismatch');
} else {
res.clearCookie(STATE_KEY);
spotifyApi.authorizationCodeGrant(code).then(data => {
const { expires_in, access_token, refresh_token } = data.body;
// Set the access token on the API object to use it in later calls
spotifyApi.setAccessToken(access_token);
spotifyApi.setRefreshToken(refresh_token);
// use the access token to access the Spotify Web API
spotifyApi.getMe().then(({ body }) => {
console.log(body);
});
res.redirect(`/#/user/${access_token}/${refresh_token}`);
}).catch(err => {
res.redirect('/#/error/invalid token');
});
}
});
So above, at the end of the request the token is passed to the browser to make requests from there: res.redirect('/#/user/${access_token}/${refresh_token}');
What if insted of redirecting there, I want to redirect a user to a form where he can search for artists. Do I need so somehow pass the token around the params at all time? How would I redirect a user there? I tried simply rendering a new page and passing params there but it didn't work.
you could store the tokens in a variety of places, including the query parameters or cookies - but I'd recommend using localstorage. When your frontend loads the /#/user/${access_token}/${refresh_token} route, you could grab the values and store them in localstorage (e.g. localstorage.set('accessToken', accessToken)) and retrieve them later when you need to make calls to the API.

Auth between a website and self-owned API

This has probably been asked before, so a preemptive apology from me.
I built a site and I built an API. The API will also be used by a mobile app in the future. I own both so I'm pretty sure two and three legged OAuth aren't for me. The API has parts that are accessible to the world and other parts that are protected and require a user account. To keep things simple I've just gone with a https + Basic Auth solution (for now). It's all fine and good when testing requests manually to the API (I didn't write tests because I'm a bad person), things work as expected and Basic Auth is fine.
I'm trying to solve the flow of a user logging in with plaintext user and password, send that to the API to authenticate, the API just needs to say yes or no, yet all requests from the site (on behalf of a user) to the API should be signed in some way with their credentials for when they want to POST/GET/PUT/DEL one of the protected resources.
Out of all of the auth resources I've read I'm still confused as to what scheme to use. Storing the plaintext password on the site side so that I can base 64 encode it and send it over the wire seems bad, but it looks like that's what I'd have to do. I've read of digest auth but I'm not sure I get it. Any and all advice is welcome.
This is how I would handle this case;
POST the username and password as a plain text to your api using HTTPS of course.
Then validate it to your database, the best algorithm used nowadays to salt password is bcrypt.
If the user is not valid return 401, or whatever.
If the user is valid, return a JWT token with his profile signed with a Public Key algorithm.
Your fron-end knows the public key so it can decode the JWT but it can't generate a new one.
For every request that needs authentication, you attach an Authentication header, with Bearer [JWT]
A middleware in the backend reads this header and validate it with the private key.
Don't be affraid of JWT there are plenty of implementations for every language and framework and is easier than you might think. A lot of applications are already using JWT already even Google.
Auth0 is an authentication broker that can validate against any identity provider or custom database, and returns JWTs. It provides a clientID that can be used to decode the profile in the front end and a secret to validate the tokens in the backend as well as client side library to do this.
Disclaimer: I work for auth0.
Update: Since you mention node.js and express in comments I will give an example in this technology.
var http = require('http');
var express = require('express');
var jwt = require('jsonwebtoken'); //https://npmjs.org/package/node-jsonwebtoken
var expressJwt = require('express-jwt'); //https://npmjs.org/package/express-jwt
var secret = "this is the secret secret secret 12356";
var app = express();
app.configure(function () {
this.use(express.urlencoded());
this.use(express.json());
this.use('/api', expressJwt({secret: secret}));
});
//authentication endpoint
app.post('/authenticate', function (req, res) {
//validate req.body.username and req.body.password
//if is invalid, return 401
var profile = {
first_name: 'John',
last_name: 'Foo',
email: 'foo#bar.com',
id: 123
};
var token = jwt.sign(profile, secret, {
expiresInMinutes: 60*5
});
res.json({
token: token
});
});
//protected api
app.get('/api/something', function (req, res) {
console.log('user ' + req.user.email + ' is calling /something');
res.json({
name: 'foo'
});
});
//sample page
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
http.createServer(app).listen(8080, function () {
console.log('listening on http://localhost:8080');
});
This is an express application with one endpoint that validates username and password. If the credentials are valid it returns a JWT token with the full profile, with expiration 5 hours.
Then we have an example endpoint in /api/something but since I've a express-jwt middleware for everything on /api it requires a Authorization: Bearer header with a valid token. The middleware not only validates the token but also parses the profile and put it on req.user.
How to use this client-side? This is an example with jquery:
//this is used to parse the profile
function url_base64_decode(str) {
var output = str.replace("-", "+").replace("_", "/");
switch (output.length % 4) {
case 0:
break;
case 2:
output += "==";
break;
case 3:
output += "=";
break;
default:
throw "Illegal base64url string!";
}
return window.atob(output); //polifyll https://github.com/davidchambers/Base64.js
}
var token;
//authenticate at some point in your page
$(function () {
$.ajax({
url: '/authenticate',
type: 'POST',
data: {
username: 'john',
password: 'foo'
}
}).done(function (authResult) {
token = authResult.token;
var encoded = token.split('.')[1];
var profile = JSON.parse(url_base64_decode(encoded));
alert('Hello ' + profile.first_name + ' ' + profile.last_name);
});
});
//send the authorization header with token on every call to the api
$.ajaxSetup({
beforeSend: function(xhr) {
if (!token) return;
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
}
});
//api call
setTimeout(function () {
$.ajax({
url: '/api/something',
}).done(function (res) {
console.log(rest);
});
}, 5000);
First, I've an authenticate call with the username and password, I can decode the profile in the JWT to get the user profile and I also save the token to use in every request later on.
The ajaxSetup/beforeSend trick adds the header for every call. So, then I can make a request to /api/something.
As you can imagine this approach doesn't use cookies and sessions so it works out of the box in CORS scenarios.
I'm a big fan of passport.js and I've contributed a lot of adapters and fixes for some other adapter but for this particular case I wouldn't use it.
I've been thinking about a similar scenario lately; here's what I did:
SSL + Basic Auth
In the DB (on the API side), generate a random salt (per user), and save the salt and the hashed (password + salt). When a request arrives, throw on the salt and hash it, then compare to what you've saved
Send the password in plaintext - you are using SSL so I think this is okay (this is the part I am most uncertain of)
I don't have a great reason for recommending this but in case you have a reason to use it:
.4. Attach a timestamp to every request and have them expire after a couple of minutes.
The reason you should save salted-and-hashed passwords in your DB is in case someone steals your DB.
Basically I'm putting a lot of faith into SSL, and what I've read tells me that's okay.