Please checkout our article
that discuss the general thoughts behind agile migration.
Facing the problems of an application migration path
Monolithic applications with their own login have one or more of the following factors in common:
own, per application different kinds, of user authentication
user data is stored in the applications database
users usually have an internal ID, generated by the database (auto-increment)
With this ID further fields from other database tables are linked
This results in the problem that removing the user authentication would entail a complete redesign of the application.
Moreover, many authentication systems demand the migration of user data to their dedicated user repositories, complete
with their unique sets of rights, roles, and profiles. These can be substantially different from the existing database
structures. When transitioning from an internal login system to OAuth, not only does the user handle change, but it also
triggers alterations within the internal structures of the application and impacts all database connections. However,
with Uitsmijter, these challenges can be circumvented. We will now explore a straightforward method for achieving this.
To enable the conversion of monolithic applications, which originally have their own login systems, to process an SSO
handle from external sources, users typically need to be migrated to an external system for most procedures.
Traditionally, this migration process involved several test runs, often conducted during batch processing at night,
especially when the implementation was not yet complete. Subsequently, the old application had to be entirely replaced
by the new implementation in a single significant transition (Big Bang). Unfortunately, this approach frequently
resulted in errors and application failures. However, with Uitsmijter, the transition of an application can be achieved
progressively and while it’s in operation, all without any downtime. We’ll delve into how this works shortly.
Previously, when monolithic applications shifted from custom login systems to OAuth, they had to grapple with the
complexities of OAuth in its entirety. This conversion was—and still is—a monumental task that consumes considerable
time and resources. However, we’re about to unveil how Uitsmijter can simplify this process dramatically, making it an
ideal fit for modern, Sprint-based development methodologies.
When all these issues are combined (and so far, we’ve only discussed user-related concerns), it leads to a prolonged,
perilous, and intricate migration process. Many companies hesitate to undertake this migration, thereby delaying their
transition to a microservices architecture—a change that could significantly boost productivity for the vast majority of
organizations. Uitsmijter offers a solution—a software and accompanying guides that make migration secure, well-planned,
agile, and, most importantly, enjoyable for developers and project owners. Its transparent and intelligent approach
makes it a standout choice for navigating the complexities of this transformation.
Prepare an Interceptor first
The Interceptor mode of Uitsmijter is primarily designed to secure individual web pages behind a login. Many users
employ this mode to protect HTML pages (for instance, within an intranet or a closed client area) or to implement
metering and a paywall to monetize editorial content.
While the Interceptor mode is well-suited for these purposes, it possesses features that can significantly aid us in the
migration process and should not be underestimated in terms of its capabilities.
Internally, Uitsmijter employs the same mechanisms and externally presents two modes: OAuth and Interceptor. Internally,
it is essentially the same, and an application protected through an Interceptor can easily be secured with an OAuth
client. An application whose authentication is protected via the Interceptor mode can be enhanced by OAuth microservices
that also recognize the user (SSO). This is precisely what we aim to achieve during the migration process: initially, we
transition the monolith’s login, allowing us to gradually extract individual parts of the application as microservices.
By following this approach, we can break down the entire migration into manageable units, modernizing the infrastructure
step by step. Moreover, with each iteration, we can continue to work on additional features. No Big Bang, no risks, no
downtime.
As described, the simple Interceptor mode is indeed the ideal initial step to authenticate users through Uitsmijter
before we proceed to externalize specific aspects.
The Demo-Project used for this documentation
To keep things simple, we use an easy ToDo-App to explain what is needed to migrate from a monolith to a microservice
architecture with Uitsmijter.
The following simple database setup is given to demonstrate the migration:
things
Table
Description
failed_jobs
Internal from the 🔗Laravel framework this legacy application is build upon. Queues in Laravel stores failed asynchronous job into the table.
migrations
🔗 Laravel provides database agnostic support for creating and manipulating tables across all supported database systems. The table stores the state of the database.
users
All users are stored in this table along a encrypted password and the email address of the user.
sessions
This application is a legacy monolith with its own user login. The active logins are stored in server sessions and be tracked in this database. 1
password_resets
Laravel provides convenient services for sending password reset links and secure resetting passwords.
todos
For demonstartion purpose the application handels todo actions that are stored in this table. A foreign key to the users table links the user who created the task.
1: This tutorial assumes that your monolithic application runs already on 🔗 Kubernetes. To
run multiple pods of the application be sure that sessions are stored in database or other shared storage and not on the filesystem.
The application itself is very basic and a straight forward. It is a Laravel application written in PHP. You can find
and download the ToDo App on this GitHub Repository.
The application provides a simple login page. After a user successfully logs in, a list of tasks is displayed. Each task
has a description and the user who created this task is shown right beside the task. The authorised user can create new
tasks and mark others as completed.
Admittedly, this is a very simple application, but it has an obvious problem that must be taken into account if the
logout is not going to be part of the application itself in the future: All tasks in the todos table are linked to the
users table. If the user table should be pulled out in the future, the link that is made with a foreign key must be
replaced by something else.
Another problem could be that when this todo-list should also be accessible via a mobile app, the session can’t be
used
when it turns to a stateless server.
Let’s dive into the code and understand how the login is made:
AuthenticatedSessionController.php have three methods:
create: This method is called by a GET request to /login. it displays the login page.
publicfunctioncreate(){returnview('auth.login');}
store: Handle an incoming authentication request. by a POST request from the login form.
First the authenticate() method is called to authenticate the user. The authenticate method in LoginRequest.php
checks the email and the password against the database. If this does not match, an error is returned.
The user session will be generated and at the end the request is redirected to the HOME, the page that shows the task
list.
destroy: Destroy an authenticated session when the user logout.
The TodoController.php is as simple as the login controller:
store: Creates a new ToDo item
update: Updates the provided ToDo task
Configuring Uitsmijter
To migrate this ToDo-Application to be used with a Single-Sign-On with Uitsmijter the first step is to create
a Tenant with enabled interceptor mode.
Create a Tenant:
---apiVersion:"uitsmijter.io/v1"kind:Tenantmetadata:name:Tasksnamespace:todo-applicationspec:hosts:- todo.example.cominterceptor:enabled:truedomain:todo.example.comcookie:.example.comproviders:- | class UserLoginProvider {
auth = false;
constructor(credentials) { this.auth = false; commit(this.auth); }
get canLogin() { return this.auth; }
get userProfile() { return { name: "No User" }; }
get role() { return "user"; }
}- | class UserValidationProvider {
valid = false;
constructor(args) { this.valid = false; commit(this.valid); }
get isValid() { return this.valid; }
}
All details for tenant configuration are bespoken
in Tenant and client configuration. For now, we accept this basic
configuration, because we do not need any further settings.
The most important section in the tenant-yaml is the providers section. In this first configuration this.auth is set
to false, because no user should be able to log in yet.
At first, we want to leave the user data in the application database. To connect the uitsmijter tenant with the database
we have to write the first microservice that acts as a proxy.
Be sure that your database credentials of the ToDo-Application is stored in a configmap or a secret. If the
application is already running on Kubernetes this is mostly the case.
We will use the same configmap or secret to configure the proxy-service to the user database.
At this point there are two options how a user database could be configured:
An Api-Route in the existing Application to verify user credentials
An extra proxy-service to the database table
You should always consider using option two! This is because we want to be able to composite our services more and if we
build the route to our existing legacy application we will never be able to pull things out in the future.
The next chapter describes the proxy service in detail, but first lets configure a uitsmijter tenant.
We do not need a Client for the interceptor mode, yet.
The little ECMA-Scripts in the tenant providers do the following:
http://checkcredentials.todo-application.svc/validate-login is called with username and the
user password. See hashing options on the providers page. this would than look like this:
If the HTTP status code is OK, than the user is logged in.
The returned object is treated as the users profile that includes a role and a userId.
The userId is important and have to be unique for all users. The userId is the main handle to identify the user. In
the case of this demo application it must be the user-id from the database! We do have the flexibility to extend this
later on, but for now the best use case is to stick to the primary key from the legacy database.
There is a problem in UserLoginProvider: the password is not hashed! This is because the ToDo application stores
the passwords in Bcrypt. There is no Bcrypt hashing available in uitsmijter yet (Checkout the roadmap for further
information). But even when Bcrypt is available, we can not hash the password here, because Bcrypt’ed passwords can not
be compared with each other like sha265-hashes. This is because Bcrypt uses a unique salt for each password hash, so
even if two users have the same password, their Bcrypt hashes will be different due to the different salts. This makes
it impossible to compare them directly. Bcrypt also incorporates a work factor, which is a parameter that determines how
computationally intensive the hashing process is. This work factor can be adjusted, and it’s typically set high to make
it time-consuming and resource-intensive for attackers to compute hash values. As a result, hashing the same password
multiple times will yield different results because the salt and work factor are different.
The proxy service must receive the plaintext password and match it with the Bcrypt hash in the database.
For security reasons, make sure that you never expose the proxy service to the outside world. Make sure you have
encrypted internal connections in Kubernetes.
That’s all Uitsmijter needs to work properly and provide a login mechanism for the ToDo application.
But wait… A service called checkcredentials needs to be created to act between Uitsmijter and the Users table.
checkcredentials Proxy Service
In this GitHub-Repository is a very simple example written in TypeScript upon
the Koa-Framework. Let’s walk through the functions:
A Credentials interface accepts the request from the fetch method of the tenant provider.
When this application is deployed to Kubernetes the service is accessible internally
via http://<service>.<namespace>.svc = checkcredentials.todo-application.svc.
Warning
Do not create an ingress to this service. This should be accessible privately inside the cluster, only!
This tutorial assumes that you are familiar
with Kubernetes Deployments and you are able to
deploy an application.
See this repository to get an example you can use.
Changes that need to be made
Back to the PHP ToDo application, the user authentication needs to be changed to parse and decode a JWT instead of
checking the user credentials against the database.
Then we need a JWTAuthProvider which handles the user management by loading the existing user when it exists.
classJWTAuthProviderextendsJWTAuthIlluminate{/**
* @param mixed $id
* @return bool
*/publicfunctionbyId($id){/** @var $user */$user=User::whereEmail($id)->first();if(!$user){returnfalse;}// Log in the user for the request
$this->auth->setUser($user);// User is authorized
returntrue;}}
To further prepare the application for the time when the available users are managed by another micro service completely,
we can extend it to create users on the fly when they don’t exist yet.
For that we extend the previously implemented user check to create a user in the application database
instead of returning false which would abort the request:
if(!$user){/** @var JWTAuth $auth */$auth=app('tymon.jwt.auth');// Load payload data
$payload=$auth->getPayload();// Create user
(newUser(['email'=>$payload->get('sub'),'name'=>$payload->get('profile')['name'],'password'=>'']))->save();// Load created users data
$user=User::whereEmail($id)->first();}
This auth provider can now be configured in config/jwt.php (which can be generated by running
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider")
by overwriting the providers.auth setting:
It needes the JWT_SECRET configured in the environment (which is Uitsmijters jwtSecret shared secret),
for example in the projects .env file.
To support logout, the AuthenticatedSessionController::destroy method must be extended to use the jwt auth guard
and redirect to the Uitsmijter servers /logout endpoint with the right client_id to properly end the session there.
That guard has to be configured in config/auth.php by extending the guards list:
To make the user data rertrieval a bit easier, we should also change the users primary key to its e-mail address
by setting protected $primaryKey = 'email'; and implementing the JWTSubject interface which can be done by adding the following methods:
This has the side-effect that queries which previously implicitly detect the reference keys must be explicitly set to users.id.
In this case that can be done by updating the Todo models user() method:
Last, when the application is deployed using Kubernetes, its Ingress must be extended to manage the user authentication by adding the annotation
traefik.ingress.kubernetes.io/router.middlewares: uitsmijter-forward-auth@kubernetescrd
where uitsmijter is the namespace of Uitsmijter and forward-auth the middleware name to forward the authentication handling.
You have made it
Congratulations! A monolithic application with its own login has been transformed into an OAuth application.
Now you can use the same OAuth sessions for other clients and start pulling out features or attaching new features as microservices.