You know, we’re aficionados of the hapi framework for Node.js and surely rely on multiple plugins and dependencies.
Honestly, we benefit a lot from the great Node.js and especially hapi ecosystem and all its plugins that do one job very well. That’s the reason we’ve decided to finally give back and open source the first piece of our Future Studio platform as a hapi plugin: hapi-geo-locate
, a plugin providing geo location based on the request’s IP address.
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
- Extend Your Server Functionality With Plugins
- Create Your Own Custom Plugin
- How to Register Plugins for a Selected Server Instance
- hapi Plugin for Client Geo Location (by Future Studio 🚀)
- Increase Development Speed With Errors in the Browser or as JSON Response
- 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
What Do You Need Geo Location For?
Future Studio is based in Germany and with the start of the University we need to apply appropriate tax rates as part of the billing procedure. Within the EU, each country has its own VAT rate that gets applied to digital services. To be VAT compliant, we need to make sure the correct rate is applied. To do that, we locate the location of the incoming request using the client’s IP address and further ask you to select your country from a list within the frontend.
Because we didn’t find any existing functionality that we could rely on, we implemented a hapi plugin to provide IP geo location by IP address.
Plugin Functionality: Determine Geo Location by IP Address
To determine your location, we use the service ipinfo.io. Precisely, we use a Node.js wrapper for the ipinfo.io API and pass the client’s IP address to determine their location, not the location of our server. Because if you request the API of ipinfo.io without a valid IP address as an argument, you’ll receive your own location in response.
Decorate the Request: request.location
The plugin decorates hapi’s request
interface with a location
object that contains the location, if determined correctly. In situations where you disable the plugin (see next section „Options“), the location object is undefined
.
Options
The current state of the plugin is an initial implementation with a reduced option set. At the time of writing this announcement, there’s just a single option to disable the plugin for every request or on a route level.
In more detail: the plugin is enabled by default and for each incoming request, the location will be determined which takes time and resources. The actual request processing is blocked until the response from ipinfo.io is received to further proceed with all the other steps applied within the route handler. That’s the reason you can disable hapi-geo-locate
globally and enable the plugin if needed for individual routes.
Dependencies
As already described, the hapi-geo-locate
plugin relies on a Node.js wrapper for the API of ipinfo.io.
That’s the only dependency we use. We could’ve done the request to ipinfo.io ourselves using an HTTP library like wreck, axios or request, but that would require the implementation of additional functionality around the ipinfo.io API. And we don’t want to invent the development wheel again, just for the sake of spending time on the implementation :) The used dependency provides all required functionality and allows us to add more features on top.
Problem: Get the Client IP Address for Proxied Requests
Using hapi, you can access the client’s IP address using the request object: request.info.remoteAddress
. While testing the implementation locally for the actual geo locating procedure, everything went fine.
The Future Studio platform leverages nginx as a reverse proxy to pass the requests to the individual application servers. Locally, we don’t rely on nginx and just start the server using node
directly.
At this point, we just used the hapi functionality to determine the client IP address within hapi-geo-locate
and didn’t think about the pass through nginx. Once we did the deployment, each client IP was just the 127.0.0.1
loopback. What’s wrong?
Each request that went through nginx to the application server (hapi) contains the IP of your server that runs nginx. In our case, nginx and the Future Studio platform are on the same server.
That means, we need to consider forwarded HTTP headers from nginx to fetch the client’s IP address from there. Ultimately, we prefer proxied headers over the IP address provided by hapi:
const ip = request.headers[ 'x-forwarded-for' ] || request.info.remoteAddress;
For now, hapi-geo-locate
only supports the x-forwarded-for
header of nginx. The following list of feature ideas contains the plan to support further proxies besides nginx.
Feature Ideas
The initial plugin state does a solid job of locating the client’s location with the help of its IP address. To support various server setups and use cases, there are multiple ideas that should be added.
Ideas
- support additional proxies (proxied headers) besides nginx
- allow IP location services other than ipinfo.io
- allow API credentials for ipinfo.io
While adding features, we’ll surely come up with additional ideas :)
Do You Want to Contribute?
Do you miss a feature or found an error? Please don’t hesitate to create an issue or just go ahead and fork the repository, add your desired functionality and open a pull request. We’re happy about any community contribution.
Thank you!
Questions?
We’re in the hype of publishing our first hapi plugin and may not have the 10,000 feet view as we should have. In case there’s anything unclear or you’ve a question about the plugin, its implementation or anything else: please reach out on Twitter @futurestud_io or leave a comment below.
Make it rock & enjoy coding!