Documentation: Sessions
Sessions
A web application needs the ability to identify users as they browse from page
to page. This series of requests and responses, each associated with the same
user, is known as a session.
HTTP is a stateless protocol, meaning that each request to an application can be
understood in isolation – without any context from previous requests. This
poses a challenge for web applications with logged in users, as the
authenticated user needs to be remembered across subsequent requests as they
navigate the application.
To solve this challenge, web applications make use of sessions, which allow
state to be maintained between the application server and the user’s browser. A
session is established by setting an HTTP cookie
in the browser, which the browser then transmits to the server on every request.
The server uses the value of the cookie to retrieve information it needs across
multiple requests. In effect, this creates a stateful protocol on top of HTTP.
While sessions are used to maintain authentication state, they can also be used
by applications to maintain other state unrelated to authentication. Passport
is carefully designed to isolate authentication state, referred to as a login
session, from other state that may be stored in the session.
Applications must initialize session support in order to make use of login
sessions. In an Express app, session support is added
by using express-session
middleware.
var
session = require
('express-session'
);
app.use
(session
({
secret
: 'keyboard cat'
,
resave
: false
,
saveUninitialized
: false
,
cookie
: { secure
: true
}
}));
To maintain a login session, Passport serializes and deserializes user
information to and from the session. The information that is stored is
determined by the application, which supplies a serializeUser
and a
deserializeUser
function.
passport.serializeUser
(function
(user, cb
) {
process.nextTick
(function
() {
return
cb
(null
, {
id
: user.id
,
username
: user.username
,
picture
: user.picture
});
});
});
passport.deserializeUser
(function
(user, cb
) {
process.nextTick
(function
() {
return
cb
(null
, user);
});
});
A login session is established upon a user successfully authenticating using a
credential. The following route will authenticate a user using a username and
password. If successfully verified, Passport will call the serializeUser
function, which in the above example is storing the user’s ID, username, and
picture. Any other properties of the user, such as an address or birthday, are
not stored.
app.post
('/login/password'
,
passport.authenticate
('local'
, { failureRedirect
: '/login'
, failureMessage
: true
}),
function
(req, res
) {
res.redirect
('/~'
+ req.user
.username
);
});
As the user navigates from page to page, the session itself can be authenticated
using the built-in session
strategy. Because an authenticated session is
typically needed for the majority of routes in an application, it is common to
use this as application-level middleware,
after session
middleware.
app.use
(session
( );
app.use
(passport.authenticate
('session'
));
This can also be accomplished, more succinctly, using the passport.session()
alias.
app.use
(session
( );
app.use
(passport.session
());
When the session is authenticated, Passport will call the deserializeUser
function, which in the above example is yielding the previously stored user ID,
username, and picture. The req.user
property is then set to the yielded
information.
There is an inherent tradeoff between the amount of data stored in a session and
database load incurred when authenticating a session. This tradeoff is
particularly pertinent when session data is stored on the client, rather than
the server, using a package such as cookie-session
.
Storing less data in the session will require heavier queries to a database to
obtain that information. Conversely, storing more data in the session reduces
database queries while potentially exceeding the maximum amount of data that can
be stored in a cookie.
This tradeoff is controlled by the application and the serializeUser
and
deserializeUser
functions it supplies. In contrast to the above example, the
following example minimizes the data stored in the session at the expense of
querying the database for every request in which the session is authenticated.
passport.serializeUser
(function
(user, cb
) {
process.nextTick
(function
() {
return
cb
(null
, user.id
);
});
});
passport.deserializeUser
(function
(id, cb
) {
db.get
('SELECT * FROM users WHERE id = ?'
, [ id ], function
(err, user
) {
if
(err) { return
cb
(err); }
return
cb
(null
, user);
});
});
To balance this tradeoff, it is recommended that any user information needed on
every request to the application be stored in the session. For example, if
the application displays a user element containing the user’s name, email
address, and photo on every page, that information should be stored in the
session to eliminate what would otherwise be frequent database queries.
Specific routes, such as a checkout page, that need additional information such
as a shipping address, can query the database for that data.