Authentication on Server side routes in Meteor - node.js

What is the best way (most secure and easiest) to authenticate a user for a server side route?
Software/Versions
I'm using the latest Iron Router 1.* and Meteor 1.* and to begin, I'm just using accounts-password.
Reference code
I have a simple server side route that renders a pdf to the screen:
both/routes.js
Router.route('/pdf-server', function() {
var filePath = process.env.PWD + "/server/.files/users/test.pdf";
console.log(filePath);
var fs = Npm.require('fs');
var data = fs.readFileSync(filePath);
this.response.write(data);
this.response.end();
}, {where: 'server'});
As an example, I'd like to do something close to what this SO answer suggested:
On the server:
var Secrets = new Meteor.Collection("secrets");
Meteor.methods({
getSecretKey: function () {
if (!this.userId)
// check if the user has privileges
throw Meteor.Error(403);
return Secrets.insert({_id: Random.id(), user: this.userId});
},
});
And then in client code:
testController.events({
'click button[name=get-pdf]': function () {
Meteor.call("getSecretKey", function (error, response) {
if (error) throw error;
if (response)
Router.go('/pdf-server');
});
}
});
But even if I somehow got this method working, I'd still be vulnerable to users just putting in a URL like '/pdf-server' unless the route itself somehow checked the Secrets collection right?
In the Route, I could get the request, and somehow get the header information?
Router.route('/pdf-server', function() {
var req = this.request;
var res = this.response;
}, {where: 'server'});
And from the client pass a token over the HTTP header, and then in the route check if the token is good from the Collection?

In addition to using url tokens as the other answer you could also use cookies:
Add in some packages that allow you to set cookies and read them server side:
meteor add mrt:cookies thepumpinglemma:cookies
Then you could have something that syncs the cookies up with your login status
Client Side
Tracker.autorun(function() {
//Update the cookie whenever they log in or out
Cookie.set("meteor_user_id", Meteor.userId());
Cookie.set("meteor_token", localStorage.getItem("Meteor.loginToken"));
});
Server Side
On the server side you just need to check this cookie is valid (with iron router)
Router.route('/somepath/:fileid', function() {
//Check the values in the cookies
var cookies = new Cookies( this.request ),
userId = cookies.get("meteor_user_id") || "",
token = cookies.get("meteor_token") || "";
//Check a valid user with this token exists
var user = Meteor.users.findOne({
_id: userId,
'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token)
});
//If they're not logged in tell them
if(!user) return this.response.end("Not allowed");
//Theyre logged in!
this.response.end("You're logged in!");
}, {where:'server'});

I think I have a secure and easy solution for doing this from within IronRouter.route(). The request must be made with a valid user ID and auth token in the header. I call this function from within Router.route(), which then gives me access to this.user, or responds with a 401 if the authentication fails:
// Verify the request is being made by an actively logged in user
// #context: IronRouter.Router.route()
authenticate = ->
// Get the auth info from header
userId = this.request.headers['x-user-id']
loginToken = this.request.headers['x-auth-token']
// Get the user from the database
if userId and loginToken
user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}
// Return an error if the login token does not match any belonging to the user
if not user
respond.call this, {success: false, message: "You must be logged in to do this."}, 401
// Attach the user to the context so they can be accessed at this.user within route
this.user = user
// Respond to an HTTP request
// #context: IronRouter.Router.route()
respond = (body, statusCode=200, headers) ->
this.response.statusCode statusCode
this.response.setHeader 'Content-Type', 'text/json'
this.response.writeHead statusCode, headers
this.response.write JSON.stringify(body)
this.response.end()
And something like this from the client:
Meteor.startup ->
HTTP.get "http://yoursite.com/pdf-server",
headers:
'X-Auth-Token': Accounts._storedLoginToken()
'X-User-Id': Meteor.userId()
(error, result) -> // This callback triggered once http response received
console.log result
This code was heavily inspired by RestStop and RestStop2. It's part of a meteor package for writing REST APIs in Meteor 0.9.0+ (built on top of Iron Router). You can check out the complete source code here:
https://github.com/krose72205/meteor-restivus

Because server-side routes act as simple REST endpoints, they don't have access to user authentication data (e.g. they can't call Meteor.user()). Therefore you need to devise an alternative authentication scheme. The most straightforward way to accomplish this is with some form of key exchange as discussed here and here.
Example implementation:
server/app.js
// whenever the user logs in, update her apiKey
Accounts.onLogin(function(info) {
// generate a new apiKey
var apiKey = Random.id();
// add the apiKey to the user's document
Meteor.users.update(info.user._id, {$set: {apiKey: apiKey}});
});
// auto-publish the current user's apiKey
Meteor.publish(null, function() {
return Meteor.users.find(this.userId, {fields: {apiKey: 1}});
});
lib/routes.js
// example route using the apiKey
Router.route('/secret/:apiKey', {name: 'secret', where: 'server'})
.get(function() {
// fetch the user with this key
// note you may want to add an index on apiKey so this is fast
var user = Meteor.users.findOne({apiKey: this.params.apiKey});
if (user) {
// we have authenticated the user - do something useful here
this.response.statusCode = 200;
return this.response.end('ok');
} else {
// the key is invalid or not provided so return an error
this.response.statusCode = 403;
return this.response.end('not allowed');
}
});
client/app.html
<template name="myTemplate">
{{#with currentUser}}
secret
{{/with}}
</template>
Notes
Make /secret only accessible via HTTPS.
While it's very likely that the user requesting /secret is currently connected, there is no guarantee that she is. The user could have logged in, copied her key, closed the tab, and initiated the request sometime later.
This is a simple means of user authentication. I would explore more sophisticated mechanisms (see the links above) if the server-route reveals high-value data (SSNs, credit cards, etc.).
See this question for more details on sending static content from the server.

I truly believe using HTTP headers are the best solution to this problem because they're simple and don't require messing about with cookies or developing a new authentication scheme.
I loved #kahmali's answer, so I wrote it to work with WebApp and a simple XMLHttpRequest. This has been tested on Meteor 1.6.
Client
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
// Skipping ahead to the upload logic
const xhr = new XMLHttpRequest();
const form = new FormData();
// Add files
files.forEach((file) => {
form.append(file.name,
// So BusBoy sees as file instead of field, use Blob
new Blob([file.data], { type: 'text/plain' })); // w/e your mime type is
});
// XHR progress, load, error, and readystatechange event listeners here
// Open Connection
xhr.open('POST', '/path/to/upload', true);
// Meteor authentication details (must happen *after* xhr.open)
xhr.setRequestHeader('X-Auth-Token', Accounts._storedLoginToken());
xhr.setRequestHeader('X-User-Id', Meteor.userId());
// Send
xhr.send(form);
Server
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import { Roles } from 'meteor/alanning:roles'; // optional
const BusBoy = require('connect-busboy');
const crypto = require('crypto'); // built-in Node library
WebApp.connectHandlers
.use(BusBoy())
.use('/path/to/upload', (req, res) => {
const user = req.headers['x-user-id'];
// We have to get a base64 digest of the sha256 hashed login token
// I'm not sure when Meteor changed to hashed tokens, but this is
// one of the major differences from #kahmali's answer
const hash = crypto.createHash('sha256');
hash.update(req.headers['x-auth-token']);
// Authentication (is user logged-in)
if (!Meteor.users.findOne({
_id: user,
'services.resume.loginTokens.hashedToken': hash.digest('base64'),
})) {
// User not logged in; 401 Unauthorized
res.writeHead(401);
res.end();
return;
}
// Authorization
if (!Roles.userIsInRole(user, 'whatever')) {
// User is not authorized; 403 Forbidden
res.writeHead(403);
res.end();
return;
}
if (req.busboy) {
// Handle file upload
res.writeHead(201); // eventually
res.end();
} else {
// Something went wrong
res.writeHead(500); // server error
res.end();
}
});
I hope this helps someone!

Since Meteor doesn't use session cookies, client must explicitly include some sort of user identification when making a HTTP request to a server route.
The easiest way to do it is to pass userId in the query string of the URL. Obviously, you also need to add a security token that will prove that the user is really who the claim they are. Obtaining this token can be done via a Meteor method.
Meteor by itself doesn't provide such mechanism, so you need some custom implementation. I wrote a Meteor package called mhagmajer:server-route which was thoroughly tested. You can learn more about it here: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94

Related

Node.js Express Spotify API save in session

Question appeared while integrating Spotify API into Nodejs Express web application using spotify-web-api-node. How multiple simultaneous user requests should be handled? After passing the authentication step, user receives access_token, which is different for each user. Each request can have a session, for example using express-session since access_token is unique for each authenticated user. The weird thing is that I can't find an example with proper session usage in the description and samples https://www.npmjs.com/package/spotify-web-api-node where spotify-web-api-node is used. How is that possible to use global variable without session? Would it make full mess among separate user requests or I'm missing something? I guess that the access_token would be always replaced with latest authenticated user. Another usage example is here https://github.com/thelinmichael/spotify-web-api-node, though it also suggests to use one global instance.
the solution is to store the access_token and refresh_token after successful authentication in the session storage, than before calling Spotify API endpoints set both tokens for the current user from the present session:
saving tokens in the session after successful authentication:
app.get('/login', (req,res) => {
var scopes = [ ... ]
var authUrl = spotifyApi.createAuthorizeURL(scopes)
res.redirect(authUrl+"&show_dialog=true")
})
app.get('/callback', async (req, res) => {
const { code } = req.query
try {
var data = await spotifyApi.authorizationCodeGrant(code)
const { access_token, refresh_token } = data.body
spotifyApi.setAccessToken(access_token)
spotifyApi.setRefreshToken(refresh_token)
req.session.spotifyAccount = { access_token, refresh_token }
res.redirect('...')
} catch(err) {
res.send(`error ${err}`)
}
});
app.get('/userinfo', async (req,res) => {
try {
spotifyApi.setAccessToken(req.session.spotifyAccount["access_token"])
spotifyApi.setRefreshToken(req.session.spotifyAccount["refresh_token"])
var result = await spotifyApi.getMe()
console.log(result.body);
res.status(200).send(result.body)
} catch (err) {
res.status(400).send(err)
}
});
since access_token is only identification key which identifies any API request, that ensures that API endpoints are called for the current user. This technique prevents mess and confusion, so that each user can see and manipulate his data only.

Getting session cookie data on initial page load following a redirect from a server (Node and React)

I am trying to build a token system to allow authentication via an email link. The flow I am thinking might work is...
click email link (of the form site.com/login?token=hf74hf64&email=m#email.com) -> server checks the token is valid and the email is registered -> server redirects to '/' with a session cookie -> client acknowledges session cookie and authenticates the user
The last step is where I'm having trouble. How do I detect from within my component that a session cookie is present?
I was thinking of something like this in my React auth component:
class AuthenticatedComponent extends Component {
componentWillMount() {
if (cookie) {
this.props.dispatch(authenticateUser())//.....
}
}
}
Might this work, or do I need to make a separate fetch to the server and trigger the dispatch depending on the response?
We've implemented a very similar approach for our app. For this to work, we handle all the login in Node and not in the actual components.
Check if token is provided in query string
Pass token to server to validate
If token is valid, create the cookie as you would for a normal user/pass login
Redirect call to original url, sans the token
server.js
// I abstracted the login functionality into one call since it's the same for us
var handleAuthRequest = function handleAuthRequest(auth_body, req, res, next) {
request ({
method: 'POST',
uri: Constants.API_LOGIN_URL,
body: auth_body,
json: true
}, (error, response, body) => {
if (response.statusCode === 200) {
// this makes a cookie with the response of the body (auth token)
ssoUtils.generateCookies(body, res)
// this redirects to the initial url, with the provided cookie.
// Assuming your router already doesn't allow certain components to be accessed
// without a cookie, it would work the same for the login_token auth too.
res.redirect(req.url)
}
else {
next();
}
})
}
// this needs to come before any other routes
app.use((req, res, next) => {
// check if login_token query string was provided
if (req.query.hasOwnProperty('login_token')) {
var {login_token} = req.query
// API call to server to validate token
var jwtToken = jwt.sign({
sub: login_token
}, Constants.API_JWT_SECRET)
// modify the redirect url to remove the token
let parsed = url.parse(req.url)
delete req.query['login_token']
let newUrl = parsed.pathname + '?' + qs.stringify(req.query)
req.url = newUrl
// call the generic login handler
return handleAuthRequest({link_token: jwtToken}, req, res, next)
}
Assuming your server will return the same response from logging in or a valid link token, this would just redirect the call back to whatever your existing process is so no separate functionality client side is needed. As you can see, we also sign the token in a JWT to ensure it's only accepted by the server if sent from our app.
We use React Router to handle our client side routing. Your onEnter check for the initial route would look like this.
routes.js
// token is passed in from our cookie by both the client and server
module.exports = function (token, userAgent, originalUrl) {
function isToken() {
return token !== undefined && token !== null;
}
function ifNoTokenRedirect(nextState, replaceState) {
// check if token is set from cookie
if (!isToken()) {
replaceState({ nextPathname: nextState.location.pathname}, '/signup? redirect=' + originalUrl.pathname);
}
}
return (
// the actual routes
)
}

How to show different page if user is logged in via firebase

I have a slight problem, and it seems to be an easy one, but I cannot seem to wrap my head around what to do.
I have an express app, that uses Firebase to store data. I am able to login, register and log out trough a client side script, but my problem is: How do I check via express if a user is logged in, to be able to send a different page to the logged in users?
This is my code so far:
var firebase = require('firebase');
// Initialize Firebase
var config = {
serviceAccount: "./Chat Application-ad4eaaee3fcc.json",
databaseURL: "MY_DATABASE_URL"
};
firebase.initializeApp(config);
and then I want to show a special page for logged in users, and this is what I have tried:
router.get("/special-page", function(req, res, next) {
var user = firebase.auth().currentUser;
console.log(user); // this variable gets undefined
if(user) {
res.render("special-page");
} else {
res.redirect("/");
}
});
I know this might seem like an easy question, but any help would be much appreciated!
Thanks in advance.
The user side, and server side, are completely different execution areas. Hence, as you probably guessed, calling firebase.auth().currentUser on the server cannot work if the authentication occurred on the client.
The server process just does not have this information, unless the client tells him.
You could just have a request header telling "i am logged as XXX", but it would not be secure, because the server would not be able to verify that information, and a malicious user could pretend to be another one.
The only solution to this, in your use case, is to provide the Firebase token to the server, and then the server needs to verify this token against firebase server, and only then it will be 100% sure about the client authentication.
I needed that in my React app for Server Side Rendering, here is how I did it.
Upon user authentication, set a cookie that contains the firebase token
Unset the cookie when the users logs out
In the server, read the cookie to authenticate client user at each request
Code in the client :
const setAppCookie = () => firebase.auth().currentUser &&
firebase.auth().currentUser.getToken().then(token => {
cookies.set('token', token, {
domain: window.location.hostname,
expire: 1 / 24, // One hour
path: '/',
secure: true // If served over HTTPS
});
});
const unsetAppCookie = () =>
cookies.remove('token', {
domain: window.location.hostname,
path: '/',
});
// triggered by firebase auth changes, this is where you deal
// with your users authentication in your app
fbAuth.onAuthStateChanged(user => {
if (!user) {
// user is logged out
return;
}
// user is logged in
setAppCookie();
// Reset cookie before hour expires
// (firebase tokens are short lived, say the docs)
setInterval(setAppCookie, 3500);
});
[...]
// In the logout code
unsetAppCookie();
Code in the server:
// Before serving express app, enable cookie parsing
app.use(cookieParser());
// In the code dealing with your requests
const { token } = req.cookies;
if (!token) {
// renderWithoutUser();
}
//
// If user found in cookie, verify the token and render with logged in store
//
console.log('Verifying token', token);
firebase.auth().verifyIdToken(token)
.then(decodedToken => {
const uid = decodedToken.sub;
console.log('User is authenticated for this request', uid);
// renderWithUser();
})
.catch(err => {
console.error('WARNING token invalid or user not found', err);
// renderWithoutUser();
});

Node/Express - Good approach to secure communication between client/server

I'm building a backend API with Node/Express, which get the data from a MongoDB. The front will be written in React.
I would like to secure the communication client/server, but I don't know how I have to think about the process.
I see many tutorial about passport or JWT, but this is good for an user authentication.
I don't know if creating a token for every request based on the time (for example) is a good approach or it's too consuming for a web app.
But my goal is to secure the data because even if the API is private you can easily find out the route and try to figure it out how to fake request with Postman or something else to scrap the data.
The accepted standard is to use a fixed API KEY. This peace of info should be a randomly generated string that you send in each request in the header. Your server has to check the HTTP request each time to see if the API KEY is present in the header, and if it is, then it has to check against the stored value in the environment variable (never store the API KEY in code).
If the API KEY gets compromised, then you can easily update the env variable, and you are good again.
Now, this solution will be pointless without a HTTPS connection, because anyone will be able to sniff the traffic and see the API KEY. An encrypted connection is a must in this case.
This approach is used by virtually every company that has a public API: Twitter, Facebook, Twilio, Google etc.
Google for example has an extra step where they give you a token that will expire, but this will be an over kill in your case: at least in the beginning.
The following code is an example of my implementation of a API KEY check
app.use(function(req, res, next) {
//
// 1. Check if the APIKey is present
//
if(!req.headers.authorization)
{
return res.status(400).json(
{
message: "Missing APIKey.",
description: "Unable to find the APIKey"
}
);
}
//
// 2. Remove Basic from the beginning of the string
//
let noBasic = req.headers.authorization.replace('Basic ', '');
//
// 3. Convert from base64 to string
//
let b64toString = new Buffer(noBasic, 'base64').toString("utf8");
//
// 4. Remove the colon from the end of the string
//
let userAPIKey = b64toString.replace(':', '');
//
// 5. Check if the APIKey matches the one on the server side.
//
if(userAPIKey != process.env.API_KEY)
{
return res.status(400).json(
{
message: "APIKey don't match",
description: "Make sure what you are sending is what is in your server."
}
);
}
//
// -> Go to the next stage
//
next()
});
You can check the whole file with the whole implementation hear.
As I just finished the auth part of my AngularJS application. The answer will be JWT and Passport, you should use the great technologies to protect your data / API.
If you use the JWT library, it will help you hold the http heads for authorization.
Some of the code I used:
app.js
var jwt = require('express-jwt');
var auth = jwt({
secret: config.jwt.secret,
userProperty: 'payload'
});
app.use('/api/secret', auth, apiSecretRoutes);
login.js
module.exports.login = function (req, res) {
if (!req.body.username || !req.body.password) {
return tools.sendJSONresponse(res, 400, {
message: 'All fields required!'
});
}
passport.authenticate('local', function (err, user, info) {
var token;
if (err) {
return tools.sendJSONresponse(res, 404, err);
}
if (user) {
token = user.generateJwt();
return tools.sendJSONresponse(res, 200, {
ok: true,
message: 'welcome ' + user.name,
token: token
});
} else {
return tools.sendJSONresponse(res, 400, info);
}
})(req, res);
};
user.js
userSchema.methods.generateJwt = function() {
var expiryDays = 1;
var expiry = new Date();
expiry.setDate(expiry.getDate() + expiryDays);
return jwt.sign({
_id: this._id,
username: this.username,
name: this.name,
exp: parseInt(expiry.getTime() / 1000)
}, config.jwt.secret);
};
More Refs:
https://thinkster.io/angularjs-jwt-auth
http://devdactic.com/restful-api-user-authentication-1/
http://devdactic.com/restful-api-user-authentication-2/
http://jwt.io
https://github.com/auth0/express-jwt

Save Token in local Storage using node

I'm using JWT ("jsonwebtoken": "^5.4.0") with express 4 and jade.
I'm able to create the right Token, but How can i Pass this token in each call?
Where I have to store this token ? in headers or in localStorage?
For now I'm using CURL with Postman, and Set token in header in
x-access-token
Have I Do create a middleware that retrieve a token from Database and use this in each call?
thanks
You do not need to save and check the token from the database. This token such a mechanism can be decoded with only your-server, and if it was done that the token is valid. The code that you want to do should look like.
var cookieParser = require('cookie-parser')
app.use(cookieParser())
app.get('/login', function(req, res, next) {
var user = {name:'test'}; //!! find the user and check user from db then
var token = jwt.sign(user, 'secret', {
expiresInMinutes: 1440
});
res.cookie('auth',token);
res.send('ok');
});
app.use(function(req, res, next) {
var token = req.cookies.auth;
// decode token
if (token) {
jwt.verify(token, 'secret', function(err, token_data) {
if (err) {
return res.status(403).send('Error');
} else {
req.user_data = token_data;
next();
}
});
} else {
return res.status(403).send('No token');
}
});
Here you can find very nice article : https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
I would recommend checking this out if you want local storage: https://www.npmjs.com/package/node-localstorage
But, with that said, you guys and girls wouldn't believe how long it took me to find res.cookie('auth' token) from the above answer. I scoured Google for hours, Passport docs, Express docs, GraphQL and authentication/authorization docs in an effort to find out how to get the token to the API in a stateless manner.
I already built JWT token security and secured my GraphQL resolvers with it, but then, I opted to use EJS along with graphql-request (approx same as Apollo Client), so I needed to find a way to pass the token to my middleware without using a server side session.
Storing a JWT token in cookies is fine especially if you take extra precautions such as signing the cookie, and I recall there are also options you can include that keep the cookie secure, so that other sites cannot see it if the "browser" allows access to cookies. If a cookie is signed with your server secret, the data inside the cookie simply cannot be altered and still be valid. The risk is always still someone leaking their token/cookie, and if that bothers you, do research into refresh tokens. However, API tokens are generally and should be kept tightly secret and safe. Your biggest annoyance will more likely be the requirement to maintain a blacklist of JWTs that expire a year from now if you set expiry to 1y.
I am just including my findings here because this question is actually a rare resource it seems...
Here is my Express middleware for authentication:
// AUTHENTICATION
app.use(async (req) => {
try {
const token = req.headers.authorization || req.cookies.auth
const { person } = await jwt.verify(token, SECRET)
req.person = person
return req.next()
} catch (e) {
return req.next()
}
})
You can see I am setting the token from the header with cookie as fallback. This supports my needs fine and allows me to use really any client with stateless security.
My logged in user is available as req.person in my views and GraphQL resolvers. If req.person is not set, the user is treated as not-logged-in.
I am using return req.next() which is important to note because calling next() without parameters is treated as "clean go-to next middleware and/or proceed to process request". If you include any string or object parameter, it will throw an error that can bubble down to your error handling middleware. You can try it yourself. Put return next('You are not authenticated.') in the catch block and you will see it halt the request before your route.
I use return next() because I handle authorization in the routes and in my resolvers. It allows more flexibility such as facilitating register and login mutations to be accessed by non-authenticated users.
Here is my GraphQL endpoint (I am using Apollo Server):
app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
const context = {
person: req.person
}
return {
schema,
context,
rootValue: null
}
}))
In my GraphQL resolvers, the third parameter of every query has context.person populated with req.person which comes from the above Authentication middleware.
That is really all a person needs to know.
Here is how I am using the NPM package called graphql-request:
https://www.npmjs.com/package/graphql-request
app.get('/allpeople', async (req, res) => {
try {
const client = new GraphQLClient(GRAPHQL_ENDPOINT, {
headers: { Authorization: req.headers.authorization || req.cookies.auth }
})
const query = `query allPeople($serialNumber: String!) {
allPeople(serialNumber: $serialNumber) {
id
created
status
email
}
}`
const variables = {
serialNumber: req.person
}
const response = await client.request(query, variables)
res.render('allpeople/list', { people: response.allPeople })
} catch (e) {
throw [`allPeople`, `${JSON.stringify(error, null, 2)}`]
}
})
I include this code because there are no "more advanced" example usages of graphql-request, and I like it so far. It is very concise and could easily be swapped out for Apollo Client if you venture into React.js. My examples here are also very relevant for anyone researching createNetworkInterface and new ApolloClient().

Resources