Building an application that allows users to sign up for your platform always involves authentication and some kind of authorization. You may have dedicated administration areas where „normal“ users shouldn’t have access to.
With hapi you can build on a great feature: scopes. Scopes are part of the authentication flow that allow simple access restrictions in your application.
In this tutorial, you’ll learn how to use hapi’s scope to allow and restrict access for individual routes.
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
Scope Fundamentals: How Do They Work?
Scopes in hapi allow you to apply access restriction on individual routes. It’s a two step procedure where you need to define a scope
property on a route and another scope
property within the authenticated user credentials. Both scope
properties are evaluated against each other.
Let’s imagine the following example: you only want registered users to access the /profile
page on your platform. Therefore, you require the scope: 'user'
on your route. Each user requesting the /profile
page needs to have the scope: 'user'
attribute set within request.auth.credentials
.
Precisely, scopes always require authentication to work properly. There’s a dedicated tutorial for cookie based authentication including remember me available on Future Studio that shows you how to set a user specific object to request.auth.credentials
.
Scopes can be either a single string or an array of string values. Alright, enough theory for now and let’s jump into the actual setup.
Restrict Access on Routes With Scope Requirements
In hapi you’ve dozens of route options to customize the processing to your needs. One option is the access restriction using the scope
property in config.auth
on your route definition. Let’s start with a sample route only accessible for admins:
server.route({
method: 'GET',
path: '/admin',
config: {
auth: {
strategy: 'session',
scope: 'admin'
},
handler: (request, h) => {
return h.view('admin')
}
}
})
Please notice the scope: 'admin'
declaration that allows only users to access this route that have the admin
scope in their authenticated credentials. Users without the admin
scope will receive an error message with HTTP status 403
(Forbidden). This error is generated automatically within hapi. You’ll learn how to customize the error handling within a later tutorial.
The scope
property can be either a string or an array of strings. That means, you can check for a single or multiple scopes.
Let’s say you want a user to have the scope of either admin
or book-buyer
to download a book. Registered users that didn’t buy the book are not allowed to download it, because they just have the user
scope.
Your route setup can look like this:
server.route({
method: 'GET',
path: '/book/{slug}/download',
config: {
auth: {
strategy: 'session',
scope: [ 'admin', 'book-buyer' ]
},
handler: (request, h) => {
return h.view('admin')
}
}
})
If you define an array of scopes like in the snippet above, hapi evaluates them as OR
. The user only needs to have one of the scopes to access this route. In a later tutorial, we’ll look at advanced scope handling. The scope of this tutorial (pun intended) is to get you going with this great feature.
Up to this point, you’ve restricted access to individual routes. Now you need to take care of the user part and assign each user one or many scopes.
Assign Scopes to Users
Required scopes on routes get validated against the user requesting that route. That means, the authenticated credentials need to have a scope
property as well. The following code snippet is a hardcoded user database which we use for illustration.
Both users have the user
scope assigned. The admin
user also has the admin
scope.
// hardcoded users object … just for illustration purposes :)
const usersDB = {
future: {
username: 'future',
password: '12345',
name: 'Future Studio',
scope: [ 'user' ],
id: '1'
},
admin: {
username: 'admin',
password: '1234567890',
name: 'Future Studio Admin',
scope: [ 'admin', 'user' ],
id: '2'
}
}
If one of the two users from the illustrative database sign in to your application, you need to store at least the value for scope
within request.auth.credentials
. Of course you can set additional data, but it’s not affecting the access related scopes.
If you require scopes in your platform, keep a scope
property for each user that signs up containing their individual scopes. Use a list of user related scopes to create advanced scenarios that allow only a segment of your users to access a given resource.
Scope Evaluation in hapi
To determine if a request is allowed to access the resource, hapi evaluates the required scope
defined within config.auth.scope
against the scope
property defined within request.auth.credentials
. Make sure to store the user’s scope into the authenticated request credentials so that both scope
properties can be validated correctly.
Outlook
This tutorial walked you through the details of access restriction in hapi. Define the required scope for a route and evaluate it against the authenticated requests to allow or restrict access to the resource.
Make use of the ability to set an array of scopes for the user to enable advanced scenarios where you only give access to a segment of your user base to a route. Like, only book buyers should be able to see a book’s download page.
Authentication in hapi can cause a common issue and you’ll learn how to avoid that in the next tutorial on how to fix the unknown authentication strategy error in hapi.
We’re happy to hear your thoughts and comments. Please don’t hesitate to use the comments below or find us on Twitter @futurestud_io. Let us know what you think!
Enjoy coding & make it rock!