Vue.js 2 Authentication JWT - node.js

[ Post has been edited: see below for answer ]
I am trying to make a Vue.js2 application using this boilerplate https://github.com/vuejs-templates/webpack
I am stuck on the Authentication process, using this library: https://github.com/websanova/vue-auth and attempting to use JWT as the authentication method. Never having rolled my own authentication before, I am slightly lost.
Packages I have installed which may be relevant: passport, passport-jwt, jsonwebtokens, passport-local, passport-local-mongoose
Looking at my logs I get a successful login response, but then it attempts to get /auth/user and responds with a 401 (unauthorized error). Perusing the auth library code, a GET req to /auth/user seems to be the expected behavior.
Here is the login code (client side):
methods: {
login() {
this.$auth.login({
body: this.data.body
success(res) {
console.log('Success: '+this.context);
this.localStorage.setItem('auth-token', res.body.token);
},
error(res) {
console.log("Error: " + this.context);
this.error = res.data;
},
rememberMe: true,
fetchUser: true
});
}
}
And here is the appropriate code server-side:
Removed Link | See Edits Section *
What I am sure of is this:
the server does in fact create a JWT which is valid (checked on jwt.io) during the login request. It does not appear to be set anywhere afterwards. It just sits there and then dies. There are mentions of an Authorization Bearer header in the response, which I am certain is not being set. Nor do I understand where or how to do this. There is no token set in localStorage after the login request. I'm not sure if this should exist, but think it likely that it should. In my console, searching local storage yields some strings and large integers, but no mention of a token in it.
Edits (8+ months later)
Gist to Solution here (slashes replaced by dashes in filenames):
https://gist.github.com/wcarron27/b0db7a16df9ceff924d4a75050093c55
The reason my login method originally did not work was that the localStorage token was not set correctly, and thus failed to pass the getData method on the client-side redirect. vue-auth does this by default. By editing the url it hits in the vue-auth config, I was able to direct it to the proper route(BUT only after I properly set the localstorage token. Use Vue.http.options.rootUrl (or something, it's in the main.js file in the gist) to set the Authorization header.
In the code, You must register vue-auth on the client side main.js, and call to it in the Login.vue "login" method. The client side config directs the http calls to the specified route in main.js. In the callback, the user and tokens are set in localStorage and the Vuex store.
The Http reqs go the the API side and hit the route in accounts.js. That route uses a passport strategy defined in ./util/passport.js, as well as a setJWT function defined in ./util/jwtLib.js.
After this, the client is redirected to a route of my choice, and data is populated by my store and ajax calls. Keep in mind, that while this should solve logins, i have not verified code correctness, as basically I would have needed to post the whole of two separate codebases.
Additionally, this does not account for refresh errors. State is dropped on refresh, so do not rely on vuex for persistence. A combination of localStorage and vuex does the trick, though.

I didn't verify this but, does remove the 'this' from your code on line 7 do the magic?
methods: {
login() {
this.$auth.login({
body: this.data.body
success(res) {
console.log('Success: '+this.context);
// original code here --> this.localStorage.setItem('auth-token', res.body.token);
localStorage.setItem('auth-token', res.body.token);
},
error(res) {
console.log("Error: " + this.context);
this.error = res.data;
},
rememberMe: true,
fetchUser: true
});
}
}

Related

How to make HTML auth form and JSON Web Tokens communicate together in Ionic/Angular

I'm working on an Ionic application.
On the one hand I have an auth basic form in which people fill in their username and password. On the other hand I'd like to implement authentification with JSON Web Tokens and Node JS.
The workflow would be this one : as soon as a user fills in his credentials, they will be sent with a POST request. If these credentials are correct, the user can access to the application and gets an access token as a response.
The thing is that I'm a little bit lost with all that concepts. I built a form and sent informations with a POST request. I managed to create some APIs with Node JS and that's ok. I see how to build a authentified webservice too (e.g : https://github.com/jkasun/stack-abuse-express-jwt/blob/master/auth.js).
But I concretely don't understand the links between the html form and the authorisation check part..
To be clearer, how is it possible to make the html part and the Node JS scripts communicate together ?
Before posting that question I made many researches and found many stuff on building an authentified API. But there was very few advice on how to make it communicate with the client part (I mean the form), which is what I have to do.
If anyone has any ressources (document, Github examples..) on that, I'll greatly appreciate. But I would be very happy too if someone try to make me understand these concepts. I guess I have to improve my knowledge on all that so that I could test some POCs.
Many thanks in advance !
JWT General flow:
1- Authenticate using a strategy (You done it)
2- Deliver an accessToken along with response (You done it)
3- The client MUST store this accessToken (LocalStorage is the best place, not cookies: They are vulnerable to csrf attacks)
4- On every request you are going to make to a protected area (where user is supposed to be authenticated and authorized), make sure to send you accessToken along with it, you can put it on Authorization header, a custom header, directly in body of the request... Basicaly just make sure to send it properly.
5- On the server receiving client requests, you NEED to verify that token (You verify it by checking the signature of the accessToken).
6- If he is authorized, great, if not, send back an HTTP Unauthorized Error.
Here is my implementation using an accessToken on a header + passportjs-jwt:
Client code
To store token:
localStorage.setItem('accessToken', myAccessToken);
To send it:
const myAccessToken = localStorage.getItem('accessToken');
{
headers: {'Authorization', `Bearer ${myAccessToken}`}
}
Server code
1- Configure passport
passport.use('jwt', new JwtStrategy({
jwtFromRequest: jwtPassport.ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: myAccessTokenSecret,
passReqToCallback: true
}, (req, payload, done: (err?, user?) => void): void {
User
.findOne({where: {id: req.params.id}})
.then((user: User) => {
if (!user) {
return done(new Error(`No user found with id: ${req.params.id}`), null);
}
return done(null, user);
})
.catch((e: Error) => done(e, null));
}));
Pay attention to callback: If your callback is called, it means that passport has successfuly verified the token (It is valid). In my example, i get the user details in database and this is the user that will be returned and put in req.user object passed to my controller below:
2- Finally, the controller route (protected area):
.get('/users/:id', passport.authenticate('jwt'), (req, res, next) => {
// do stuff in protected area.
}
And that's it. If you want more security, check refreshTokens implementation.
I used passport because i found it relevant in my case, but you can write your own handler, by using jsonwebtoken and just calling its "verify" function.
You can find documentation of passport jwt strategy here => http://www.passportjs.org/packages/passport-jwt/

Preventing man-in-the-middle attacks with user authentication (Node/Vue/Passport)

I currently have a webapp I'm writing in Node/Vuejs with Passport handling authentication, and I've run into a problem. I was thinking about how I have authentication currently set up and I realized I had a glaring security hole.
In short, I have my Vuex store hitting a local API endpoint /api/me. That endpoint does a simple return of req.user. For the sake of brevity, a typical response looks like this:
{
username: 'Bob',
roles: [] // normal user has no roles,
email: 'someguy#bob.com'
}
My admin route /admin has a beforeEnter check, as shown below, that incorporates this check using the Vuex store, so I can have a cached version of user data accessible on the frontend.
{
path: '/admin',
name: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
store.dispatch('getMe').then(() => {
if (store.getters.user.roles && store.getters.user.roles.includes('administrator')) {
next();
return;
}
next({ path: '/' });
});
}
}
Here's the thing though - I realized that someone could easily game the system. In fact, I tried it myself with a test, non-Adminstrator account, and I was able to get in by returning the following from a local server set up for this purpose in Postman:
{
username: 'Super Admin Joe',
roles: ['administrator'] // normal user has no roles,
email: 'admin#bob.com'
}
And viola! The user now has full access to admin pages.
My question is, how could I prevent against this?
I need to check that the user is authenticated on every page, but a potential attacker could quite easily proxy any request (in this case it's /api/me) to make themselves any user they want. They can login normally with their own account, open the Network tab and copy the response payload, then change the user data as they wish. There needs to be some sort of encryption between the frontend and backend when checking a users' logged-in status, I believe.
I tried thinking about how I could prevent this from happening, but anything on my end (server-side, at least) seems useless as any request could easily be redirected to an attacker's local machine.
Any advice on how to "sign" my requests to make sure they aren't being proxied? Thanks in advance!
You shouldn’t have to be signing the response body of an api request. The typical way to do authentication is to establish a signed session cookie that acts either as an identifier to session information in an external database, or contains session information itself. This cookie should be in the header of your response and passport should give you a way to administer this cookie without you even realizing it.
This way the user can’t tamper with the information sent from the server in a way that’s easy to detect, and since it’s a cookie it will automatically be sent with each request by your browser (although if you’re using some AJAX library you may have to explicitly specify you’d like to send the cookie). What MadEard was referring to in the comment is where the cookie information is able to be accessed using passprt which is the ‘user’ property in the ‘req’ object.
After reading your github files:
server.get("/admin", function(req, res){
if(req.user && req.user.roles.includes("administrator")){
//user is an administrator, render the admin panel view
}else{
//is not an admin, redirect or send error message
}
});
In every Express route, after authentication with Passport, you have the req.user object.
It is established by checking the request cookie connect.sid, and checking which session this cookie belongs to on the server.
As such, you can trust that in any Express route, the object req.user contains the information relevant to that cookie and you can act upon it.
A little note: doing server-side validation should become a reflex for you over time.
The client is meant to display information. If, at any point, you are making the client take any decision that could be a security liability, take a step back and think it again.

How to implement authentication in Next.js

I am new to Next.js and I am struggling with the authentication system using jwt token. I want to know what is the best / standard way to store the jwt token and routing with the authentication system. I have been trying different approaches, from different tutorials/articles, but do not quite understand it. Here are what I have tried.
When the user login, it sends username/password to a separated API server (ex. new project that handles backend stuff), the server will respond with the access-token, then in Next.js project, I set the cookie with that received token. In Next.js project, protected routes will be wrapped with a withAuth hoc, which will check for the token in a cookie. The problem with this approach is that it is vulnerable to XSS because the cookie has no httpOnly flag.
This is similar to 1.) but using localStorage, the problem is access-token could not be sent to the server on the first request. (This one I'm not sure, but in my understanding, in every HTTP request, I must stick my access-token manually, so what about requests that I have no control over? ex. first request or using <a> tag).
I wrote authentication backend inside Next.js server (custom express server). When the user login, the server will validate it and then set an httpOnly cookie. Then the problem is, with client-side routing (go to URL using Next.js Router), it could not check for token. For example, if a page is wrapped with withAuth hoc, but it cannot access the token inside cookies with javascript.
And I've seen a lot of people, in getInitialProps of the protected route, they only check for existence token in cookie / localStorage, then what if the token is being revoked or blacklisted, how do they handle it because they did not send the token to the server? Or do I have to send the token to the server in every client-side page change?
Since we are on quarantine I have enough time to answer this question. It will be a long answer.
Next.js uses the App component to initialize the pages. _app page is responsible for rendering our pages. We authenticate users on _app.js because anything that we return from getInitialProps can be accessed by all of the other pages. We authenticate user here, authentication decision will be passed to pages, from pages to header, so each page can decide if the user is authenticated or not. (Note that it could be done with redux without prop drilling but it would make the answer more complex)
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {};
const user = process.browser
? await auth0.clientAuth()
: await auth0.serverAuth(ctx.req); // I explain down below
//this will be sent to all the components
const auth = { user, isAuthenticated: !!user };
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps, auth };
}
render() {
const { Component, pageProps, auth } = this.props;
return <Component {...pageProps} auth={auth} />;
}
}
If we are on the browser and need to check if a user is authenticated, we just retrieve the cookie from the browser, which is easy. But we always have to verify the token. It is the same process used by browser and server. I will explain down below. But if we are on the server. we have no access to the cookies in the browser. But we can read from the "req" object because cookies are attached to the req.header.cookie. this is how we access to cookies on the server.
async serverAuth(req) {
// console.log(req.headers.cookie) to check
if (req.headers.cookie) {
const token = getCookieFromReq(req, "jwt");
const verifiedToken = await this.verifyToken(token);
return verifiedToken;
}
return undefined;
}
here is getCookieFromReq(). remember we have to think functional.
const getCookieFromReq = (req, cookieKey) => {
const cookie = req.headers.cookie
.split(";")
.find((c) => c.trim().startsWith(`${cookieKey}=`));
if (!cookie) return undefined;
return cookie.split("=")[1];
};
Once we get the cookie, we have to decode it, extract the expiration time to see if it is valid or not. this part is easy. Another thing we have to check is if the signature of the jwt is valid. Symmetric or asymmetric algorithms are used to sign the jwt. You have to use private keys to validate the signature of symmetric algorithms. RS256 is the default asymmetric algorithms for APIs. Servers that use RS256, provide you with a link to get jwt to use the keys to validate the signature. You can either use [jwks-rsa][1] or you can do on your own. You have to create a certificate and then verify if the token is valid.
Assume that our user authenticated now. You said, "And I've seen a lot of people, in getInitialProps of the protected route, they only check for existence token in cookie / localStorage,". We use protected routes to give access only to the authorized users. In order to access those routes, users have to show their jwt tokens and express.js uses middlewares to check if the user's token is valid. Since you have seen a lot of examples, I will skip this part.
"then what if the token is being revoked or blacklisted, how do they handle it because they did not send the token to the server? Or do I have to send the token to a server in every client-side page changing?"
with verifying token process we are 100% sure if the token is valid or not. When a client asks the server to access some secret data, the client has to send the token to the server. Imagine when you mount the component, component asks the server to get some data from the protected routes. The server will extract the req object, take the jwt and use it to fetch data from the protected routes. Implementation of the fetching data for browser and server are different. And if the browser makes a request, it just needs the relative path but the server needs an absolute path. As you should know fetching data is done getInitialProps() of the component and this function executed on both client and server. here is how you should implement it. I just attached the getInitialProps() part.
MyComponent.getInitialProps = async (ctx) => {
const another = await getSecretData(ctx.req);
//reuslt of fetching data is passed to component as props
return { superValue: another };
};
const getCookieFromReq = (req, cookieKey) => {
const cookie = req.headers.cookie
.split(";")
.find((c) => c.trim().startsWith(`${cookieKey}=`));
if (!cookie) return undefined;
return cookie.split("=")[1];
};
const setAuthHeader = (req) => {
const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");
if (token) {
return {
headers: { authorization: `Bearer ${token}` },
};
}
return undefined;
};
export const getSecretData = async (req) => {
const url = req ? "http://localhost:3000/api/v1/secret" : "/api/v1/secret";
return await axios.get(url, setAuthHeader(req)).then((res) => res.data);
};
[1]: https://www.npmjs.com/package/jwks-rsa
With the introduction of Next.JS v8, there are examples placed in the NextJS example page. The basic idea to follow is:
JWT
Using cookies to store the token (you may choose to further encrypt it or not)
Sending the cookies as authorization headers
OAuth
Using a third-party authentication service such as OAuth2.0
Using Passport
This question might need an updated answer, now middlewares are there in Next.js 12 (october 2021): https://nextjs.org/docs/middleware
I am drafting a comprehensive answer to explain auth in Next.js more deeply, you can follow the progress there on GitHub
Here I'll try to propose a summary for Next.js, using middlewares.
Verifying the token after auth and redirecting accordingly
Most of the answer from #Yilmaz from april 2020 is still relevant. However, previously, we had to use getInitialProps in _app to process the request OR a custom server.
This is no longer the case.. Using a middleware let's you achieve a similar purpose, with cleaner code. Because middleware are specifically designed for such use cases.
Here, I suppose you get a JWT access token using an asymetrical algorithm like RS256, exactly like in this previous answer.
Here is a possible implementation:
import { NextFetchEvent, NextRequest, NextResponse } from "next/server";
const removeCookie = (res: NextResponse, cookieName: string) => {
res.headers.append("Set-Cookie", `${cookieName}=; Max-Age=-1; Path=/`);
return res;
};
export default async function middleware(
req: NextRequest,
ev: NextFetchEvent
) {
const { pathname } = req.nextUrl;
const isPublic = isPublicRoute(pathname);
if (isPublic) {
return NextResponse.next();
}
const accessToken = req.cookies[TOKEN_PATH];
if (!accessToken) {
return NextResponse.redirect(LOGIN_HREF);
}
const isValidToken = await checkAccessToken(accessToken);
if (!isValidToken) {
let res = NextResponse.redirect(LOGIN_HREF);
res = removeCookie(res, TOKEN_PATH);
return res;
}
return NextResponse.next();
}
How to verify the token
In my example, the checkAccessToken should verify the token (not decode, verify the signature).
This is where things are the most complicated imo.
When using the RSA256 algorithm
You also get a PUBLIC certificate (in addition to the SECRET key that must be... kept secret). Eventhough you do the check in the middleware, which is private and server-only code, that's good news because it means you could even use it in the browser, in theory.
So, you can either fetch the token validation endpoint provided by your auth server, or verify the token yourself.
Fetching is not the recommended option because it might break Vercel/Next edge capabilities and add latency, according to the documentation.
I must admit that I did not succeed to verify the token yet using Next.js :) I'll update this answer if I manage to have a code sample that works.
When using a symmetrical encryption
You have only a PRIVATE secret passphrase. It means that the decoding have to happen server-side (good news, you are writing a middleware).
Login/logout
This doesn't change with middlewares. You store your access token as an httpOnly cookie. When logging out, you unset this cookie.
Managing those Set-Cookies headers are the responsibility of your auth server.
This is a basic workflow but it should work. You can then add a refresh token in the mix with a similar approach.
About token revokation
If you verify the token in your middleware, there is no immediate revokation mechanism for the access token. Because there is no call to a database.
Therefore, in this scenario, you'd want to opt-in for short lived access token (eg 5 minutes) coupled with a refresh token. You can revoke the refresh token, so basically revoking works but takes a few minutes.
If a 3rd party server verifies the token: then it could check for blacklisted tokens.
Caveats
Also, some piece of advice: most articles, tutorials etc. online are focused on server-to-server communication. Or client-to-API. They completely suck when it comes to check authentication before accessing web pages.
For instance, setting the Authorization header is not possible in the browser. It works only when communicating with an API. Cookies are mandatory for web pages.
Even then, if this API is meant to be called from a browser, it should preferably accept a cookie.
When discussing with experts on the field, you need to always clarify the Next.js use case.
Open questions: about session-based authentication
Some frameworks seem to prefer relying on the database. They store a hashed token in the db, which acts as a session. If you want to check auth, you need a server that will check the user's token against the stored token (= checking that there is an active session with this token).
I am thinking of Meteor for instance.
I couldn't find the name of this mechanism and its actual relation to JWT however. Are they simply variations of the JWT approach?
Next.js official authentication doc is not showing middlewares at the time of writing, but instead use getServerSideProps. I really don't like this pattern.
It uses a kind of session system but I am not clear about the internals of it, I am not even sure of the name (is that session-based auth?).
Vercel edge handles examples shows how to secure an API route, but not a page (at the time of writing)

Using Hapijs and Bell with twitter provider. How to handle the authorize rejection from Twitter using the Bell module?

I'm using the Hapi framework (nodejs) with the Bell module, working with the Twitter provider.
It was pretty simple to get a working code with the example given in the github page. I access the /login route and I get redirected to Twitter, where I authorize the app and then I'm redirected back to /login?oauth_token=xxxxxxx&oauth_verifier=xxxxxxx where I can have access to the user profile in the request.auth.credentials.
The problem came when I tried to reject the app. Instead of clicking the "Sign In" button on Twitter, I clicked the "Cancel" button and then the "Return to site name" button. This last button redirects me to /login?denied=xxxxxx and then I'm redirected (again) to Twitter to approve the app.
I tried to handle this scenario using another example in the same page https://github.com/hapijs/bell#handling-errors but can't get it to work.
server.route({
method: ['GET', 'POST'],
path: '/login',
config: {
auth: {
strategy: 'twitter',
mode: 'try'
},
handler: function (request, reply) {
if (!request.auth.isAuthenticated) {
return reply('Authentication failed due to: ' + request.auth.error.message);
}
return reply.redirect('/home');
}
}
});
It seems that before checking the request.auth it interprets the /login route and redirects to Twitter. I still don't understand very well the Bell module but it might be that the Twitter strategy is expecting the oauth_token and oauth_verifier in the request.params, but the denied param is not interpreted by the strategy and that's why the redirect happens.
Has somebody managed to handle this scenario?
I found a workaround. It's not an optimal solution but at least allows me to handle the rejection from Twitter.
I had to modify a file inside the bell module. In bell/lib/oauth.js, before the verification of oauth_token
exports.v1 = function (settings) {
var client = new internals.Client(settings);
return function (request, reply) {
var cookie = settings.cookie;
var name = settings.name;
// Sign-in Initialization
// Verify if app (Twitter) was rejected
if (name=='twitter' && request.query.denied) {
return reply(Boom.internal('App was rejected'));
}
if (!request.query.oauth_token) {
// Obtain temporary OAuth credentials
var oauth_callback = request.server.location(request.path, request);
With that change I can catch and show the auth error in the handler, without the automatic redirect.
At least this is the way I managed to make it work. The cons of this modification is that if the bell module is updated then the modification is lost and the bug arise again, unless the updated module comes already with a fix for this. So, you have to keep an eye on that.
Here's the link off the Github issue I created on the Bell repository regarding this bug.

redirect to another app with session token (jwt) in AngularJS and NodeJS

I have a startup module in angularjs. This module is just to login and have public information (login, prices, newsletter...). I have many roles and for each role, i have an app (angular module). I made this architecture because i have complex module for each role and it was impossible to put all roles in one module.
So, for login, i use jsonwebtoken in node like this :
var token = jwt.sign(user, config.secureToken, { expiresInMinutes: 20*5});
res.json({ token: token, user: user });
It works perfectly. I can login into my app. After that, i have to propose a list of roles to redirect to the right module.
In angular, I have AuthHttp service that adds security headers (with token) to call rest service with $http.
How can i redirect to 'mydomain:port/anotherModule' with $location or $http ?
With this code in nodejs :
app.get('/secondModule', expressJwt({secret: config.secureToken}), function (req, res) {
res.render('restricted/secondModule/index.html');
});
NodeJs sends an html code in response and does'nt redirect...
And if i do this in my angular controller :
location.href = route;
i have this result on nodejs console :
Error: No Authorization header was found
I am not sure about the libraries you are using, but issue seems that you are loosing the token because you navigate to a altogether new page.
Based on your auth library you need to pass the token that you get after auth from one page to another.
The options here are to either use browser sessionStorage or querystring to pass the token along and at it back to the http header collection on the new page (module)
This is an old post but I recently took a long time to figure this out. I may be wrong but I believe nodeJS/expressJS can't read the token from the session storage. I believe you will need to pass the token via the request header using AngularJS.
This depends on the front end that you are using. For me, I am using AngularJS and I have to do something like this.
angular.module('AngularApp').factory('authFactory',
function($window){ //the window object will be able to access the token
var auth = {};
auth.saveToken = function(token){
$window.localStorage['token_name'] = token; //saving the token
}
auth.getToken = function(){
return $window.localStorage['token_name']; //retrieving the token
}
return auth;
}
.service('authInterceptor, function(authFactory){
return { headers: {Authorization: 'Bearer "+ authFactory.getToken()}
} //the last line gets the retrieved token and put it in req.header
Then, you just need to include 'authInterceptor' in all the http methods when you communicate with the backend. This way, nodeJS will be able to pick up the token.
You can see the Authorization field in req.header if you use the chrome developer tool and look at the Network tab. Hope this helps.

Resources