Skip to content

Using Monaco with Keycloak

Matthew Toghill edited this page May 1, 2024 · 1 revision

Using Monaco Template with Keycloak

Install the Monaco Template

First, we need to install the Monaco Template.

dotnet new install Monaco.Template

image

The following command will show us all the available options within the template:

dotnet new monaco-backend-solution --help

We are going to need these:

image25

Create a New Solution

To create a solution named MonacoWithKeyCloak that contains the configuration for Keycloak and a json file with the realm settings, we will use the following command:

dotnet new monaco-backend-solution -n MonacoWithKeyCloak -kc -kr monaco-realm

image3

Now the solution has been created.

These are the files that are different from a vanilla usage of Monaco template

image4

In the solution file we will find a new folder called KeyCloak which contains the json file with the realm configuration:

image5

The appsettings file in the Api project has endpoints configured to match the name we gave to the realm:

image6

And the realm configuration json file itself:

image7

Run Keycloak in a Container

For this tutorial we will run Keycloak inside a container. Here you can find very useful information for this purpose.

For now, we will just use Keycloak in development mode:

docker run --name monaco-keycloak-tutorial -p 8080:8080 -e
KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=P@ssw0rd
quay.io/keycloak/keycloak:latest start-dev

Keycloak is now up and running. We can navigate to http://localhost:8080 where we will be prompted for the admin credentials we just used in the "docker run" command.

image8

Realm Creation

Now it's time to create a new realm by importing the json file provided by the Monaco Template. You will find it in the solution directory.

In the realm selector, click on "Create realm"

image26

Drag and drop the json file to the "Resource file" area

imagea

Now you can review the json contents and modify the name of the Realm. In this example we'll leave it as is.

image27

The realm is now created, and you can select it in the realms dropdown

image28

Testing the Realm

At this point, let's see what happens if we try to call an endpoint in our solution.

Open the solution and run the Api project. We will be presented with the Swagger OpenApi user interface.

If we try to execute a call to GET Companies then we receive a 401 status code, since that endpoint requires the caller to be authenticated and have the companies:read scope.

image29

image2a

This means that we need to authorize the caller.

If we click on "Authorize"

imagef

The following dialog will appear:

image10

We leave the default "client id" and select at least the scope that we need for this example, companies:read, and click Authorize.

We are now redirected to the Keycloak login page where we are prompted for user credentials.

image11

At this point we only have the "admin" user in Keycloak and that is not a valid user for client authentication.

We need to create a new valid user.

Creating a User

Go to "Users", "Create new user"

image2b

Write an email for the user (no need to be real) and switch on "Email verified". Click create.

image2c

A message will appear at the top indicating that the user has successfully been created.

image14

Now we need to set a password for the user. Click on the "Credentials" tab and set a password

image2d image2e

image2f

The User is now created and can be authenticated, but we still need to give it the right access permissions.

Assign a Role to the User

We need to give the User a Role that contains the companies:read scope.

Click on Users in the left menu and click on our user [email protected].

Filter by clients

image1e

Filter by "Customer" role and select the role. Click on Assign

image20

Now we can successfully log in through Swagger

image30

image22

If we try again calling the GET Companies method, it will no loger return a 401 or 403.

In this example we have not set a database in the Monaco solution, so it will return a 500 error when trying to connect to the database.

image31

Analyse the Access Token

Copy the Access Token from the Authorization Header

image32

And paste it into a token decoder, like https://jwt.io/

image33

Let's see some interesting points:

The field Authorized Parties (azp) is set to monaco-realm-api-swagger-ui because it is the client_id that we have used when authorizing in Swagger.

But the field Audience (aud) is set to monaco-realm-api, and therefore the roles field contains the role Customer, and the scope field contains companies:read (remember that we assigned the Customer Role of client monaco-realm-api to our User).

Why are we getting the scopes and roles from a client different than the one we have set while authorizing?

This is because Keycloak allows the configuration of protocol mappers that can affect how the "aud" is composed. The "Audience Template Mapper" can be used to add static audiences to both the access token and the ID token. This allows us to explicitly specify the clients that should be included in the "aud" field.

So, in Keycloak, if we go to the client monaco-realm-api-swagger-ui, and to the tab "Client scopes" we will see that it contains by default the client scope monaco-realm-api.

image34

Now go to "Client scopes" and select "monaco-realm-api".

Firstly, it is configured to be added as a "scope" in the token

image35

Secondly, in Mappers tab, it contains a mapper of type "Audience"

image36

Which finally is configured to include the client monaco-realm-api in the "aud" field of the access token

image37

So, in summary, this feature allows us to share the configuration of one client within other clients, which avoids the need for duplication.