I don't know if this is the right place to ask that.
I have an existent Express API and now we started to using Parse in the same server.
Is there a way to authenticate an user in the Parse server from the Express endpoints?
I'm trying to avoid the migration of all the existent API to the cloud functions.
Thanks
The best way to do this is to use node-parse SDK from within your node app. Call the Parse.User.logIn() method with the credentials supplied by the user. Get the session key and store that in a cookie. Then write an auth middleware to verify that the session key exists in the database and points to a valid user. Optionally, cache the session keys in redis or memcached to avoid DB lookups on every request.
Try adding an express middleware something like this. Just showing the happy path if the token is valid, it'll add the user object to the request and then run next middleware.
app.use('/api/*', (req, res, next) => {
unirest.get(env.getParseURL() + '/users/me')
.headers({
'X-Parse-Application-Id': env.getApplicationId(),
'x-parse-session-token': req.header("x-parse-session-token")
})
.send({})
.end((userData) => {
if (userData.status == 200) {
req.user = Parse.Object.fromJSON(userData.body);
next();
}
What I do is:
Parse.User.logIn to validate the user. If successful, lookup session token and store the session token in a cookie.Here's the middleware I use:
authMiddleware.requireLogin = function (req, res, next) {
// debug(req.session);
debug('AUTH: ' + req.method + ' ' + req.url);
var redirectUrl = qs.escape(req.originalUrl);
// debug(req.session);
if (req.session.token == undefined) {
debug('No session token');
return res.redirect('/account/pub/login?redirect=' + redirectUrl);
} else {
debug('Query for session token' + req.session.token);
Parse.Cloud.useMasterKey();
var sq = new Parse.Query('_Session')
.equalTo('sessionToken', req.session.token)
.include('user');
sq.first().then(function(sessionResult) {
if (sessionResult == undefined) {
debug("No matching session");
res.redirect('/account/pub/login');
} else {
debug("Got matching session");
req.user = sessionResult.get('user');
res.locals.session = req.session;
res.locals.user = req.user;
next();
}
}, function(err) {
debug("Error or no matching session: " + err);
res.redirect('/account/pub/login');
});
}
};
And the login code:
router.post('/login', function(req, res) {
debug('Got post to /login');
req.checkBody('lg_email', 'Enter a valid email address.').isEmail();
req.checkBody('lg_password', 'Password required.').notEmpty();
var errors = req.validationErrors();
if (errors) {
debug('ERRORS logging in:');
debug(errors);
res.render('pages/account-public/login', { 'errors': errors, 'prev_body': req.body });
return;
}
debug('Got valid login form.');
debug('Try to login');
Parse.User.logIn(req.body.lg_email, req.body.lg_password).then(function(user) {
debug('Successfully logged in!');
debug(user);
debug('---');
debug(Parse.User.current());
// req.session.user = user;
req.session.token = user.getSessionToken();
debug("Session token: " + user.getSessionToken());
debug("Session token: " + req.session.token);
debug(req.session);
req.user = user;
if (req.body.redirectUrl) {
res.redirect(req.body.redirectUrl);
} else {
res.redirect('/');
}
}, function(err) {
debug('Error logging in.');
debug('Error: ');
debug(err);
if (err.code == 101) {
errors = [{ param: 'unknown_param',
msg: "Invalid email or password.",
value: ''
}];
} else {
errors = [{ param: 'unknown_param',
msg: error.message,
value: ''
}];
}
req.session = null;
res.render('pages/account-public/login', { 'errors': errors, 'prev_body': req.body });
});
});
This all depends on the cookie-session middleware (npm install cookie-session) which needs to be applied before any middleware that uses it (e.g. this authentication middleware):
app.use(cookieSession({
name: 'session',
secret: "xxxxx",
maxAge: 15724800000
}));
@milesrichardson I'm getting:
error: Error generating response. ParseError { code: 209, message: 'This session token is invalid.' } code=209, message=This session token is invalid.
As a result of:
var sq = new Parse.Query('_Session')
.equalTo('sessionToken', req.session.token)
.include('user');
sq.first().then(function(sessionResult) {
console.log("sessionResult :"+sessionResult);
if (sessionResult == undefined) {
console.log("No matching session");
res.session.redirect_to = req.url;
return res.redirect('/login');
} else {
console.log("Got matching session");
req.user = sessionResult.get('user');
res.locals.session = req.session;
res.locals.user = req.user;
return next();
}
}, function(err) {
console.log("Error or no matching session: " + err);
return res.redirect('/login');
});
Querying for the session token in the API explorer on the dashboard succeeds..
Any thoughts?
Why are you guys home baking the logic to validate a session token using queries against the _Session collection when parse-server already has the build-in token validation via the REST api by doing a GET on parse/users/me? For example are you checking the session expiry? If you query this with the token in the header it'll do all this validation and return the user in the body of the request.. Is it the overhead of doing a http call that's leading you to rewrite this, seems a bit risky imho.
http://blog.parse.com/announcements/validating-session-tokens-with-the-rest-api/
We'll provide in the next release an authentication middleware backed by the parse-server auth middleware.
@flovilmart - Will this be before the parse server shutdown?(oh please oh please) :)
I came up with this solution, caching the user in Redis and implementing this middleware.
Set the x-session-token as a session header and pass it from ParseUser.getCurrentUser().getSessionToken() value.
And access the req.user object in your routers endpoints.
auth.js
import request from 'request-promise'
import ParseClient from './parse-client'
import Cache from './cache'
export default (config) => {
let client = ParseClient(config);
let cache = Cache('auth');
function auth(req, res, next) {
// if no session token, bad request
if(!req.headers['x-session-token']){
res.status(400).send("Missing sessionToken")
return
}
let authToken = req.headers['x-session-token']
//if user in session, forward the request
cache.get(authToken)
.then( user => user == null ? client.getMe(authToken) : user )
.then( user => cache.set(authToken, user) )
.then( user => {
req.user = user;
next();
})
.catch( error => {
console.error('Fail to authenticate user', error);
res.sendStatus(401);
})
}
return auth
}
cache.js
import redis from 'redis'
import Promise from 'bluebird'
import { config } from './config'
// Define redis promisses
Promise.promisifyAll(redis.RedisClient.prototype);
Promise.promisifyAll(redis.Multi.prototype);
let redisClient = redis.createClient(config.cache.port, config.cache.host, {db: 1});
export default (prefix) => {
return {
set: function(_key, value) {
let key = `${prefix}:${_key}`
return redisClient.setAsync(key, JSON.stringify(value))
.then(() => redisClient.persistAsync(key))
.then(() => redisClient.expireAsync(key, config.cache.ttl))
.then(() => value)
},
get: function(_key) {
let key = `${prefix}:${_key}`
return redisClient.getAsync(key)
.then(value => value == null ? null : JSON.parse(value))
},
del: function(_key) {
let key = `${prefix}:${_key}`
return redisClient.delAsync(key)
}
}
}
and the parse-client.js a simple function to get the user based on the session token.
import request from 'request-promise'
...
getMe: function(authToken) {
return request({
uri: config.uri + '/users/me',
method: 'GET',
json: true,
headers: {
'X-Parse-Application-Id': config.applicationId,
'X-Parse-REST-API-Key': config.restKey,
'X-Parse-Session-Token': authToken
}
})
}
and set up the middleware
app.use('/api/*', auth(parseConfig));
Running redis adds to the complication. I'd rather something which didn't
make my parse-server migration simple..
On Sun, Dec 11, 2016 at 2:41 AM, ronaldo-getreveel <[email protected]
wrote:
I came up with this solution, caching the user in Redis and implementing
this middleware.Set the x-session-token as a session header and pass it from
ParseUser.getCurrentUser().getSessionToken() value.
And access the req.user object in your routers endpoints.auth.js
import request from 'request-promise'import ParseClient from './parse-client'import Cache from './cache'
export default (config) => {
let client = ParseClient(config);
let cache = Cache('auth');function auth(req, res, next) {
// if no session token, bad request
if(!req.headers['x-session-token']){
res.status(400).send("Missing sessionToken")
return
}let authToken = req.headers['x-session-token'] //if user in session, forward the request cache.get(authToken) .then( user => user == null ? client.getMe(authToken) : user ) .then( user => cache.set(authToken, user) ) .then( user => { req.user = user; next(); }) .catch( error => { console.error('Fail to authenticate user', error); res.sendStatus(401); })}
return auth
}cache.js
import redis from 'redis'import Promise from 'bluebird'import { config } from './config'
// Define redis promissesPromise.promisifyAll(redis.RedisClient.prototype);Promise.promisifyAll(redis.Multi.prototype);
let redisClient = redis.createClient(config.cache.port, config.cache.host, {db: 1});
export default (prefix) => {
return {
set: function(_key, value) {
let key =${prefix}:${_key}
return redisClient.setAsync(key, JSON.stringify(value))
.then(() => redisClient.persistAsync(key))
.then(() => redisClient.expireAsync(key, config.cache.ttl))
.then(() => value)
},get: function(_key) { let key = `${prefix}:${_key}` return redisClient.getAsync(key) .then(value => value == null ? null : JSON.parse(value)) }, del: function(_key) { let key = `${prefix}:${_key}` return redisClient.delAsync(key) }}
}and the parse-client.js a simple function to get the user based on the
session token.getMe: function(authToken) {
return request({
uri: config.uri + '/users/me',
method: 'GET',
json: true,
headers: {
'X-Parse-Application-Id': config.applicationId,
'X-Parse-REST-API-Key': config.restKey,
'X-Parse-Session-Token': authToken
}
})
}and set up the middleware
app.use('/api/*', auth(parseConfig));
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/ParsePlatform/parse-server/issues/2985#issuecomment-266279891,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAFBNKwdfg2zeaK9j5dr_dQF2UnvXC_kks5rG-99gaJpZM4Kmuid
.
@flovilmart Hi sir, do you have any news regarding the authentication middleware ?
For now, I decided to go the single page application route (using Vue.js). I store the session token on the client and upon refresh, I use Parse.User.become(...) from the stored session token, or present login if needed, etc.
Closing due to lack of activity, please update to latest parse-server version and open a new issue if the issue persist.
Don't forget to include your current:
Is there any update on Parse express auth middleware ?
Most helpful comment
@flovilmart Hi sir, do you have any news regarding the authentication middleware ?