So, I think we are ready to start the next to the last session of the day.
We have been the whole day talking about passwordless, pseudo, SSH.
But now we are going to switch the topic to something completely different, and we have
here together with us, Carla, she is the main developer for the modern UI in the free
P.A. and she is going to talk about some internal aspects.
Thank you very much.
This is my first time at PhosThem, so please welcome there with me.
So, as you already know, so I am Carla, I am super engineer in Red Hat more specifically,
I am on the free A.P.A. team, and I will talk about how we migrated the web UI from the
current version to the modern one.
So, especially I am going to focus on the multifactor authentication.
Okay, this is the agenda that we will follow in today.
So, I will start with some context, speaking about the free A.P.A.
The web UI and some things that are important to know.
I will provide a very more context on the legacy states, so that means the current web UI.
And I want to make a note, so I will make reference to the current web UI as the
all the web UI, but this is not discontinued yet, okay, but in order to make the difference,
I will refer to the current one as the old one.
Then I will follow up with the modern approach and what we did to modernize the web UI,
and I will end up with the conclusion and what will be working next.
Okay, so let's start with the immigration context.
So, as many of you already know, so free A, it's an integrated identity and authentication solution
that combines a lot of administrative tools and to manage the security of Linux and
Unix network environment.
So, actually there are currently two ways of using free A.P.A.
You can either using the I.B.A. commands directly, or you can use the web interface.
This is the modern, not the modern, the web UI, and this is what we will be talking today.
So, free A, it's quite an old tool.
It was born, I think, 15 years old, and its web UI has been around since 2000 and
you live, and so basically they are like two teenagers.
And yeah, that's why since 2022, there is a network to modernize the current web UI into
modern one, using some changing the new infrastructure and using modern tools.
Because the changes won't be made on the top of the existing web UI.
As we need a fundamental shift, we are implementing a new web UI.
Independent from the 1801, because eventually the modern web UI will replace the current one,
the all the one.
I won't type deep matching to the main reasons for why this change, because this was already
covered in one first and top that we did in 2004, actually it was junior percent in it.
But in a nutshell, so it contains a lot of legacy code that over time became
very difficult to maintain, and this is partly because of the use of the percated libraries
and the framework tools, and this leads to poor accessibility, responsiveness, issue, and
security. And with this reasons, it is important to perform a radical change to adapt the web UI.
And that includes the way we manage the authentication flows and methods.
Okay, so currently there are three ways of types of, there are three ways of
outenticating into the modern web UI, not the modern thing, regular UI.
So you can authenticate either using the user name and password.
Secondly, you can enhance it with two patterns of authentication.
You can authenticate using the curver of tickets and the client certificates.
And the goals for this new adaptation and the challenge that we have ahead of us is to
preserve the multifactor authentication functionality, but modernizing the implementation following
those better practice that we will be mentioning in this talk.
Okay, to understand why we migrated, I would like to provide more context about what we are
migrating from, especially in terms of the authentication methods.
Okay, our legacy goal was built in JavaScript, more specific using the tool kit and
BigQuery. And at the time this was built, it was cutting edge. But remember,
it's already 15 or 60, I don't remember, 16 years old, so it's quite old.
And over the last decade, this architecture revealed some structural weaknesses that made
maintaining complex flows increasingly painful. And we identified five friction points that were
slowing down in terms of how to properly adapt this to a modern solution.
So, we were working with a monolithic architecture. Where logic was tightly coupled with
the manipulation and that forced us into writing imperative methods to create and manage the UI elements.
Also, where application was not centralized, but rather scattering to multiple
width instances. And in the top of that, we were using a massive dependency web to load models,
which was heavy to load and had to optimize. I won't type deep into all these reasons,
but I would like to highlight some, the most relevant ones, because that will be important to
know when we start talking about the, um, some aspects of the modernization of the modern
web UI. Okay, this is what I mean by monolithic. This is a single file, so the login screen
GFs. This is the one that performs the authentication of the users of the all web UI. This is,
of course, a simplification, because the code is very extensive. But, uh, and it is supposed to be a
view, but this is not behaving as a view. Because, as you can see here, it's managing heavy,
and this is not working. It's managing a heavy dependencies. It's all dependencies. It's manually
building the HTML. And this is specifically method that's for some buttons. And it's directly
managing the business logic and the API call. Because everything it's dropped in this single file,
you can't reuse the logic. So you can test it properly, and you can change the UI with a
risking security flow. This is a massive got object that knows too much, and, uh, handles too many
things for being a view. And this is not how a view should behave. Oops, moment.
This is not working. Oops, I jumped away. Okay, so this is another problem,
representing the, in the approach. So how we build the UI elements. Look at the coat of the
left, because, uh, this is juicing, I mean, this is a practical meaning that we have to tell the
browser how to build the interface step by step. So this, for example, this code here,
this is corresponds to the login button from this, um, login screen, uh, file that we mentioned
before. But we're into saying show buttons. So we are telling, we are defining an object in memory.
We are defining the properties. We are finding a specific container and we are explicitly
placing that into this container. And this is exhausting. It's like going to a restaurant,
heading to the kitchen and telling the chef, hey, got the meat, little fire, put the meat,
cook it, wait five minutes, put it the right service. Uh, yeah, that's imperative. So for
example, you know, I mean, in a smaller framework like React, we need to say I want a hamburger.
We define what we want and the framework handles the kitchen work. Wait, easier, right, I mean.
Okay, so finally we have the problem of the head of the, how the pendant sees.
So this corresponds to the same file, the login screen, and this is the top of the file.
So before this file can run before it can even render a single pixel, it demands a lift of 50
different models. So the constructor, the topic, because the login screen, the authentication
library, etc. So this has so many problems because it's difficult to maintain and test.
You have to mock the entire universe and you have to feel out all these requirements in order
to make it work. It's more authentic. The browser has to download and analyze holding models before
doing anything. And it's fragile if any dependency fails. If I don't know, you have a network
leads on a file that you don't even need that moment, the entire login page can crash.
So it's the difference between saying, I just need a hammer to work and saying,
I cannot start working until you bring it entire toolbox, the mechanic and the truck.
Because why not? Okay, so we had a heavy dependence chain. We have imperative code and the
monolithic architecture. That's why I mentioned before that we couldn't just tweak the current solution.
We needed a fundamental shift in how we build the applications so that brings us to our solution.
Okay, so what are the new things care? First of all, the tech is stuck. So we are relying on
react as the main framework using VIT because it provides a lot of optimizations for the browser.
And for the UI elements, we are relying on patternplace 6 currently in the latest version.
As we are using a modern framework, which is react, the way of declaring the components is made in a
declarative way versus the imperative way that we saw before. And apart from the regular states,
a really learning react, redaxe also provide global unified state to manage the login state,
meaning the application will need to know if the current session is authenticated or not.
And for consuming the API commands, we rely on another library called RTK Query.
This allows to have a better separation of concerns between the services and the components.
Yeah, since we are using a TypeScript that also ensures some strict type safety.
Okay, you can see here how different the architecture or the approach is in comparison with the
monolithic view that we saw before. So in terms of the authentication, we have the view,
which is managed by this specific components, the app component and the login main page.
I will explain the difference of them in a second. I said that we are relying in the global
state on this file managed by the authentication slide and the multifactor authentication
endpoint, aka the API calls, that will be managed by the RTK Query library. And this will be the
name of the file. Don't worry, we are not able to remember anything but I will keep mentioning it
and I will remember it for you. Okay, we have the main overview. Let's see how some details have
about how we fix some issues in comparison with the legacy approach.
Okay, this is one of the solutions. On the left, you can see the all-dojo imperative call and
here's the new approach. Notice that we are in calling, construct, place any more obvious,
we change to another framework. But what I mean by that is that we are not manually manipulating
the DOM anymore. Yay. So we are declaring what we want, the login form. So in this case, the UI
is pure reflection of a state. For example, is the authentication value is true. The login
button is disabled and this is handled by React. We don't need to call explicitly or look for
the button ID and call disabled because React handles this work. So we change this imperative code
by a declarative one. And another remarkable change is related to the state management. So in order to
tell the other React components, if there is an existing session and some data, for example,
this one. If the user is logged in, which is a Boolean, the user that has been authenticated
and this error, in case there is any. So we are relying on global statuses and this is tracking
values that I said before. But this is not only helpful to determine if a given session is
or given user has been authenticated. It is also helpful to manage the routes because there will
be protected routes. Sorry, protected pages in this case. And the very important thing is that
we have an initial state with some of the fine values. And we are not updating the manually,
but we are relying on these reducers. Set is login and set is logout. I will explain it in a minute
with an example. But these specific reducers are calling two different scenarios.
When the application initialize and that will be taken by the app component. And via any user
logging by any authentication mechanism and that will be shown by the login main page component.
So I'm not sure if you are more or less familiar with the react, but it follows this hierarchy
way of the find the components. So a component can have a parent and multiple children.
But the parent of all of them, it's the app component, meaning that this is the component,
the first component that renders and initialize when any free application begins.
So that's why this is important because when the modern web UI starts and initializes,
we need to know if there is an existing session. So this operation needs to be done. We need to
know if there is an existing session. And also the way we consume, maybe, I call have
changed. So the API logic is setting what we call the communication layer, aka the service.
And this is where the hooks, um, I mean, the endpoints of all the authentication methods are defined.
In this example, so I only put the user and password logging. So we defined here
the URL and some parameters here. I'm not sure if you can see it in the bottom of the
half of the anyway. So you define this endpoint here. I haven't added it in the code, but
you can export it here and from any other component, you can export it. And in this case, this is being
used in the login button event clicker. So you call it here, you provide some payload and you handle
the response. And after considering that there is no error, you can call this function that we
saw before the global state. And of course, then I will show it in a minute, because if not,
I will repeat so much. Okay, how is this work in the important part? So I have mentioned
in one of the previous slides that this status, the state, um, the global state, um,
modifies, it's simply continuous scenarios when the application initializes and when the user
wants to login. And I would like to describe the first, um, case. And if we have some time,
I will explain the other one. Otherwise, it will be in the slides anyway. So at the very beginning,
when the model and web UI loads, it performs an initial handshake. That means that there is a
batch of API calls that are called. So and it gets like some information about the configuration
environment. And this is the same as it happens in the all web UI. But the, the value that
are interesting is the who am I because if there is an existing session, you will see which
you ID, which user has been authenticated. So if there is a valid session, we will call the
set is login function. Otherwise, we will call the set is logout. If they're depending if there
is some session, some routes will be unlocked or not. So if there is, and this is performed by this
Boolean, if this is true, there is authentication. So the protected pages are shown.
Otherwise, it will redirect to the login page or you can have some access to non protected
pages. Okay, this is basically the same thing that I choose to explain about more details.
I will skip this slide and I will explain, I'm not sure if I have some time, I will need to speed up.
So this is the same thing, but this is the other approach. So what happens if a user is authenticated?
How is this managed? So the process is quite similar. But the main difference is that the user needs
to introduce the user and password. This is only the example for a user and password.
But it works similarly in other authentication methods. So once the user enters the credentials
and clicks login, the component that is performing the work is the login main pitch component.
And it's calling a different hook, which is the one that locks in and gets the user and password.
And there is this the same. If you get a success, the redix store and the status will be changed,
and the page will be reload, and you will be able to see the main page, which is the active user's page.
Okay, this is the demo, but I'm not sure if I will be have to show it. So if you're free to check it in the slides,
they are already available. And oops, I'm not sure. Okay, let's get the conclusion super quick.
So we changed from an imperative way of constructing the elements, the UI elements into a declarative way.
Also the global state, which in the legacy code where distributed in widgets, now we rely on redaxed
to manage them. And the back can, while it was mixed in the view, now it's encapsulated in the hooks,
using the RTK query library, the safety well from JavaScript, the tapescript, from implicit
to strict. And I put here some examples of the bundler that we're using one approach on the other,
and how the integration tests libraries are using each of them.
And this is the last slide, I think. So one thing that we want to improve in the future is that
we would like to optimize the code in terms of good separation of constraints, because when we were
adapting this legacy code, we took some of the same choices to simplify the migration process,
and that included adopting some anti patterns that even though they are not so super critical,
the code could be written in a more readable or structural way, so this would be one goal.
Another goal will be providing the passwordless authentication method to log in into
for IPA, most likely using well too. Since we are currently working on optimizing the integration
test of the modern web UI to run them faster, we will adapt also belonging and the authentication
method to test for them. And of course we would like to add some internationalization,
so belonging, the belonging pitch will be affected by that. I put here some resources and
the fuzz and talk in case you want to locally test the solution and try the other authentication
methods I added here some design docs that are adapted to the main to the modern web UI.
And that's all from my side, any questions?
Any questions from anyone?
Where the granola bars?
Okay, if there is no questions, thank you for coming.