Skip to content

Commit

Permalink
Merge pull request #7 from aolle/refactor
Browse files Browse the repository at this point in the history
Rewrite
  • Loading branch information
dbgjerez authored Jan 24, 2024
2 parents d5dfcd7 + befe458 commit 39f5153
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 78 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Red Hat Build of Keycloak Workshop
---
[Àngel Ollé Blázquez](https://www.linkedin.com/in/angelolle/)

[David Borrego Gutiérrez](https://www.linkedin.com/in/david-borrego-guti%C3%A9rrez-868a2466/)
140 changes: 63 additions & 77 deletions documentation/modules/ROOT/pages/openid-jwt.adoc
Original file line number Diff line number Diff line change
@@ -1,57 +1,58 @@
= OpenID + JWT
include::_attributes.adoc[]

This tutorial shows how to implement an OpenID Connect flow with a JWT implementation.
This tutorial demonstrates how to implement an OpenID Connect flow with a JWT implementation.

We will configure a new Red Hat Build for Keycloak (RHBK) realm and configure it. After that, we will still deploy the stack and configure the user's access to allow roles for different operations.
We will set up a new realm in Red Hat Build for Keycloak (RHBK) and configure it. Subsequently, we will deploy the stack and configure user access, enabling roles for various operations.

[#architecture]
== Architecture
Firstly, we need a RHBK instance. This instance controls the access across our applications. An advantage of OpenID Connect with JWT implementation is the easy way to connect different applications and frameworks, as each has libraries to facilitate that.

In this case, we'll develop a complete architecture with some microservices. We have one frontend, developed in ReactJS, which shows the data.
Firstly, we require an instance of RHBK to manage access across our applications. One advantage of implementing OpenID Connect with JWT is the seamless integration of various applications and frameworks, facilitated by libraries available for each.

The user backend microservice uses Quarkus, and we can see an easy integration between Keycloak and the application, thanks to its OpenID library.
In this instance, we will construct a comprehensive architecture involving several microservices. The frontend is developed in ReactJS, responsible for displaying the data.

The user backend microservice, built with Quarkus, showcases a straightforward integration between Keycloak and the application, thanks to its OpenID library.

image::openid/frontend-architecture-01.png[]

[#rhbk]
== RHBK configuration
This section explains how to create a new realm with OpenID connect flow and how to configure it.

=== Create a realm
This section outlines the process of creating a new realm using the OpenID Connect flow and provides guidance on its configuration.

A realm is a domain in RHBK where we can configure clients, users, roles, etc. In this example, we provide an import with the configuration to run this workshop.
=== Create a realm

However, we're creating one to see all the processes:
A realm in RHBK functions as a domain where clients, users, roles, etc., can be configured. In this example, we offer an import containing the configuration to execute this workshop. Nevertheless, we are creating one from scratch to illustrate all the processes:

image::openid/rhbk-create-realm-01.png[]

=== Create a client

A client is a RHBK consumer. We're using an OpenID Connect flow, so depending on whether we are configuring a private or public client, we must select a standard or implicit flow.
A client in RHBK serves as a consumer. Since we are employing an OpenID Connect flow, the selection of a standard or implicit flow depends on whether we are configuring a private or public client.

To register a new client, go to the Keycloak console admin into the client options. In this section, find the option called "Create client".
To register a new client, navigate to the Keycloak admin console and access the client options. In this section, locate the "Create client" option.

image::openid/rhbk-create-client-01.png[]

The following step depends on the flow that we can. In this workshop, we will use the standard flow for backend applications and the implicit flow for the frontend application.
The next step depends on the flow we choose. In this workshop, we will opt for the standard flow for backend applications and the implicit flow for the frontend application.

image::openid/rhbk-create-client-02.png[]

The last step indicates a few security configuration parameters such as web origins, redirect URL after a correct login, etc.
The final step involves configuring security parameters, including web origins, redirect URLs after a successful login, etc.

image::openid/rhbk-create-client-03.png[]

[#backend]
== Backend

Now, we will configure and secure a backend service. In this case, we are using Quarkus to develop the application.
Now, let's proceed to configure and secure a backend service. In this case, we are using Quarkus to develop the application.

The application is a backend service to store a list of users of a sports centre.
The application serves as a backend service designed to store a list of users for a sports center.

=== Find the backend URL
The front-end application consumes a back-end application. We need to know this URL:

The front-end application consumes the back-end application, and it is crucial to be aware of the following URL:

[.lines_space]
[.console-input]
Expand All @@ -62,19 +63,17 @@ oc get route -A | grep ms-users | awk '{print $3}'

=== RHBK configuration

The Keycloak instance contains a configured realm with a specific client for this workshop.
The Keycloak instance includes a realm that has been configured with a dedicated client tailored for this workshop.

This application is a backend service that uses a standard flow with a private client. In the following section, you can see all the configurations needed.
This application functions as a backend service utilizing a standard flow with a private client. In the upcoming section, you will find all the necessary configurations.

If you wish to see the OpenID Connect client configuration and explore the RHBK admin console, you can navigate along the following section.
If you want to review the OpenID Connect client configuration and explore the RHBK admin console, please navigate to the following section.

image::openid/backend-rhbk-config-01.png[]

=== Quarkus configuration

Quarkus simplifies the security configuration in our backend services.

We need to import the corresponding library. In this case, we're working with RHBK, but indeed, it's an OpenID connect protocol, so we'll use the ```quarkus-oidc``` generic library.
Quarkus streamlines the security configuration in our backend services. To achieve this, we need to import the corresponding library. In this case, even though we are working with RHBK, since it follows the OpenID Connect protocol, we will utilize the `quarkus-oidc` generic library.

[.lines_space]
[.console-input]
Expand All @@ -86,7 +85,7 @@ We need to import the corresponding library. In this case, we're working with RH
</dependency>
----

At this point, we only have to configure some properties to indicate where and how to use the OIDC server:
At this stage, we only need to configure some properties to specify where and how to use the OIDC server:

[.lines_space]
[.console-input]
Expand All @@ -99,30 +98,30 @@ quarkus.oidc.credentials.secret=0unvbdWCBWcFMQUyVukqA6TQuHHWjj9x

[WARNING,subs="attributes+,+macros"]
====
The client secret is the password that uses the client application. It's critical to provision it securely.
The client secret is the password used by the client application, and it's crucial to handle it securely.
====

Now, we can secure our backend application endpoints.
With the configurations in place, we can now proceed to secure the endpoints of our backend application.

=== Secure the application

Another advantage of using Quarkus is the facility of use. We can secure the application endpoint just with annotations.
Another advantage of using Quarkus is its user-friendly approach. Securing the application endpoints is simplified using annotations.

To secure the applications, we have two options: generic or in a fine-grain way. The main difference between both is to indicate the user roles using ```@RolesAllowed({ })``` or not with ```@Authenticated``` in the application controllers.
To secure applications, we have two options: a generic approach or a fine-grained one. The primary distinction lies in specifying user roles using `@RolesAllowed({ })` or not using `@Authenticated` in the application controllers.

The client's authentication, authorization and roles are managed automatically by Quarkus.
Quarkus automatically handles client authentication, authorization, and roles management.

=== CRUD operations

CRUD represents all the basic REST operations you can do over a domain: create, read, update and delete.
CRUD represents all the fundamental REST operations performed on a domain: create, read, update, and delete.

Depending on the operation, we need more security or not. In this case, we will see the securitization for the main options.
The level of security required varies depending on the operation. In this case, we will explore the security measures for each of the main options.

==== How to get an access token

We will use the curl bash client to test the rest of the application endpoints. If you prefer something more visual, you can avoid doing this section and jump directly to the front-end application section.
We will employ the `curl` bash client to test the remaining endpoints of the application. If you prefer a more visual approach, you can skip this section and proceed directly to the front-end application section.

To get an access token, we need to configure some parameters that RHBK ask for.
To obtain an access token, we need to configure certain parameters that RHBK requires.

[NOTE,subs="bash,+macros"]
====
Expand All @@ -139,7 +138,7 @@ PASSWORD=dborrego
----
====

In each petition, you have to put the access token. You can request for one the following way:
In each request, it's necessary to include the access token. You can request one in the following manner:

[.lines_space]
[.console-input]
Expand All @@ -156,9 +155,9 @@ ACCESS_TOKEN=`curl \

==== Create

Creating a new element into the domain is a restricted operation that just should be done by an admin. We have a specific role for admins: ```padel-users-admin```
Creating a new element in the domain is a restricted operation that should only be performed by an admin. We have a dedicated role for admins: `padel-users-admin`.

The application has to avoid the connection and return 403 to any user without enough permission:
The application must prevent the connection and return a 403 error to any user without sufficient permissions:

[.lines_space]
[.console-input]
Expand All @@ -171,7 +170,7 @@ The application has to avoid the connection and return 403 to any user without e
}
----

To test the creation of a user, you can use the following command:
To test the creation of a user, you can use the following command:

[.lines_space]
[.console-input]
Expand All @@ -188,13 +187,13 @@ curl \

==== Find

We allow any user to find himself, so we need some filters.
We permit any user to retrieve their own information, so we require some filters.

The first control we need is to check if the user is logged in with the correct role to enter the application.
The first control is to verify if the user is logged in with the correct role to access the application.

The second one is that the application checks it's just trying to get its user. Nobody can retrieve another user who is not himself. A good example is a bank application where you can get your account information, but not another one.
The second control ensures that the application confirms it is only attempting to retrieve its own user information. No user should be able to fetch another user's data. A good analogy is a bank application where you can access your account information but not that of another account.

The last check involves getting information from the token and taking it to the database query.
The final check involves extracting information from the token and incorporating it into the database query.

[.lines_space]
[.console-input]
Expand All @@ -218,7 +217,7 @@ The last check involves getting information from the token and taking it to the
}
----

Now, you can try to get your user:
Now, you can attempt to retrieve your user information:

[.lines_space]
[.console-input]
Expand All @@ -233,7 +232,7 @@ curl \
[#frontend]
== Frontend

In this section, we want to create a new record in our system, but we must configure some security points.
In this section, we aim to create a new record in our system, but we need to configure some security measures.

=== Find the front-end application URL

Expand All @@ -244,64 +243,57 @@ In this section, we want to create a new record in our system, but we must confi
oc get route -A | grep frontend | awk '{print $3}'
----

When you enter into the application, you should see a message like this:
Upon entering the application, you should see a message similar to this:

image::openid/frontend-02.png[]

This message indicates that you have not logged into the application. So we have to log in. We need a user with enough roles to enter into the platform.

=== Create the user

The first step for us is to create a new user to show all the configuration step by step.

So, enter the RHBK admin console, go to the "users" section and press the "add user" button.
The initial step is to create a new user to demonstrate the entire configuration step by step. To do this, enter the RHBK admin console, navigate to the "users" section, and click the "add user" button.

image::openid/frontend-create-user-01.png[]

In the following screen, we can select different properties about our new user. We are just going to set the name.
In the subsequent screen, you can choose various properties for your new user. For now, we will only set the name.

image::openid/frontend-create-user-02.png[]

Once the user is created, we have to set a
temporal or definitive credential to enter.

To do it, we go to the "Credentials" tab and press the "Set password" button.
After creating the user, the next step is to set a temporary or permanent credential for access. To accomplish this, navigate to the "Credentials" tab and click the "Set password" button.

[NOTE,subs="attributes+,+macros"]
====
For this example, you can use whatever password you want, but I recommend using an easy password as "reader".
For this example, you can use any password you prefer, but I recommend using a simple password like "reader".
====

image::openid/frontend-create-user-03.png[]

=== Login into the frontend application

Now, we have our new user, so we can return to the frontend URL and try to log in to the application.

Click the green button and the application will redirect you to the Keycloak login page.
Now that we have our new user, let's return to the frontend URL and attempt to log in to the application. Click the green button, and the application will redirect you to the Keycloak login page.

image::openid/frontend-login-01.png[]

Once you log in, you will be redirected to the main page, already signed in.

Now you should see the following button:
Now, you should see the following button:

image::openid/frontend-login-02.png[]

It means you are correctly logged in to the application. You can't see anything thanks to the ReactJS configuration.
This indicates that you have successfully logged into the application. You may not see anything due to the ReactJS configuration.

=== ReactJS configuration

[IMPORTANT,subs="attributes+,+macros"]
====
This section shows Javascript code to show how easy it is to configure a ReactJS application.
This section presents JavaScript code to illustrate how straightforward it is to configure a ReactJS application.
Keycloak gives us a library to integrate into any popular frontend framework to work easily. In this case, I've used ```"keycloak-js": "^22.0.1",```
Keycloak provides a library that can be easily integrated into any popular frontend framework. In this case, I've used `"keycloak-js": "^22.0.1"`.
====

We can see the code and inspect it. The variables ```isAdmin``` and ```isUser``` control the roles of the users.
We can examine and inspect the code. The variables `isAdmin` and `isUser` are responsible for controlling the user roles.

The Menu component renders the application menu depending on the roles. So, if the user is an admin, it'll be able to see the "users" option, and if it is a player, it can see the matches tab.
The Menu component renders the application menu based on these roles. Therefore, if the user is an admin, they will see the "users" option, and if they are a player, they can view the matches tab.

[.lines_space]
[.console-input]
Expand Down Expand Up @@ -334,47 +326,41 @@ The Menu component renders the application menu depending on the roles. So, if t
}
----

As we're working with the SPA page, the user may know the path to access, for example, the admin user's page. The Keycloak library controls this option too:
Since we are working with a Single Page Application (SPA), users may be aware of the paths to access specific pages, such as the admin user's page. The Keycloak library also manages this aspect:

image::openid/frontend-login-03.png[]

=== Add roles to the user

At this point, we know how the ReactJS security model depends on the user roles in the JWT token.

Now, we're going to add roles to the users to be able to do private actions.
At this point, we understand how the ReactJS security model relies on the user roles present in the JWT token.

The first step is to return to the RHBK admin page and find the user ```reader```.

Once we have located the user, we find the "Role mapping" tab.
Now, let's proceed to add roles to users to enable private actions. The initial step is to return to the RHBK admin page and locate the user named `reader`. Once you have identified the user, navigate to the "Role mapping" tab.

image::openid/frontend-add-role-01.png[]

After clicking the "Assign role" button, RHBK shows a list of all available roles in the system.
After clicking the "Assign role" button, RHBK will display a list of all available roles in the system.

Select the role "padel-player" and click the "Assign" button.

image::openid/frontend-add-role-02.png[]

Now, you can return to the front page. It's mandatory to log out and log in to refresh the JWT token.
Now, you can return to the front page. It's necessary to log out and log back in to refresh the JWT token.

After doing it, your user has the role assigned, and the front shows him the "Matches" tab.
After doing so, your user will have the assigned role, and the front end will display the "Matches" tab.

image::openid/frontend-add-role-03.png[]

=== Add role admin to manage users

Now, you should repeat the process to add a role to the user ```reader```. Currently, we assign the role ```padel-users-admin``` to the user. After that, you should be able to see the main page like this:
Now, repeat the process to add a role to the user named `reader`. This time, assign the role `padel-users-admin` to the user. After completing this step, you should be able to see the main page like this:

image::openid/frontend-add-role-admin-01.png[]

=== Call the backend service

Now, we know how to manage the security in both applications following the OpenID and JWT approach.

We need the last point: join the two applications.
Now that we've learned how to manage security in both applications following the OpenID and JWT approach, let's address the final point: integrating the two applications.

We have to propagate the JWT user token to the back-end application. It gets the token, and depending on the role, it allows doing the operation or not.
We need to propagate the JWT user token to the backend application. The backend receives the token and, based on the user's role, allows or denies the operation.

[.lines_space]
[.console-input]
Expand All @@ -396,4 +382,4 @@ export const createUser = async (domain, userToken, userData) => {
return await response.json();
};
----
----
Loading

0 comments on commit 39f5153

Please sign in to comment.