Within last week’s tutorial, you’ve learned how protect routes from unauthorized access of users leveraging basic authentication with username and password in hapi.
The downside of this approach is that users need to put in their credentials on every request. Even just a site refresh forces them to log in again … and again on the next page … and you see where we’re going.
This guide walks you through the usage of hapi and the hapi-auth-cookie
plugin to implement a remember me functionality with sessions based on cookies.
Before diving into the details, have a look at the series outline and find posts that match your interests and needs.
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
Authentication in Hapi
We’ve already touched the underlying mechanisms for authentication in hapi within the basic authentication guide. Nonetheless, let’s shortly recapture what’s behind the scenes. We’ll dive deeper into the details of authentication later within this hapi series.
As of now, it’s important to know that hapi uses a scheme
and strategy
mechanism for authentication. Picture yourself that schemes
are general types of authentication like basic
or digest
. In reference, a strategy
is the actual reference and named instance of an authentication scheme. This abstract concept is hard to grasp without an example and we’ll do that within a minute using the hapi-auth-cookie
plugin.
Cookie Based Sessions in Hapi Including Remember Me
You already guessed that you don’t need to implement the cookie based session handling on your own. Make use of the existing and well tested hapi-auth-cookie plugin that decorates your hapi server instance with a cookieAuth
object and brings all required functionality to manage sessions for you.
At first, add hapi-auth-cookie
as a dependency to your project and install it. You can combine both steps by executing the following snippet within your command line:
npm i -S hapi-auth-cookie
Having the plugin installed, you need to register it to your hapi server. Afterwards create a new authentication strategy and make it available to the rest of your app.
const Hapi = require('hapi')
// create new server instance
const server = new Hapi.Server()
async function liftOff() {
server.register({
plugin: require('hapi-auth-cookie')
})
server.auth.strategy('session', 'cookie', options) // your TODO: options -> there are required ones
try {
await server.start()
console.log('info', 'Server running at: ' + server.info.uri)
} catch (err) {
console.error(err)
process.exit(1)
}
}
liftOff()
If you aren’t familiar with hapi’s plugin system and feel uncomfortable seeing the code above, follow our tutorial on extending your hapi server’s functionalities with plugins within this hapi series and get a fundamental understanding.
Alright, reviewing the code above you see the registration of hapi-auth-cookie
plugin to your server instance by using server.register(require('hapi-auth-cookie'))
. Internally, the plugin adds a new authentication scheme to your hapi server called cookie
. From now on, you can create a new strategy that uses the newly created cookie
scheme. To create an authentication strategy for your hapi server, leverage the server.auth.strategy(name, scheme, [mode], options)
function that expects three ([optionally four]) parameters:
name
: your strategy name that will be used throughout your appscheme
: the scheme name on which the strategy is based upon (e.g.cookie
)options
: required and optional options
Within the code snippet above, you can see that the cookie
scheme is used to create a new strategy called session
. You could use the same name for strategy and scheme. For this tutorial, it’s a lot easier to keep context when choosing different names so you don’t get confused what’s actually in reference.
The fourth, optional mode
parameter in server.auth.strategy(name, scheme, [mode], options)
is used to indicate whether the new scheme should be the default one and/or be applied to every route that is defined. We’ll have a look at this in the upcoming tutorial. Stay tuned :)
hapi-auth-cookie
— Options
There are multiple options to customize the behavior of hapi-auth-cookie
to your needs. Please find the complete list in the linked GitHub repository. It’s redundant to point out each of the 14 available options, which will just bloat this guide.
Actually, there’s a single required option when creating an authentication strategy based on the cookie
scheme: a cookie password to encode it.
password
: used to encode the cookie by Iron. Requires a length of at least 32 characters.
The other options are optional and follow the principle of convention over configuration by falling back to a default value. However, you can adjust the individual options as you need.
Validate User Credentials
If you’ve read the previous tutorial on basic authentication with hapi, you may wonder if it’s also required to provide a validation function that check the user’s credentials. Actually, the validation function is a valid option that can be used to authenticate and verify the provided data, and also to perform other checks like if the user is still registered on your platform. It’s not a required function for hapi-auth-coookie
to do its work properly.
Route Configuration to Require Authentication
By default, hapi doesn’t require any kind of authentication for specified routes. You have to restrict access with a default or required authentication strategy or individually on a route basis and you’re free to choose from multiple authentication strategies if you’re using more than just cookie based sessions. You’ll learn how to specify multiple authentication strategies for a route within a later tutorial.
Defining authentication for a route requires you to provide a config
object for the route. In a simple setup, you would set the method
, path
and handler
for a route. Now the handler
moves into the route’s config
object. To ultimately set the authentication mechanism(s) for a route, set your desired strategy name as a string value to the auth
field. The following snippet visualizes the description:
server.route({
method: 'GET',
path: '/private-route',
config: {
auth: 'session',
handler: (request, h) => {
return 'Yeah! This message is only available for authenticated users!'
}
}
})
The route’s config
object has the auth
field set to the previously registered authentication strategy name session
. The handler
function within the config
object is located aside the auth
field.
Set a Cookie
You made it through the basics and it’s finally time to set the user’s cookie and remember him for future visits. You’ve to implement a login route that handles the functionality on checking whether the username is actually registered and if yes, do the passwords match. In case everything went smooth and the user authenticated successfully, you can set the individual cookie using the request.cookieAuth.set(object)
method. The cookieAuth
object is available, because the hapit-auth-cookie
plugin decorates the request object.
server.route({
method: 'POST',
path: '/login',
config: {
handler: (request, h) => {
const username = request.payload.username
const password = request.payload.password
// check if user exists in DB
// compare passwords
// if everything went smooth, set the cookie with "user" specific data
request.cookieAuth.set(user);
return 'Wohoo, great to see you'
}
}
})
The .set(object)
method sets the current session data after successful login. The object
can’t be null
and is available in subsequent requests as request.auth.credentials
.
Once you have a valid session object stored, you can also use the .set(key, value)
to set or update the given key
with provided value
. You can’t use the .set(key, value)
method without having set a proper object first.
Check If Cookie Is Set and User Is Authenticated
Hapi’s request.auth
object provides the helper field request.auth.isAuthenticated
that indicates either the user is already authenticated and you can access its session data via request.auth.credentials
or not.
server.route({
method: 'GET',
path: '/some-route',
config: {
auth: {
mode: 'try',
strategy: 'session'
},
handler: (request, h) => {
if (request.auth.isAuthenticated) {
// session data available
const session = request.auth.credentials
return 'Bro, you’re already authenticated :)'
}
// further processing if not authenticated …
}
}
})
Obviously, the user’s session data isn’t available if the user isn’t authenticated correctly. Be careful with your app and check if you receive proper values when requesting the stored session data.
Clear the Cookie — Logout
Of course, there are situations where you want to clear previously saved session data, like log the user out. In those cases, make use of the request.cookieAuth.clear([key])
method. The key
parameter is optional and if not provided, the complete session will be deleted.
server.route({
method: 'GET',
path: '/private-route',
config: {
auth: 'session',
handler: (request, h) => {
// clear the session data
request.cookieAuth.clear()
return 'Logged out. See you around :)'
}
}
})
If you just want to clear a field within your session object, provide the key
parameter for the .clear(key)
method. So for example, if you want to remove the user’s email
from the session data, use request.cookieAuth.clear(email)
to remove it.
Full Context — Complete Code Snippet
Throughout this guide we’ve used only short code snippets to clarify the points of interest. Typically, the user has to be authenticated via web forms which means that views are required and also the functionality to validate and authenticate the provided user credentials. We’ve prepared a sample project that presents a minimal required setup to implement a cookie-based session management. The code is publicly accessible on GitHub. If you’re interested in the details, please head over to GitHub and check out the linked repository.
Outlook
This guide gives you a brief overview on how to implement cookie-based session handling that will allow you to recognize existing users and personalize your application. Keep the existing hapi-auth-cookie
plugin in mind and leverage its functionality to set a personalized cookie that automatically authenticates a user for subsequent requests. If the user logs out of your app, just clear the previously set cookie and erase all traces.
Additionally to the cookie-based authentication, you can benefit from OAuth and use third-party services like GitHub to authenticate users for your platform.
We appreciate feedback and love to help you if there’s a question in your mind. Let us know in the comments or on twitter @futurestud_io.
Make it rock & enjoy coding!
Additional Resources
- hapi-auth-cookie on GitHub
- Encapsulate tokens with encryption
- Our NPM Quick Tips — #2 Use Shortcuts to Install Packages
- Sample code for hapi cookie auth on GitHub
- Our tutorial on basic authentication in hapi