The Backend - FeathersJS
This article focuses on the backend, which leverages theFeathersJS framework and several complementary libraries in the FeatherJS Ecosystem:feathers-authentication-management andfeathers-permissions.
Getting Started With FeatherJS
Getting started with FeathersJS is pretty easy. There's a CLI that generates an application based on several configurable options.
Note: I would advise against copying/pasting any code snippets and instead go directly to the repository to view the code. Due to the amount of code involved, I've omitted lines of code in this article for brevity. As this is not written as a tutorial, please view the repository if you want to create a similar project.
FeathersJS Overview
Feathers has a greatgetting started guide, so I'd highly recommend reviewing their guide for a more in-depth overview. I'll highlight a few features that are customized for this starter.
Configuration
With Feathers,configuration is fairly straightforward. It takes theNODE_ENV
environment variable to determine which configuration to use. For example, ifNODE_ENV=prod
, then it will merge thedefault.json
withprod.json
configuration settings. We'll add some settings to the configuration file to ensure services have the necessary values to run properly.
On Login
I wanted to store a timestamp for when a user logs in, so I used theapp.on('login'...) connection event.
app.on('login',(data)=>{data.user['lastLoggedIn']=newDate();app.service('users').patch(data.user._id,data.user);});
Feathers Services
Feathers services can be generated using the command line generator withfeathers generate service
. This will begin a prompt sequence that configures the service to your needs.Services in feathers consist of a class, hooks, and a service definition.
Feathers Mailer
To send emails, the server uses thefeathers-mailer library, which is a wrapper fornodemailer. For this starter, I configured it forAWS SES, but you can use anysupported transport. To configure for AWS, the following configuration keys will be needed from your AWS account:
{"smtp_user":"aws_smtp_user","smtp_pw":"aws_smtp_pw","smtp_host":"aws_smtp_host"}
You can add these to the${env}.json
configuration file ordefault.json
configuration file.
I created a custom service using the feathers cli and configured it for the AWS SES Transport. Theemail service shows how this is set up.
module.exports=function(app){app.use('/email',Mailer(smtpTransport({host:app.get('smtp_host'),secure:true,auth:{user:app.get('smtp_user'),pass:app.get('smtp_pw'),},})));};
Once the email service is configured, it can be used to verify emails on sign up with the feathers-authentication-management library.
Coming Soon: I'll be writing a separate article about creating beautiful templated emails to send out using this service.
Feathers Authentication Management
Feathers Authentication Management is a library that enables several useful features during the user signup process:
- email verification
- password reset
- update password
- update to new email with verification
To add it to the user workflow, I created anauth-management service.
constauthManagement=require('feathers-authentication-management');consthooks=require('./auth-management.hooks');constnotifier=require('./notifier');module.exports=function(app){app.configure(authManagement(notifier(app)));// Get our initialized service so that we can register hooksconstservice=app.service('authManagement');service.hooks(hooks);};
Thenotifier processes the incoming request and handles the case accordingly based on theaction
received from the request. TheresendVerifySignup
case will resend the verification email to the user.
functionsendEmail(email){returnapp.service('email').create(email).catch((err)=>{console.log('Error sending email',err);});}switch(type){case'resendVerifySignup'://sending the user the verification emailtokenLink=getLink('verify',user.verifyToken);email={from:FROM_EMAIL,to:user.email,subject:'Verify Email',html:tokenLink,};returnsendEmail(email);}
To ensure this service has all the necessary information to generate the correct email, the following configuration keys were also added to the${env}.json
file.
{"from_email":"no-reply@test.com","client_url":"http://localhost:8080","api_url":"http://localhost:3030/"}
Hooks are used to update the user record before and after various actions.
module.exports={before:{create:[// after user is created, add verification fields to user recordverifyHooks.addVerification(),],patch:[authenticate('jwt'),iff(// if request is from external providerisProvider('external'),// do not allow the following fields to be updatingpreventChanges(true,'email','isVerified','verifyToken','verifyShortToken','verifyExpires','verifyChanges','resetToken','resetShortToken','resetExpires'),),],// don't allow external requests to delete the userremove:[disallow('external')],},after:{all:[// prevent leak of these user information fieldsprotect('password','verifyToken','updatedAt','createdAt','verifyShortToken','verifyExpires','resetToken','resetExpires','verifyChanges','__v'),],create:[// after a user is created, send the user an email to verify email(context)=>{accountService(context.app).notifier('resendVerifySignup',context.data);},// remove the user verification fields before returning user as part of requestverifyHooks.removeVerification(),],},};
After a user is created, the verification fields are added to the user (and later removed before being return as part of a request). For security purposes, fields also should not be directly updated by external requests. After a user is created, another hook sends the user a verification email before removing the verification fields from the user.
Feathers Permissions
Finally, the backend implements the role concept usingfeathers-permissions, using a manually created admin account. Accounts default to a 'guest' role defined in the mongoose model (covered in the next article).
The admin role can update users into other roles. This starter defines 4 roles: 'guest' (default role), 'user', 'admin', 'inactive'. If an admin deactivates a user, it will update their role to 'inactive'.
Hooks are used to control and limit access to specific admin functions, such as updating a user role.
iff(checkPermissions({roles:['super_admin','admin'],field:'permissions',error:false,}),validate.mongoose(adminUpdateSchema,joiOptions)),iff((context)=>!context.params.permitted,[// ensure user only updates their own recordsetField({from:'params.user._id',as:'params.query._id',}),validate.mongoose(updateSchema,joiOptions),]),
The hooks above check if the user is an admin, and if so, check the data against the approved admin schema (which allows more fields to be updated). If the user isn't an admin, make sure the user only updates their own record with the approved user schema.
The permissions can be refined even further. Check thedocumentation for details. I kept it simple for this starter.
Additional Resources
Special shoutout to these authors who wrote tutorials for setting up email verification.
Did I miss anything?
This wraps up the backend code. Let me know if you have any questions, comments or suggestions. In the next article, I'll be reviewing the MongoDB setup!
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse