I wrote a JS script for a webserver that includes authentication using the passport and the digest strategy. I am not using sessions, but I have tried using sessions and it does not change the results. The browser requests the "/login" route and displays a built-in login dialog. Authentication works fine, but I can't get the user to "logout." The problem seems to be that the browser remembers the login credentials and resends them automatically. The end result is that the user must close the browser completely to log out, but that is a problem for this application. I know that there must be a way to tell the browser not to do this, but I haven't figured it out.
I figured out a hack to get the browser to display the login dialog again; force the authentication function to return a false. However, I haven't figured out a way to do this per-session. Right now, if one person logs out, everyone gets logged out. It's not a workable solution.
Can anyone tell me what I'm doing wrong here? One thing I'm wondering is whether I'm returning the proper response to the browser when it POSTs to the /logout route (see end). I return res.json(""), but maybe there's a different response I should send to tell the browser to forget the credentials for the session?
My code follows. Any insight is greatly appreciated. Thank you in advance.
T
var passport = require('passport'),
DigestStrategy = require('passport-http').DigestStrategy;
var express = require('express');
var app = express();
app.configure(function () {
app.use(
"/", //the URL throught which you want to access to you static content
express.static('./www') //where your static content is located in your filesystem
);
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keep moving forward' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
});
app.listen(80); //the port you want to use
/**
* CORS support.
*/
app.all('*', function(req, res, next){
if (!req.get('Origin')) return next();
// use "*" here to accept any origin
// For specific domain, do similar: http://localhost'
// Use an array for multiple domains, like [http://localhost', 'http://example.com' ]
res.set('Access-Control-Allow-Origin', '*' );
res.set('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.set('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Authorization');
next();
});
//
// Configure passport authentication
//
// Used to force the browser to display the login screen again.
var forceLogin = false;
passport.use(new DigestStrategy({ qop: 'auth' },
function(username, done ) {
if ( !forceLogin )
{
return done(null, username, "nimda");
}
else
{
//
// Forces the browser to request the user name again by returning a failure to its last request.
//
console.log ( "forcing user to log in" );
forceLogin = false;
return done(null, false);
}
));
passport.serializeUser(function(user, done) {
console.log( "serialize user " + user.toString() );
done(null, user.toString());
});
passport.deserializeUser(function(id, done) {
console.log( "deserialize user " + id.toString() );
done(null, id);
});
app.post('/login', passport.authenticate('digest', { session: true }),
function(req, res) {
console.log( "/login");
res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
res.json({ id: req.user.id, username: req.user.username });
});
app.post('/logout', function(req, res){
req.logOut();
// NOTE: Same results as req.logout
//
// req.session.destroy(function (err) {
// res.redirect('/');
// });
res.redirect("/");
// flag to force a login
forceLogin = true;
console.log( "logout");
// Is this the proper return to the browser?
return res.json("");
});
You could try the following
req.session.destroy()
req.logout()
res.redirect('/')
Saving the new state of the session is important if you're using some form a session store for persistent sessions (eg. Redis). I tried the above code with a similar setup and it seems to work for me
Server needs somehow notify web browser that he need to refresh login info (MD5 digests).
For that you can send 401 error code and usually browser will show login popup message.
By the way, all these tricks with sessions are useless because browser already have all required info for automatic logging in.
So you can try next code:
req.logout();
res.send("logged out", 401);
A bit late to the party, but I found this thread on Google while searching for answer and nothing worked for me. Finally, I was able to solve it so thought I could post solution here for anyone that might read this in the future.
So basically, I use node.js, express, passport(local) and rethinkdb as my storage, and I was having problem with req.logout(); not logging me out.
My setup:
var express = require( 'express' );
var passport = require( 'passport' );
var session = require( 'express-session' );
var RDBStore = require( 'express-session-rethinkdb' )( session );
I tried a lot of stuff and nothing was working, so finally I decided to manually do it, by removing session record from database myself.
Here is the code I used:
app.get( '/logout', function ( req, res, next ) {
if ( req.isUnauthenticated() ) {
// you are not even logged in, wtf
res.redirect( '/' );
return;
}
var sessionCookie = req.cookies['connect.sid'];
if ( ! sessionCookie ) {
// nothing to do here
res.redirect( '/' );
return;
}
var sessionId = sessionCookie.split( '.' )[0].replace( 's:', '' );
thinky.r.db( 'test' ).table( 'session' ).get( sessionId ).delete().run().then( function( result ) {
if ( ! result.deleted ) {
// we did not manage to find session for this user
res.redirect( '/' );
return;
}
req.logout();
res.redirect( '/' );
return;
});
});
So I hope this helps someone :)
res.redirect('/') will end the response, you can not call subsequently write(), end(), send(), json() ...etc
Just like this:
app.post('/logout', function(req, res){
req.logOut();
res.redirect("/");
});
deserializeUser should return a user instead of id:
passport.deserializeUser(function(id, done) {
findUser(id, function(err, user) {
if(err) {
done(err)
} else {
done(null, user);
}
}
});
I have taken a short discussion, I realize that digest username/password store on browser (not on server), so req.logout not help, there no way to clear it from server. You just close browser and open again that mean logout.
You can use a variable in cookie or session to mark the session as logout, but I think we should not do not, because username/password still in browser.
It's digest!
Related
I wanted to use my login data (username, ID) and store them in other tables so that I could access the other data stored in those tables. I am using Node.js and express for my server, and for the session I am using the express-session module.
Here is the app.js and the session middleware and its default options
import session from 'express-session';
app.use(session({
name:'test',
secret: "thisismysecrctekeyfhrgfgrfrty84fwir767",
saveUninitialized: true,
resave: false
}));
//some other stuff
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((_, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
app.use((req,res,next) => {
console.log('req.session',req.session);
next();
});
After this I use router for all my routes,
Here is the login route where I save my username into the session
router.post('/login', (req, res, next) => {
// checks if email exists
User.findOne({ where : { //
name: req.body.name, //
}})
.then(dbUser => {
if (!dbUser) {
return res.status(404).json({message: "user not found"});
} else {
// password hash
bcrypt.compare(req.body.password, dbUser.password, (err, compareRes) => {
if (err) { // error while comparing
res.status(502).json({message: "error while checking user password"});
} else if (compareRes) {
// password match
const token = jwt.sign({ name: req.body.name }, 'secret', { expiresIn: '1h' });
const sessName= req.body.name;
req.session.name=sessName;
res.status(200).json({message: "user logged in", "token": token});
} else { // password doesnt match
res.status(401).json({message: "invalid credentials"});
};
});
};
})
.catch(err => {
console.log('error', err);
});
});
After the user enters the Login info it goes from the login route to the Auth route
router.get('/private', (req, res, next) => {
const authHeader = req.get("Authorization");
if (!authHeader) {
return res.status(401).json({ message: 'not authenticated' });
};
const token = authHeader.split(' ')[1];
let decodedToken;
try {
decodedToken = jwt.verify(token, 'secret');
} catch (err) {
return res.status(500).json({ message: err.message || 'could not decode the token' });
};
if (!decodedToken) {
res.status(401).json({ message: 'unauthorized' });
} else {
res.status(200).json({ message: `here is your resource ${req.session.name}` });
};
});
If I console log the session it shows the username on the terminal, for other routes when I store some data onto the session and access it from other routes it works fine, but when I store the username from the login route it doesn't show up when I try to access it.
router.get('/session', (req,res,next)=>{
if (req.session.name) {
res.json({name: req.session.name})
} else {
res.json({name: null});
}
})
I am new to Node.js so it would be helpful if anyone could answer this.
After
const sessName= req.body.name;
req.session.name=sessName;
the name gets saved in the session from the login route, but it shows me a null value when I access it from other routes. Plus is it normal for the session to end and the username to disappear after I restart my server.
Since both /login and /private look like they are being made by code, the problem is probably caused by that code not capturing the session cookie, retaining it and making sure it is sent with all subsequent requests on behalf of this same user.
That session cookie is the key to making express-session work. Upon first engagement with a client, the express-session middleware sees that there is no session cookie attached to this request. It creates a new one with an encrypted unique key in it, adds that key to the internal session store and returns that cookie with the http response.
For the session to work, the client that receives that cookie must retain it and send it with all future http requests from this client (until it expires). Since these http requests to /login and /private are being made programmatically, your code has to be set up properly to retain and send this cookie. It will depend upon what http library you are using to make these http requests for how exactly you go about retaining and resending the session cookie.
If these are made from Javascript running in a browser in a web page, then the browser will retain the cookie for you and you will just need appropriate settings on the http requests to make sure the cookie is sent. If you are doing cross-origin requests, then things get a little more complicated.
If these are made from your own code in your own app, then the precise steps to include the cookie will depend upon what http library you're using. Many http libraries have something often referred to as a cookie jar that can be associated with a given request/response to help maintain and send cookies.
Now that you've shown your client code, that code is using fetch() to make these calls. That's the http request library your client is using.
Because it appears this may be a cross-origin request, to be sure that cookies are sent with requests made by fetch(), you can add the credentials: "include" option like this:
fetch(`${API_URL}/private`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
credentials: "include",
}).then(...)
If fetch() doesn't send credentials (which include cookies), then your session cookie won't be sent and your server will think it has to create a new session on each new request so your session will never be maintained from one request to the next.
I am having troubles understanding how passport.js authentication flow works.
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
done(null, id);
});
app.use(
cookieSession({
name: 'session',
maxAge: 60 * 60 * 1000,
keys: [config.COOKIE_KEY_1, config.COOKIE_KEY_2],
})
);
app.use(passport.initialize());
app.use(passport.session());
// this is a middleware for my protected routes
const checkLoggedIn = (req, res, next) => {
const isLoggedIn = req.isAuthenticated() && req.user;
if (!isLoggedIn) {
return res.status(401).json({
error: 'you muse log in!',
});
}
next();
};
I have read a few articles. They say the "user.id" in passport.serialize is stored in req.session & the "id" in passport.deserialize is the same as "user.id".
My questions are:
is "user.id" sent to the browser along with the cookie?
How does passport verify cookie when there is a request to the server to get the id out of it?
Does the client know about "req.session" & can the client access this data?
Thank you very much!
The contents of the session are only known to the server and are always available in req.session. A cookie is sent between server and client which ensures that req.session is always the session that belongs to the client that sent the request req. In other words: When the code on your server accesses req.session, it will always access the session contents of the user who made the current request.
But the session contents are not accessible by the client (and nor is the session cookie if it is configured as httpOnly).
So the answers to your questions are:
No
Don't know details, but passport can access req.session.user.id
No
I'm using sessions and cookies to authenticate the users. I would like to check for users having a cookie and if so i will set the sessions variables.
So basicly what i do is :
Check if sessions variables exist
If not, check if user has cookie
If he has a cookie, I compare the value in my database.
If everything's ok, I set up the session.
Now i'd like to have that process into a module so i don't have to paste that code into each routes of my site.
Let's say I've put all that code in a middleware route located at routes/middleware/check_auth.js.
How do I export this module so I can check in my route page if the user has auth or not, something like :
//routes/index.js
var check_auth = require('./middleware/check_auth');
module.exports = function(app){
app.get('/', check_auth, function(req, res){
if(variable_from_check_auth == true){
res.render('index_with_auth');
}else{
res.render('index_without_auth');
}
});
};
Btw, I'm not sure if it's the right way to do or if I simply have to :
Call the module on each routes.
Check for some sessions variables before rendering.
If someone could help me!
You can just export your middleware as simple as this(assuming you are using express session handler and cookie parser):
var userModel = require('./user');
module.exports = function check_auth(res, req, next) {
if (!res.session) {
req.send(401);
return;
}
userModel.isAuthenticated(req.session.id, function (result) {
if (!result) {
req.send(401);
return;
});
next();
});
};
What I am trying to do is
-> use a logic form which posts to '/check_user'
-> check_user has logic to generate session and it creates the session perfectly
-> then it redirects to '/chat' which is restricted by verify() function
-> verify checks the session if persists (AT THIS POINT SESSION IS ALWAYS UNDEFINED FOR SOME REASON)
--> check_user does create session and it persists there but when redirected to '/chat' and hits verify, the session is lost.
Here is the code snippet I am using, I am not using any templating engines but plain HTML files. Any help is highly appreciated. Thanks!
app.use(express.static(__dirname + '/public'));
app.get('/login', function (req, res) {
res.sendfile(__dirname + '/public/index.html');
});
app.get('/chat', verify, function(req, res){
res.sendfile(__dirname + '/public/chat.html');
});
// when login form is posted to check_user, handle the username and passwords validation.
app.post('/check_user', function(req, res){
req.session.regenerate(function(){
req.session.user = "test";
//debugging codes debug code 1
console.log("3");
console.log(req.session.user);
//End debugging codes
res.redirect('/chat');
});
});
function verify(req, res, next) {
console.log(req.session.user);
if (req.session.user) {
console.log("1");//debug code 2
next();
} else {
console.log("2");//debug code 3
req.session.error = 'Access denied!';
res.redirect('/login');
}
};
Fixed! it. The problem was I was using res.render after ajax submit. I used window.location on ajax submit and it worked.
And another thing I found out is,
removing this closure made my session persistant throughout my application
req.session.regenerate(function(){
});
I'm using Express and Passport OpenID Google strategy and I would like to set returnURL on each auth request to be able to return to the page that initiated that auth.
The situation is that I have HTML5 slides application with Node.js backend (and with social stuff and editor and Portal and extensions... https://github.com/bubersson/humla) and I want be able to log in user on some slide (via slide menu...) but then I want him to get back to same slide easily.
So I would need something like this?
app.get('/auth/google', function(req,res) {
var cust = "http://localhost:1338/"+req.params.xxx;
passport.authenticate('google', returnURL:cust, function ...
}
I've read Passport's guide, but still don't know how to do that. I know this wouldn't be safe, but how else could I do it?
Or how can I make the application to return to the page from where the login has been initiated? Or is there a way to make OpenID authentication using AJAX (and still be able to use passport as well)?
I've figured this out for my apps Twitter authentication, I am sure that the GoogleStrategy is quite similar. Try a variant of this:
Assuming you have defined the route for the callback from the authentication service like so (from the passport guide):
app.get('/auth/twitter/callback',
passport.authenticate('twitter', {
successRedirect: authenticationRedirect(req, '/account')
, failureRedirect: '/'
})
);
Just change that block to this:
app.get('/auth/twitter/callback', function(req, res, next){
passport.authenticate('twitter', function(err, user, info){
// This is the default destination upon successful login.
var redirectUrl = '/account';
if (err) { return next(err); }
if (!user) { return res.redirect('/'); }
// If we have previously stored a redirectUrl, use that,
// otherwise, use the default.
if (req.session.redirectUrl) {
redirectUrl = req.session.redirectUrl;
req.session.redirectUrl = null;
}
req.logIn(user, function(err){
if (err) { return next(err); }
});
res.redirect(redirectUrl);
})(req, res, next);
});
Now, define your middleware for authenticated routes to store the original URL in the session like this:
ensureAuthenticated = function (req, res, next) {
if (req.isAuthenticated()) { return next(); }
// If the user is not authenticated, then we will start the authentication
// process. Before we do, let's store this originally requested URL in the
// session so we know where to return the user later.
req.session.redirectUrl = req.url;
// Resume normal authentication...
logger.info('User is not authenticated.');
req.flash("warn", "You must be logged-in to do that.");
res.redirect('/');
}
Works!
Wherever you have your login button, append the request's current URL as a
query parameter (adjust for whatever templating system you use):
<a href='/auth/google?redirect=<%= req.url %>'>Log In</a>
Then, add middleware to your GET /auth/google handler that stores this value in
req.session:
app.get('/auth/google', function(req, res, next) {
req.session.redirect = req.query.redirect;
next();
}, passport.authenticate('google'));
Finally, in your callback handler, redirect to the URL stored in the session:
app.get('/auth/google/callback', passport.authenticate('google',
failureRedirect: '/'
), function (req, res) {
res.redirect(req.session.redirect || '/');
delete req.session.redirect;
});
Try res.redirect('back'); in the callback for passport.authenticate
According to the author this isn't possible with OpenID strategies. We managed to update these dynamically by directly accessing the variables:
app.get('/auth/google', function(req, res, next) {
passport._strategies['google']._relyingParty.returnUrl = 'http://localhost:3000/test';
passport._strategies['google']._relyingParty.realm = 'http://localhost:3000';
passport.authenticate('google')(req, res, next);
});