An authentication strategy for restful data and application interfaces built with Fastify.
Fastify is a low overhead web framework for node.js. We can not provide definitive benchmarks in terms of performance but have chosen Fastify as our framework (web server & router) of choice for its developer friendliness and extensive plugin ecosystem.
The authentication strategy outlined in this article may probably be applied to other web frameworks which facilitate node middleware (e.g. Express, Hapi, Restify, Koa). Code examples are reducted from the XYZ repository for which this authentication strategy was designed. Please consult the Fastify pages for implementation specific details on decorators, hooks, handler, route schema validation & serialization.
XYZ is an open source spatial data and application interface in active development at GEOLYTIX. Once deployed, the middleware will expose a RESTful API as well as a client library which interacts with the hosted API. A responsive view is returned from the XYZ root endpoint. The script provided with this view expands the capabilities of the XYZ control bundle and wires its API methods to a browser interface.
Authentication strategy, Rules, and Security.
Authentication is a key component for the overall security strategy at GEOLYTIX.
Successful authentication grants to a client roles for access of data available to the XYZ backend. Having 3rd party API keys and PostgreSQL user credentials stored as environment variables the node process acts as proxy to external services and data sources.
Rules define which roles are required for individual endpoints, how these roles can be obtained, and securely transferred from the client to the host.
Starting with the obvious the three cardinal rules for our authentication strategy are:
- A client must be able to securely log in to the system to access data.
- Roles must be available to discern data access and administrative tasks.
- Anonymous access must be provided for public facing interfaces.
Registering, verifying, and approving user accounts.
User accounts are stored in the Access Control List (ACL), a PostgreSQL table.
Registration is the first step to create a new user account. A valid email address must be associated with every new account.
Following a link with a verification token which is sent to the registered email address a user verifies his identity as the owner of the email account.
Once an account has been verified an approval token is sent to all administrators asking them to either approve (or block) the account.
The user can now log on to the system.
Password resets are achieved by repeating the verification process. A new passwords is confirmed after the user confirms the receipt of the verification token.
Failed logins result in an account being locked. Repeating the verification process will unlock the account.
Administrator may assign roles such as the admin role itself to other user accounts. They can also block an account. A blocked account can no longer be verified and will thus restrict the use of the same email address to gain access to the system.
Who ate all the session cookies?
The hyper text protocol being stateless makes secure authentication and session management one of the most daunting features to develop. Even more so with the restrictions of a serverless architecture where subsequent requests maybe routed to different process spawns.
Authentication is ensured through the verification of JWT signatures. Every non-public request to the middleware must provide a signed token as querystring parameter.
The fastify-jwt plugin provides an implementation of the JOSE framework; A series of standards for the encryption and signature of arbitrary data token. The token’s payload contains the email and roles associated with the user account.
Anatomy of a fastify route.
Within a node process, request and response will traverse a series of layers. These layers, the node middleware, are often compared to an onion.
Middleware and hooks allow us to inject a security strategy into the Fastify lifecycle using existing plugins.
Requests for the application view from root route handler must pass through a preValidation hook. At this stage the authToken function is called by the fastify-auth plugin. Public as well as requests with verified token are routed to the root handler which returns an application view to the client.
If not validated a login view will be returned to the client. The submit action of the login form will post the credentials (email / password) to the root route which attaches the root handler to the request object before being validated by the login handler.
The root handler is returned from the login handler once the credentials from the request object are validated against the access control list (ACL). This will return the view from the root handler to the client.
Before returning to the client the root handler will render the signed token as a data-attribute into the document body of the view.
API routes will not return a login view. If not public, missing, or failing to verify a token the request will respond with a 401 error code.
Passing preValidation, the route schema will be validated.
Request parameter are further validated in the preHandler hook to prevent the handler failing, protect against SQL injections, and check role access against a lookup set in the process’ workspace.
The preHandler hook allows to pass the request object through a chain of custom fastify decorators.
Fastify-auth does not provide an authentication strategy. The strategy, defined in the authToken function will be assigned to the fastify-auth module as a decorator.
The request and response object are passed through the authToken method.
At points of failure an Error (401) with the appropriate message is returned as a response to the client.
The request is passed on to the route validation if the token authorization is successful.
preValidation hooks may pass an access object as 4th parameter to authToken. The access parameter defines whether a route should respond with a login view, requires administrative credentials, or is public.
If public, as defined by environment variables, authToken may immediately forward the request. Otherwise fastify-jwt must verify the token signature against an environment secret.
The XYZ host may issue API keys. A single API key may be assigned to a user account in the ACL.
API keys do not expire!
API keys may never be used for administrative tasks.
After verification, authToken will query the ACL for the user account associated with the key. A new token with user roles, but an expiry of 10 seconds only, will be signed and replaces the API key token on the request object.
Let her rip…
With this being said we can finally look at the Fastify startup. Node will execute server.js in which the Fastify process is declared.
A logger is defined, plugins are registered, and decorators for parameter evaluation, login view/handler, as well as authToken are set.
Finally Fastify may listen to requests from the client…
I hope this article may be helpful whether you work with Fastify or develop a security strategy for a different framework. Any comments and especially concerns are welcome on the appropriate channels. Please open an issue in the XYZ github repository with any comments in regard to the authentication strategy.
Please contact firstname.lastname@example.org for any business inquiries.