HTTP as a stateless protocol won’t keep the client’s state across different requests in your application. A common example for this scenario is the log-in and afterwards remember the user credentials for a defined time interval, like until the browser window gets closed. In case you don’t save the HTTP state across requests, the user needs to sign in again on every page that requires authentication.
This tutorial will show you how to save states for a client across requests and also how to update the associated data or remove the state at all.
hapi Series Overview
- What You’ll Build
- Prepare Your Project: Stack & Structure
- Environment Variables and Storing Secrets
- Set Up MongoDB and Connect With Mongoose
- Sending Emails in Node.js
- Load the User’s Profile Picture From Gravatar Using Virtuals in Mongoose
- Implement a User Profile Editing Screen
- Generate a Username in Mongoose Middleware
- Displaying Seasons and Episodes for TV Shows with Mongoose Relationship Population
- Implementing Pagination for Movies
- Implement a Watchlist
- Create a Full Text Search with MongoDB
- Create a REST API with JSON Endpoints
- Update Mongoose Models for JSON Responses
- API Pagination for TV Shows
- Customize API Endpoints with Query Parameters
- Always Throw and Handle API Validation Errors
- Advanced API Validation With Custom Errors
- Create an API Documentation with Swagger
- Customize Your Swagger API Documentation URL
- Describe Endpoint Details in Your Swagger API Documentation
- 10 Tips on API Testing With Postman
- JWT Authentication in Swagger API Documentation
- API Versioning with Request Headers
- API Login With Username and Password to Generate a JWT
- JWT Authentication and Private API Endpoints
- Refresh Tokens With JWT Authentication
- Create a JWT Utility
- JWT Refresh Token for Multiple Devices
- Check Refresh Token in Authentication Strategy
- Rate Limit Your Refresh Token API Endpoint
- How to Revoke a JWT
- Invalidate JWTs With Blacklists
- JWT Logout (Part 1/2)
- JWT “Immediate” Logout (Part 2/2)
- A Better Place to Invalidate Tokens
- How to Switch the JWT Signing Algorithm
- Roll Your Own Refresh Token Authentication Scheme
- JWT Claims 101
- Use JWT With Asymmetric Signatures (RS256 & Co.)
- Encrypt the JWT Payload (The Simple Way)
- Increase JWT Security Beyond the Signature
- Unsigned JSON Web Tokens (Unsecured JWS)
- JWK and JWKS Overview
- Provide a JWKS API Endpoint
- Create a JWK from a Shared Secret
- JWT Verification via JWKS API Endpoint
- What is JOSE in JWT
- Encrypt a JWT (the JWE Way)
- Authenticate Encrypted JWTs (JWE)
- Encrypted and Signed JWT (Nested JWT)
- Bringing Back JWT Decoding and Authentication
- Bringing Back JWT Claims in the JWT Payload
- Basic Authentication With Username and Password
- Authentication and Remember Me Using Cookies
- How to Set a Default Authentication Strategy
- Define Multiple Authentication Strategies for a Route
- Restrict User Access With Scopes
- Show „Insufficient Scope“ View for Routes With Restricted Access
- Access Restriction With Dynamic and Advanced Scopes
- hapi - How to Fix „unknown authentication strategy“
- Authenticate with GitHub And Remember the Login
- Authenticate with GitLab And Remember the User
- How to Combine Bell With Another Authentication Strategy
- Custom OAuth Bell Strategy to Connect With any Server
- Redirect to Previous Page After Login
- How to Implement a Complete Sign Up Flow With Email and Password
- How to Implement a Complete Login Flow
- Implement a Password-Reset Flow
- Views in hapi 9 (and above)
- How to Render and Reply Views
- How to Reply and Render Pug Views (Using Pug 2.0)
- How to Create a Dynamic Handlebars Layout Template
- Create and Use Handlebars Partial Views
- Create and Use Custom Handlebars Helpers
- Specify a Different Handlebars Layout for a Specific View
- How to Create Jade-Like Layout Blocks in Handlebars
- Use Vue.js Mustache Tags in Handlebars Templates
- How to Use Multiple Handlebars Layouts
- How to Access and Handle Request Payload
- Access Request Headers
- How to Manage Cookies and HTTP States Across Requests
- Detect and Get the Client IP Address
- How to Upload Files
- Quick Access to Logged In User in Route Handlers
- How to Fix “handler method did not return a value, a promise, or throw an error”
- How to Fix “X must return an error, a takeover response, or a continue signal”
- Query Parameter Validation With Joi
- Path Parameter Validation With Joi
- Request Payload Validation With Joi
- Validate Query and Path Parameters, Payload and Headers All at Once on Your Routes
- Validate Request Headers With Joi
- Reply Custom View for Failed Validations
- Handle Failed Validations and Show Errors Details at Inputs
- How to Fix AssertionError, Cannot validate HEAD or GET requests
Prepare a Cookie
HTTP state management in hapi is done using cookies. Cookie management is a two-step process in hapi. At first, you need to tell your server what state should be stored by providing a name and an object of options. To prepare your server for a cookie, use the server.state(name, options)
method that tells the server to create a cookie with name
and the related options
.
The following example illustrates a very basic cookie configuration where you’re going to save a cookie for one day and encode its data as JSON and afterwards Base64.
hapi v17
const Hapi = require('hapi')
// create new server instance
const server = new Hapi.Server({
host: 'localhost',
port: 3000
})
server.state('session', {
ttl: 1000 * 60 * 60 * 24, // 1 day lifetime
encoding: 'base64json' // cookie data is JSON-stringified and Base64 encoded
})
hapi v16
const Hapi = require('hapi')
// create new server instance
const server = new Hapi.Server()
// add server’s connection information
server.connection({
host: 'localhost',
port: 3000
})
server.state('session', {
ttl: 1000 * 60 * 60 * 24, // 1 day lifetime
encoding: 'base64json' // cookie data is JSON-stringified and Base64 encoded
})
Notice that you use the state()
method on your server
object. If you split your application into multiple plugins, you’ve access to your server instance within the plugin itself. Depending on where you want to set your cookie definitions, you can do that individually in your plugins or globally outside the plugins.
There are multiple cookie options that you can use and benefit from. Please refer to the server state options in hapi’s API reference to read the complete list of available settings.
Create a Cookie
Your server is prepared to save a cookie called session
and the cookie has a time to live of one day. To save a state and data to your cookie, you leverage the reply
interface and call the state()
method. Please notice, that the method name is the same for your server preparation and the cookie store functionality. In the end, both methods perform different tasks.
The following code snippet will get ahead on how to read a cookie to get its data. However, the important part is how to set the cookie data. As you can see in the example below, you read the existing cookie value, check if it’s already defined, update the lastVisit
property and finally reply the Hello Future Studio
message with a saved the state within the session
cookie.
hapi v17
server.route({
method: 'GET',
path: '/',
config: {
handler: (request, h) => {
let cookie = request.state.session
if (!cookie) {
cookie = {
username: 'futurestudio',
firstVisit: false
}
}
cookie.lastVisit = Date.now()
return h.response('Hello Future Studio').state('session', cookie)
}
}
})
hapi v16
server.route({
method: 'GET',
path: '/',
config: {
handler: function (request, reply) {
var cookie = request.state.session
if (!cookie) {
cookie = {
username: 'futurestudio',
firstVisit: false
}
}
cookie.lastVisit = Date.now()
reply('Hello Future Studio').state('session', cookie)
}
}
})
Your session
cookie now includes the username
, indicates that the next request is not your first one and the time of your last visit. The lastVisit
property is a custom field and is not used to evaluate the lifetime of the cookie itself.
Create Multiple Cookies
Up to this point, you’ve created a single cookie. There are situations where you want to create another one or multiple at the same time. The functionality is already available and you can just go ahead and chain the .state(name, data)
method on your reply
interface.
hapi v17
// prepare another cookie without encoding
// value must be string if no encoding is set
server.state('email', {
ttl: 1000 * 60 * 60 * 24 * 7 // 7 days lifetime
})
server.route({
method: 'GET',
path: '/',
config: {
handler: (request, reply) => {
let email = request.state.email
let session = request.state.session
if (!email) {
email = 'info@futurestud.io'
}
if (!session) {
session = { username: 'futurestudio', firstvisit: false }
}
cookie.lastVisit = Date.now()
return h.response('Hello Future Studio')
.state('session', session)
.state('email', email)
}
}
})
hapi v16
// prepare another cookie without encoding
// value must be string if no encoding is set
server.state('email', {
ttl: 1000 * 60 * 60 * 24 * 7 // 7 days lifetime
})
server.route({
method: 'GET',
path: '/',
config: {
handler: function (request, reply) {
var email = request.state.email
if (! email) {
email = 'info@futurestud.io'
}
var session = request.state.session
if (! session) {
session = {
username: 'futurestudio',
firstvisit: false
}
}
cookie.lastVisit = Date.now()
reply('Hello Future Studio')
.state('session', session)
.state('email', email)
}
}
})
The first part of this snippet prepares the cookie via the server.state(name, options)
method. Afterwards, you get the current cookie values for session
and email
, update individual fields if necessary on both objects and ultimately reply
the request including both cookies.
Read a Cookie
Within the request.state
object, you’ll find all your cookie data and you can access them using their defined name. Remember the server preparation from above? There you set your cookie name which is now used to access the data.
hapi v17
server.route({
method: 'GET',
path: '/',
config: {
handler: (request, h) {
const cookie = request.state.session
if (cookie) {
// use cookie values
}
return 'Hello Future Studio'
}
}
})
hapi v16
server.route({
method: 'GET',
path: '/',
config: {
handler: function (request, reply) {
var cookie = request.state.session
if (cookie) {
// use cookie values
}
reply('Hello Future Studio')
}
}
})
If you already stored data for your session
cookie, you can use the data to perform further processing.
Update a Cookie Value
To update the cookie data, just go ahead and apply the same method as you would do to create it: reply().state(name, data)
. Because you never know if the cookie data is still existing due to cookie lifetime restrictions, make sure to check whether your cookie data is accessible after requesting it with request.state.cookieName
.
hapi v17
server.route({
method: 'GET',
path: '/',
config: {
handler: (request, h) {
let updatedCookie = request.state.session
if (updatedCookie) {
updatedCookie.lastVisit = Date.now()
}
return h.response('Hello Future Studio').state('session', updatedCookie)
}
}
})
hapi v16
server.route({
method: 'GET',
path: '/',
config: {
handler: function (request, reply) {
var updatedCookie = request.state.session
if (updatedCookie) {
updatedCookie.lastVisit = Date.now()
}
reply('Hello Future Studio').state('session', updatedCookie)
}
}
})
The reply().state(name, data)
method will override your existing data immediately.
Delete a Cookie
If you manually want to delete the cookie’s data, use the reply().unstate(name)
method.
hapi v17
server.route({
method: 'GET',
path: '/',
config: {
handler: (request, reply) => {
return h.response('Hello Future Studio').unstate('session')
}
}
})
hapi v16
server.route({
method: 'GET',
path: '/',
config: {
handler: function (request, reply) {
reply('Hello Future Studio').unstate('session')
}
}
})
This will reset your cookie data and you don’t need to wait for the cookie ttl
to exceed until the actual data gets deleted.
Outlook
The built-in support for HTTP state management in hapi lets you easily store user-related data and access it across multiple requests. Various cookie options allow you to specify cookie lifetime, encoding, and many more.
Do you have any question or just want to leave a message? Use the comments below or find us on Twitter @futurestud_io
Make it rock & enjoy coding!
Additional Resources
- API reference for HTTP state management in hapi, explaining available cookie options
- HTTP State Management, RFC to specify the HTTP Cookie and Set-Cookie header fields
- Encrypt your tokens with the iron utility