Planning a registration/authentication service

I have seen too many articles and tutorials that instead of explaining how to structure an authentication service properly — jump straight to the implementation; framework usages, salt configurations, cryptography, etc.
An easy search will lead you to many posts that describe a MEAN/MERN (NodeJS + express + Mongo + Mongoose + PassportJS + ReactJS/Angular) approach to create a quick implementation of your own “users” [login/registration/authentication] service .
This post is about planning and architecting the registration/authentication service that, from my experience, is generic enough to be reused across different projects.
Though, if you came here looking for implementation guides — you can jump straight to the end of the article and choose a stack and frameworks combination to your taste.
I would like to focus on separating the responsibilities and structuring the solution properly, following SOLID principles and understanding the reasons behind them (As opposed to just following npm -i commands).
If it article seems trivial and straightforward to you — it is great news. This means you are in a good place, so be proud and celebrate with a quiche.
What is our client going to face?
- Registration
- Authentication
- Getting basic user info
- Logout (Token Invalidation)
The user information should be saved in the database. We don’t care about which database at the moment, but on a side note — working with relational databases for years, I always experienced the same issue. Every X months, we need to collect a new kind of information about our user, and the most logical way to put it is in the ‘users’ table. So we kept adding columns until, in one of my previous workplaces, our ‘poor’ users table reached 44 (!!) columns.
Since this article is about decisions and structure — I am not going to suggest any specific technology. But I would suggest going for a non-relational database for storing users’ information.
After we‘ve decided how our client-facing API will look like, let’s take a deeper look at the expected flows.
A user can register & login via our own sign-up/login form or via 3rd parties like Facebook.

So, both registration_api and login_api should be able to support different kinds of calls (3rd party/inhouse).
An in-house request would look familiar:
{
"first_name": "Lev",
"last_name": "Perlman",
"dob": "...",
.......
}Now, let’s take a look at a 3rd party registration call:
{
"type": "facebook",
"token": "$TOKEN$"
}After receiving this request, we will use the token to call the 3rd party from our server, validate the token, and get the user’s info.
Using that info, we would then register the user.
So, what is the common functionality between the two types (in-house, 3rd party) of registration?
— saving the user’s details to our database
What are the differences?
3rd party registration:
- User’s token is validated prior to saving the user to our DB
- User’s info is provided to us by the 3rd party
In-house registration:
- No token process is happening
- User’s info is provided by the user
It is important to mention that some might choose to allow only 3rd party registrations to the system since it is safer. This way, the registration and phone verification are delegated to the 3rd party, and fraudsters are less likely to spend time dealing with those 3rd parties. Fraudsters mostly perform fraud when it’s fast, easy and cost-effective.
Our platform should support both registration options, and we should be able to expose or hide the APIs as we please (manually or through API Gateways).
So, keeping all of the above in mind →
Let’s build our client-facing API:
api ├── index.js # API root - routes, etc. └── v1 ├── info.js # Thin api to get the user's info ├── token_api.js # Get a token └── registration_api.js # Register a user + get a token
Yes, putting a V1 beforehand sets your mindset that one day there might be a V2. Some might say it’s bad practice, but in my opinion it just persists the structure for when v2 appears (and in most projects in the world — it will appear at some point).
Obviously, we are not going to place the model-creation and 3rd-party validations inside our controllers, so let’s create the relevant services so we could easily inject them using DI and mock them during testing:
apiservices └── facebook # facebook-related operations └── index.js └── google # google-related operations └── index.js
For now, each of these services should expose the following method:
* authenticate(authenticationInfo, onSuccess, onFail, onError)Each service will know what to look for in the authenticationInfo variable. We have no interfaces in NodeJS or Ruby, but what I see clearly in front of my eyes is an IAuthenticationHandler interface with FacebookAuthenticator, GoogleAuthenticator, InternalAuthenticator implementors. The correct one should be injected to the controller at runtime based on some decision making process (*coughing* strategy *coughing again*)
Our facebook (Or Google, SoundCloud, MySpace ) services should NOT know how to authenticate and communicate with the actual 3rd parties, so for that purpose let’s create specific clients (which, in turn, will be injected into the services):
apiservicesclients ├── facebook └── index.js ├── google └── index.js
Each client will be aware of the 3rd party authentication and querying style. If something is going to change, the client will be the sole responsible entity to be aware of that change, thus keeping the actual business logic unaware and untouched.
The client would also be the one to read the credentials/tokens/details of the 3rd party — either from a .secrets file, environment variables, or from any other storing option.
A little recap — our services might be accessed by anyone, while the most common consumers will be the registration_api and the token_api.
Token_api will be satisfied by just validating that the user is authenticated, so the only thing left for us to do is to “translate” internal server errors and authentication failure to safe, user-understandable errors like “Wrong password provided” + i18n translation (never simply return database/codebase errors to the user. Never ever!)
Registration_api, on the other hand, might intend to save the user’s information to our internal database. This will be done either straight away, or after a successful token verification.
We already covered the 3rd party authentication handlers, so now let’s proceed to the registration itself.
The common functionality to all registrations scenarios is →
Creation of the user in the database
Let’s extract the user handling into it’s own separate service, called “user_service”:
apiservices├── user_service
└── index.js..........
The responsibility of the user’s service is fairly simple — Create/Update/Get operations on the database.
So, the following methods should be exposed:
* create_user(user_data)
* update_user(user_data)
* get_user(user_id)* user_exists(user_id)
* user_exists_from_3rd_party(external_id, provider_type)
To the consumer, the implementation doesn’t matter. It can be a Mongo database or a MySQL one — the consumer doesn’t care. Should we decide to change the database in the future — that’s not going to be a problem.
The implementation should be pretty straightforward. Creating entities in the desired persistent store and receiving an ID as a result.
After we handled the user’s creation, we are ready to create a token for the user to communicate with any further request to our backend.
In order to do that, let’s get back to our services folder. It’s time to create our own auth_service. This service will receive a UserID and generate an access token for that user. The service will also be responsible for validating an existing token — checking whether the token exists and hasn’t expired yet.
So, the auth_service should contain the following methods:
* generate_token(user_id, expire_in= { duration: 2, type: 'hours' })
* validate_token(token)The first method will create a token for a user (that will be valid for 2 hours by default), while the second one will validate an existing token.
We need to store those tokens somewhere, and remove them once they are expired (or once the user logs out). Best would be an in-memory caching service like Redis. If we were to go down that path, we would also need to create a redis_client in the clients directory, which will contain the following methods:
store_item(key, value)
get_item(key, value)
item_exists(key)
remove_item(key)This redis-client will be reusable by any entity in our system that desires to store items in an in-memory, key-value storage.
Redis is just a specific implementation — so if we were to write in a language that actually supports interfacing — the code above would define the interface, and redis would just be an implementation.
Now that all of that had been dealt with, let’s look at the flow that a request will go through:

Step number 1 is optional, depending on whether the client requests a registration using a 3rd party, or a native-in-house registration (in which case we can skip straight to step 2).
The result of the flow — is a registered user that can perform actions against our API using the token that was returned.
The login flow is fairly similar to the registration flow:
3rd party (i.e facebook) login:

Local (in-house) login:

Once you have all of that designed and planned — you can proceed to actual coding.
I would like to suggest a few specific technology combinations that, in my opinion, can be formed into a pretty generic registration/authentication/users-storage service.
Each of the following paragraphs has it’s own relevant links.
JS:
Dockerising a NodeJS server that uses ExpressJS which exposes a RestfulAPI. The server uses MongoDB to store the data and Mongoose to model and handle the data from the code. The user’s access token can be saved in a Redis service which can easily be hosted on AWS ElastiCache. The passwords themselves can be encrypted using BCrypt. The 3rd party authentication and registration can be handled manually, or by a library like PassportJS. Test your code using Chai, and mock the clients and services using Mocha+Chai+Sinon.
Ruby:
Dockerising a RubyOnRails server that uses either Rails or Grape which exposes a RestfulAPI. The server uses MongoDB to store the data . The user’s access token can be saved in a Redis service which can easily be hosted on AWS ElastiCache. The passwords themselves can be encrypted using BCrypt. The 3rd party authentication and registration can be handled manually, or by a library like AuthLogic + Plugins. Test your code using RSpec, and mock the clients and services using RSpec doubles.
C#:
Dockerising a .NET Core ASP.NET server that uses WebAPI which exposes a RestfulAPI. The server uses MongoDB to store the data. The user’s access token can be saved in a Redis service which can easily be hosted on AWS ElastiCache. The passwords themselves can be encrypted using BCrypt. The 3rd party authentication and registration can be handled manually, or by the built-in ASP handlers. Test your code using NUnit, and mock the clients and services using Moq.
Java:
Dockerising a Java Spring server that exposes a RestfulAPI. The server uses MongoDB to store the data. The user’s access token can be saved in a Redis service which can easily be hosted on AWS ElastiCache. The passwords themselves can be encrypted using BCrypt. The 3rd party authentication and registration can be handled manually, or by Spring Social. Test your code using JUnit, and mock the clients and services using Mockito.
In one of the following posts I am going to write a few suggestions and tips on getting the CI/CD pipeline ready for your new service. But for now, make sure that you write your service properly and test it well. Don’t allow yourself to have a code-coverage below 90%. Set that standard and make sure you follow it by heart. Only then Jesus will save you a spot in heaven.

