# Understanding the Powerful Keycloak Tool

Authentication Server with NestJS

### Index

1.  Tutorial
2.  Installation
3.  Setup
4.  Add a New Realm
5.  OpenID Connect
6.  Integration NestJS + KeyCloak
7.  ReactiveX
8.  Internal Router Docker
9.  Multi-Tenancy Implementation
10.  Setup MySQL Database with Docker
11.  Setup Sequelize ORM
12.  Create NestJS Module Mult Tenant
13.  H2 Database Setup
14.  Keycloak email Setup
15.  MySQL Integration
16.  Migration Database
17.  Keycloak Tests
18.  References

### 1\. Tutorial

### 1.1 Start Environment

### 1.1.1 KeyCloak Environment

// Start Docker  
**sudo dockerd** 

docker images

// Start Docker Keyclak App  
**docker start keycloak\_app\_1**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834860104/zJLneVe42.gif)

KeyCloak Admin Console

### 1.1.2 KeyCloak API Test

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834866197/HlhidUQn9.gif)

Demo Key Cloak API

### 1.1.3 Backend NestJS with KeyCloak

*   [https://hub.docker.com/r/jboss/keycloak/](https://hub.docker.com/r/jboss/keycloak/)

### Start NestJS with Docker

docker-compose up

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834870027/Xpxi0xU_G.gif)

### 1.1.4 Database with support to Backend NestJS with KeyCloak

### 2\. Installation KeyCloak

### Install Keycloak

*   [https://www.keycloak.org/getting-started/getting-started-docker](https://www.keycloak.org/getting-started/getting-started-docker)

```
docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:16.1.0
```

### by Docker

*   docker-compose.yaml

version: "3"

services:

app:

image:  jboss/keycloak:15.0.0

\# volumes:

\#   - ./.docker/keycloak/data:/opt/jboss/keycloak/standalone/data

environment:

\- KEYCLOAK\_USER=admin

\- KEYCLOAK\_PASSWORD=admin

#- KEYCLOACK\_IMPORT=/tmp/test-realm-export.json

\- DB\_VENDOR=h2

ports:

\- 8080:8080

#Start Docker Main Deamon  
sudo dockerd &

#Download & Up Docker Image  
docker-compose up

\# localhost:8080

docker start keycloak\_app\_1

### Docker Initialize

sudo dockerd &

  
docker images

docker start keycloak\_app\_1

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834873782/6S2L-TVCjp.png)

\- Http management interface listening on [http://127.0.0.1:9990/management](http://127.0.0.1:9990/management)

\- Admin console listening on   
[http://127.0.0.1:9990](http://127.0.0.1:9990)

\# Auth

[http://127.0.0.1:8080/auth/](http://127.0.0.1:8080/auth/)

[http://127.0.0.1:8080/management](http://127.0.0.1:9990/management)

### 3\. Setup KeyCloak

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834876159/LIqOtlRUw.gif)

KeyCloak

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834889702/d-x-rr9aX.png)

add a New Realm

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834890996/g7R3ZHGe5.png)

### OAuth2 Concepts

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834892799/MtZspBpjm.png)

#### Resource Owner

*   Spotfy — Luiz

#### Client

*   Application Spotify

#### Resource Server

*   Facebook

#### Authorization Server

*   Facebook
*   access\_token | login
*   Open ID Connect = OAuth2 + Login

### SAML2

*   Web Application
*   auth server
*   XML

### Open ID Connect

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834894145/bHJYt3URx.png)

### Template Engine

*   FreeMarker Java

[**What is Apache FreeMarker™?**  
*Apache FreeMarker™ is a template engine: a Java library to generate text output (HTML web pages, e-mails, configuration…*freemarker.apache.org](https://freemarker.apache.org/ "https://freemarker.apache.org/")[](https://freemarker.apache.org/)

### Add KeyCloak Client

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834895454/oChvD5pwq.png)

add clients nest (Backend)

### Access Type

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834896675/zp14NGU8c.png)

*   Select => confidential
*   Backend App => Confidential

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834897891/b4a1GUqwY.png)

*   Secret
*   84c8ea8c-5e7e-48ea-a38f-61547e3d4ad7

### **Access Type:**

*   Confidential
*   Public
*   Bear-Only

### Connection KeyClak

POST http://localhost:8080/auth/realms/fullcycle/protocol/openid-connect/token

Content-Type: application/x-www-form-urlencoded

client\_id=nest

&client\_secret=84c8ea8c-5e7e-48ea-a38f-61547e3d4ad7

&grant\_type=password

&username=user1@user.com

&password=123456

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834899579/jIxQLXbbl.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834901820/0Qf0g8oFE.png)

### 4\. OpenID Connect

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834903362/YHrPKa29R.png)

[https://openid.net/](https://openid.net/)

### JWT Token

eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZamg3TDNjbzhHNlU4Ykh0Rlg4Q3h1Y3phY0JEY0ZiMHJCRWhCSmRCTzQ4In0.eyJleHAiOjE2NDI1Mjk1NTUsImlhdCI6MTY0MjUyOTI1NSwianRpIjoiNDZkM2ZiN2QtNTkwMi00NGU5LWI1MWUtM2QxNWQ0YmYzYzgwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2Z1bGxjeWNsZSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI2ZjE4OTExNi1hNTQyLTQzNmItYmE1ZC00MDUyMmMxYTZhZTAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJuZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6IjRiMWM3MTdmLTAyMWMtNDY0My05ZGY0LWZiOWM1YjMwZDEzNyIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZ2VyZW50ZSIsImRlZmF1bHQtcm9sZXMtZnVsbGN5Y2xlIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiNGIxYzcxN2YtMDIxYy00NjQzLTlkZjQtZmI5YzViMzBkMTM3IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiVXNlcjEgTGFzdCBOYW1lMSIsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIxQHVzZXIuY29tIiwiZ2l2ZW5fbmFtZSI6IlVzZXIxIiwiZmFtaWx5X25hbWUiOiJMYXN0IE5hbWUxIiwiZW1haWwiOiJ1c2VyMUB1c2VyLmNvbSJ9.aDlJv-JUxxWtepMuiJsNgVP0EIxBZ6pybr9Nsqhn8u42Ai3t2R4JRkUzk97zHjOXEtkPc6yKJ\_BCEwEjlHyAHba2Tv\_3UzdT0geAguvnDRyIi7T3D1feYG8UMnEn-NxO1LVdb8XSq5OENsmZZZtYQavXY12nqv5c3Go\_Gqr9QeA1ijr6NCZnKi01hGpbhkE85dt2wJK\_XIFizUzsBRF-hW9nwMZrKPSBAGFXU8y0OY9UOZtE21mgeFmqFqR48yTvC9sBTeumm0RTFkyU\_wws2gq6JCFtFASIvt3pwl-chKAzXPm1IKOUn14kQFo8Qhw49URh3jTMk97xQHEi82xWIg","expires\_in":300,"refresh\_expires\_in":1800,"refresh\_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiNjBkYTAwOS05YWMyLTQ4MjgtOTkyYS05MzI4NDQxZTYzMGMifQ.eyJleHAiOjE2NDI1MzEwNTUsImlhdCI6MTY0MjUyOTI1NSwianRpIjoiODhmYWM0ZWMtNTg1ZC00YzE4LTk4MWYtYWEyZjMzZDNiNzNjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2Z1bGxjeWNsZSIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9mdWxsY3ljbGUiLCJzdWIiOiI2ZjE4OTExNi1hNTQyLTQzNmItYmE1ZC00MDUyMmMxYTZhZTAiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoibmVzdCIsInNlc3Npb25fc3RhdGUiOiI0YjFjNzE3Zi0wMjFjLTQ2NDMtOWRmNC1mYjljNWIzMGQxMzciLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0YjFjNzE3Zi0wMjFjLTQ2NDMtOWRmNC1mYjljNWIzMGQxMzcifQ.lx23mdtPF918kVEnmT78\_yco2Wc4fSo5jOasB3MyPbI

[https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZamg3TDNjbzhHNlU4Ykh0Rlg4Q3h1Y3phY0JEY0ZiMHJCRWhCSmRCTzQ4In0.eyJleHAiOjE2NDI1Mjk1NTUsImlhdCI6MTY0MjUyOTI1NSwianRpIjoiNDZkM2ZiN2QtNTkwMi00NGU5LWI1MWUtM2QxNWQ0YmYzYzgwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2Z1bGxjeWNsZSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI2ZjE4OTExNi1hNTQyLTQzNmItYmE1ZC00MDUyMmMxYTZhZTAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJuZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6IjRiMWM3MTdmLTAyMWMtNDY0My05ZGY0LWZiOWM1YjMwZDEzNyIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZ2VyZW50ZSIsImRlZmF1bHQtcm9sZXMtZnVsbGN5Y2xlIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiNGIxYzcxN2YtMDIxYy00NjQzLTlkZjQtZmI5YzViMzBkMTM3IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiVXNlcjEgTGFzdCBOYW1lMSIsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIxQHVzZXIuY29tIiwiZ2l2ZW5fbmFtZSI6IlVzZXIxIiwiZmFtaWx5X25hbWUiOiJMYXN0IE5hbWUxIiwiZW1haWwiOiJ1c2VyMUB1c2VyLmNvbSJ9.aDlJv-JUxxWtepMuiJsNgVP0EIxBZ6pybr9Nsqhn8u42Ai3t2R4JRkUzk97zHjOXEtkPc6yKJ\_BCEwEjlHyAHba2Tv\_3UzdT0geAguvnDRyIi7T3D1feYG8UMnEn-NxO1LVdb8XSq5OENsmZZZtYQavXY12nqv5c3Go\_Gqr9QeA1ijr6NCZnKi01hGpbhkE85dt2wJK\_XIFizUzsBRF-hW9nwMZrKPSBAGFXU8y0OY9UOZtE21mgeFmqFqR48yTvC9sBTeumm0RTFkyU\_wws2gq6JCFtFASIvt3pwl-chKAzXPm1IKOUn14kQFo8Qhw49URh3jTMk97xQHEi82xWIg%22%2C%22expires\_in%22%3A300%2C%22refresh\_expires\_in%22%3A1800%2C%22refresh\_token%22%3A%22eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiNjBkYTAwOS05YWMyLTQ4MjgtOTkyYS05MzI4NDQxZTYzMGMifQ.eyJleHAiOjE2NDI1MzEwNTUsImlhdCI6MTY0MjUyOTI1NSwianRpIjoiODhmYWM0ZWMtNTg1ZC00YzE4LTk4MWYtYWEyZjMzZDNiNzNjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2Z1bGxjeWNsZSIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9mdWxsY3ljbGUiLCJzdWIiOiI2ZjE4OTExNi1hNTQyLTQzNmItYmE1ZC00MDUyMmMxYTZhZTAiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoibmVzdCIsInNlc3Npb25fc3RhdGUiOiI0YjFjNzE3Zi0wMjFjLTQ2NDMtOWRmNC1mYjljNWIzMGQxMzciLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0YjFjNzE3Zi0wMjFjLTQ2NDMtOWRmNC1mYjljNWIzMGQxMzcifQ.lx23mdtPF918kVEnmT78\_yco2Wc4fSo5jOasB3MyPbI&publicKey=%7B%0A%20%20%22e%22%3A%20%22AQAB%22%2C%0A%20%20%22kty%22%3A%20%22RSA%22%2C%0A%20%20%22n%22%3A%20%22jBojl\_HQ8J0BXCtLTnX0hQBLfIflbPclukIFwrFQ2JY9wSACXpOhO2vC6NLu02JO2r9z68VnxTgov8LuCArL\_zzr4XZsOATK8bKdT6GI\_bcsoCH0yJ0\_CJ5go6KIOraQbsGI7rjWW\_2If-5xfucJ4apiX1XpDAgKEOLV9tTCwMc-G7zPMFEiVZbS9HPI7BHPkYkHUmpR2K6klP7qSW9PnpFnGz1J6\_vkP6yDUKYVkg7cUIV93rVcZvAXNGrLOmgvVAouLFFGgRGnKj-wdUFtRofVeOjYTnFwcot9P2wADQz8IkpD15NmY\_l2PgB3uigRi7I83oWAwWVuFhNuxoCYcQ%22%0A%7D](https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZamg3TDNjbzhHNlU4Ykh0Rlg4Q3h1Y3phY0JEY0ZiMHJCRWhCSmRCTzQ4In0.eyJleHAiOjE2NDI1Mjk1NTUsImlhdCI6MTY0MjUyOTI1NSwianRpIjoiNDZkM2ZiN2QtNTkwMi00NGU5LWI1MWUtM2QxNWQ0YmYzYzgwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2Z1bGxjeWNsZSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI2ZjE4OTExNi1hNTQyLTQzNmItYmE1ZC00MDUyMmMxYTZhZTAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJuZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6IjRiMWM3MTdmLTAyMWMtNDY0My05ZGY0LWZiOWM1YjMwZDEzNyIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZ2VyZW50ZSIsImRlZmF1bHQtcm9sZXMtZnVsbGN5Y2xlIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiNGIxYzcxN2YtMDIxYy00NjQzLTlkZjQtZmI5YzViMzBkMTM3IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiVXNlcjEgTGFzdCBOYW1lMSIsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIxQHVzZXIuY29tIiwiZ2l2ZW5fbmFtZSI6IlVzZXIxIiwiZmFtaWx5X25hbWUiOiJMYXN0IE5hbWUxIiwiZW1haWwiOiJ1c2VyMUB1c2VyLmNvbSJ9.aDlJv-JUxxWtepMuiJsNgVP0EIxBZ6pybr9Nsqhn8u42Ai3t2R4JRkUzk97zHjOXEtkPc6yKJ_BCEwEjlHyAHba2Tv_3UzdT0geAguvnDRyIi7T3D1feYG8UMnEn-NxO1LVdb8XSq5OENsmZZZtYQavXY12nqv5c3Go_Gqr9QeA1ijr6NCZnKi01hGpbhkE85dt2wJK_XIFizUzsBRF-hW9nwMZrKPSBAGFXU8y0OY9UOZtE21mgeFmqFqR48yTvC9sBTeumm0RTFkyU_wws2gq6JCFtFASIvt3pwl-chKAzXPm1IKOUn14kQFo8Qhw49URh3jTMk97xQHEi82xWIg%22%2C%22expires_in%22%3A300%2C%22refresh_expires_in%22%3A1800%2C%22refresh_token%22%3A%22eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiNjBkYTAwOS05YWMyLTQ4MjgtOTkyYS05MzI4NDQxZTYzMGMifQ.eyJleHAiOjE2NDI1MzEwNTUsImlhdCI6MTY0MjUyOTI1NSwianRpIjoiODhmYWM0ZWMtNTg1ZC00YzE4LTk4MWYtYWEyZjMzZDNiNzNjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2Z1bGxjeWNsZSIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9mdWxsY3ljbGUiLCJzdWIiOiI2ZjE4OTExNi1hNTQyLTQzNmItYmE1ZC00MDUyMmMxYTZhZTAiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoibmVzdCIsInNlc3Npb25fc3RhdGUiOiI0YjFjNzE3Zi0wMjFjLTQ2NDMtOWRmNC1mYjljNWIzMGQxMzciLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0YjFjNzE3Zi0wMjFjLTQ2NDMtOWRmNC1mYjljNWIzMGQxMzcifQ.lx23mdtPF918kVEnmT78_yco2Wc4fSo5jOasB3MyPbI&publicKey=%7B%0A%20%20%22e%22%3A%20%22AQAB%22%2C%0A%20%20%22kty%22%3A%20%22RSA%22%2C%0A%20%20%22n%22%3A%20%22jBojl_HQ8J0BXCtLTnX0hQBLfIflbPclukIFwrFQ2JY9wSACXpOhO2vC6NLu02JO2r9z68VnxTgov8LuCArL_zzr4XZsOATK8bKdT6GI_bcsoCH0yJ0_CJ5go6KIOraQbsGI7rjWW_2If-5xfucJ4apiX1XpDAgKEOLV9tTCwMc-G7zPMFEiVZbS9HPI7BHPkYkHUmpR2K6klP7qSW9PnpFnGz1J6_vkP6yDUKYVkg7cUIV93rVcZvAXNGrLOmgvVAouLFFGgRGnKj-wdUFtRofVeOjYTnFwcot9P2wADQz8IkpD15NmY_l2PgB3uigRi7I83oWAwWVuFhNuxoCYcQ%22%0A%7D)

### a) Header

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834904705/r-gQwSDX0.png)

Header JWT

### b) Payload

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834905951/9Leiy0Tt5.png)

Payload

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834907564/aIqswqEfw.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834909033/1KiBRcLqh.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834910408/JHs6MsKd1.png)

Payload

{  
  "exp": 1642529555,  
  "iat": 1642529255,  
  "jti": "46d3fb7d-5902-44e9-b51e-3d15d4bf3c80",  
  "iss": "[http://localhost:8080/auth/realms/fullcycle](http://localhost:8080/auth/realms/fullcycle)",  
  "aud": "account",  
  "sub": "6f189116-a542-436b-ba5d-40522c1a6ae0",  
  "typ": "Bearer",  
  "azp": "nest",  
  "session\_state": "4b1c717f-021c-4643-9df4-fb9c5b30d137",  
  "acr": "1",  
  "allowed-origins": \[  
    "[http://localhost:3000](http://localhost:3000)"  
  \],  
  "realm\_access": {  
    "roles": \[  
      "offline\_access",  
      "uma\_authorization",  
      "gerente",  
      "default-roles-fullcycle"  
    \]  
  },  
  "resource\_access": {  
    "account": {  
      "roles": \[  
        "manage-account",  
        "manage-account-links",  
        "view-profile"  
      \]  
    }  
  },  
  "scope": "email profile",  
  "sid": "4b1c717f-021c-4643-9df4-fb9c5b30d137",  
  "email\_verified": false,  
  "name": "User1 Last Name1",  
  "preferred\_username": "[user1@user.com](mailto:user1@user.com)",  
  "given\_name": "User1",  
  "family\_name": "Last Name1",  
  "email": "[user1@user.com](mailto:user1@user.com)"  
}

### c) Signature

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834911623/kSYIw-iQv.png)

Signature

### Test Get Method

POST http://localhost:8080/auth/realms/fullcycle/protocol/openid-connect/token

Content-Type: application/x-www-form-urlencoded

client\_id=nest

&client\_secret=84c8ea8c-5e7e-48ea-a38f-61547e3d4ad7

&grant\_type=password

&username=user1@user.com

&password=123456

###

GET http://localhost:8080/auth/realms/fullcycle/protocol/openid-connect/userinfo

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZamg3TDNjbzhHNlU4Ykh0Rlg4Q3h1Y3phY0JEY0ZiMHJCRWhCSmRCTzQ4In0.eyJleHAiOjE2NDI1NzA4NjcsImlhdCI6MTY0MjUzNDg2NywianRpIjoiZjQzZjc5MjMtNWVhNS00MzI1LWE3OTEtOGQzOTVlNTFmNTI1IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2Z1bGxjeWNsZSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI2ZjE4OTExNi1hNTQyLTQzNmItYmE1ZC00MDUyMmMxYTZhZTAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJuZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6IjM1ZDE5ODBmLTQ3YTktNGNlMS05YzU3LTU5ZGM0MDRlYzAxOCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZ2VyZW50ZSIsImRlZmF1bHQtcm9sZXMtZnVsbGN5Y2xlIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiMzVkMTk4MGYtNDdhOS00Y2UxLTljNTctNTlkYzQwNGVjMDE4IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiVXNlcjEgTGFzdCBOYW1lMSIsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIxQHVzZXIuY29tIiwiZ2l2ZW5fbmFtZSI6IlVzZXIxIiwiZmFtaWx5X25hbWUiOiJMYXN0IE5hbWUxIiwiZW1haWwiOiJ1c2VyMUB1c2VyLmNvbSJ9.Rwlqx6WdQ4V4QwPYOYtiHnOxAtokssUjvcG2-q03CFvOTXfbuzvhzY6szndzAH2XFC2dY2MhGRZeWCiiTyGM5z\_heSMf5atZ36rFM7ujEW\_\_S-WIoDRAWqAmaPErnWB5YWtuhvUjRk9ToLbcm6P0R70fY0ItZ7CRva2SoTXEDoOrE9HChRVyP-9S-Ecesjn4-s0dMg5w\_ANgsnAzw76GqWpfN-X6cbpB3IRN2fIXArP3CccpJIE708SHmNAQaUlPt6VM5VTdVB6isX8ujpWA8egyWqw2zAuqz-xyR2FrtVIndNhDAoKk3AinMyceLLImIF-5c9olHekXE8FYHwYnxA

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834913210/SDgqtumM2Q.png)

### Tokens JWT

*   Access Token
*   ID Token

### 5.Integration NestJS + KeyCloak

*   jwt-strategy.service.ts

import { Injectable } from '@nestjs/common';

import { PassportStrategy } from '@nestjs/passport';

import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()

export class JwtStrategyService extends PassportStrategy(Strategy, 'jwt') {

constructor() {

*super*({

jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),

ignoreExpiration: false,

secretOrKey: 'abcd123456',

});

}

async validate(*payload*) {

return *payload*;

}

}

*   docker-compose.yml

version: '3'

services:

app:

build: .

entrypoint: ./entrypoint.sh

ports:

\- 3000:3000

volumes:

\- .:/home/node/app

*   Docker Compose Up

docker-compose up

docker-compose exec app bash

npm install [@nestjs/axios](http://twitter.com/nestjs/axios "Twitter profile for @nestjs/axios") --save  
  

### 6\. ReactiveX

> An API for asynchronous programming  
> with observable streams

[**ReactiveX**  
*Edit description*reactivex.io](https://reactivex.io/ "https://reactivex.io/")[](https://reactivex.io/)

import { firstValueFrom } from 'rxjs';

### 7\. Internal Router Docker

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834914722/FDU5faLce.png)

*   auth.service.ts

import { HttpService } from '@nestjs/axios';

import { Injectable } from '@nestjs/common';

import { firstValueFrom } from 'rxjs';

//bcrypt

@Injectable()

export class AuthService {

constructor(private *http*:HttpService) {}

async login(*username*: *string*, *password*: *string*): *Promise*<*void*\>{

const { data } = await firstValueFrom(

*this*.http.post(

**'http://host.docker.internal/auth/realms/fullcycle/protocol/openid-connect/token',**

**new** *URLSearchParams*({

client\_id:'nest',

client\_secret:'84c8ea8c-5e7e-48ea-a38f-61547e3d4ad7',

grant\_type:'password',

username,

password,

// username:'user1@user.com',

// password:'123456'

})

));

return data;

}

}

*   docker-compose.yaml

version: '3'

services:

app:

build: .

entrypoint: ./entrypoint.sh

ports:

\- 3000:3000

volumes:

\- .:/home/node/app

**extra\_hosts:**

**\- "host.docker.internal:172.17.0.1"**

//etc/hosts

127.0.0.1 host.docker.internal

KeyCloak  
Port: 8080

*   /etc/hosts
*   Windows 10
*   aa

C:\\Windows\\System32\\drivers\\etc\\hosts

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834916418/DHtt7Fp67.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834918102/nd9RbS0n_.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834919958/GFEO9Db7m.png)

HTTP/1.1 201 Created

X-Powered-By: Express

Content-Type: application/json; charset=utf-8

Content-Length: 2370

ETag: W/"942-K7GG7R7oh3DcCQW7N3JdyLFcgkA"

Date: Wed, 19 Jan 2022 00:21:15 GMT

Connection: close

{

"access\_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZamg3TDNjbzhHNlU4Ykh0Rlg4Q3h1Y3phY0JEY0ZiMHJCRWhCSmRCTzQ4In0.eyJleHAiOjE2NDI1ODc2NzUsImlhdCI6MTY0MjU1MTY3NSwianRpIjoiZjBlMGY0MDMtNjc1MC00ZWJhLTk4NzEtZmRmMGQ4Y2QxNDgzIiwiaXNzIjoiaHR0cDovL2hvc3QuZG9ja2VyLmludGVybmFsOjgwODAvYXV0aC9yZWFsbXMvZnVsbGN5Y2xlIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjZmMTg5MTE2LWE1NDItNDM2Yi1iYTVkLTQwNTIyYzFhNmFlMCIsInR5cCI6IkJlYXJlciIsImF6cCI6Im5lc3QiLCJzZXNzaW9uX3N0YXRlIjoiODI5MTJjZWEtMzI4ZC00Y2ZkLTg1NTQtZWU2Y2RmMWUyMzQ5IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjMwMDAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJnZXJlbnRlIiwiZGVmYXVsdC1yb2xlcy1mdWxsY3ljbGUiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI4MjkxMmNlYS0zMjhkLTRjZmQtODU1NC1lZTZjZGYxZTIzNDkiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJVc2VyMSBMYXN0IE5hbWUxIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidXNlcjFAdXNlci5jb20iLCJnaXZlbl9uYW1lIjoiVXNlcjEiLCJmYW1pbHlfbmFtZSI6Ikxhc3QgTmFtZTEiLCJlbWFpbCI6InVzZXIxQHVzZXIuY29tIn0.UGLiygD0SgoBvS4-CkPI6duW-ROfvL2cUJ3KHJhxMYfV4n0PWBWQ00K\_R8lJ2hKVx0\_9zhLyPp-JWYiBdnZJIHyfsQmLMZHGggaKRP9X5HO9bOxAHf5flfE4OrZBFmQ8bnVV1ENm4gRtBMYlBr2HDgJbx6bshN2pXwZU8t0bS8\_S6cnX82fkXnVoRWiU0uHG\_UbK\_JSaL8Eupo7DY1wUpo5d7WxsQ4pQKxcKLMo3hgLsrN\_TjUnqQab6xpicGbrHzNIEw75IaRp0yenGymNEIh8rdZlOcPuWH6IRp8JKA8r27TOjZX2cnUN2g2g5ChoyhaSc5trOUGLbVzkpWEn8Qw",

"expires\_in": 36000,

"refresh\_expires\_in": 1800,

"refresh\_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiNjBkYTAwOS05YWMyLTQ4MjgtOTkyYS05MzI4NDQxZTYzMGMifQ.eyJleHAiOjE2NDI1NTM0NzUsImlhdCI6MTY0MjU1MTY3NSwianRpIjoiNTI1MDE1NTMtNzFmZi00NjI0LTg0NDItMmMzNzY3NGRkYjNkIiwiaXNzIjoiaHR0cDovL2hvc3QuZG9ja2VyLmludGVybmFsOjgwODAvYXV0aC9yZWFsbXMvZnVsbGN5Y2xlIiwiYXVkIjoiaHR0cDovL2hvc3QuZG9ja2VyLmludGVybmFsOjgwODAvYXV0aC9yZWFsbXMvZnVsbGN5Y2xlIiwic3ViIjoiNmYxODkxMTYtYTU0Mi00MzZiLWJhNWQtNDA1MjJjMWE2YWUwIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6Im5lc3QiLCJzZXNzaW9uX3N0YXRlIjoiODI5MTJjZWEtMzI4ZC00Y2ZkLTg1NTQtZWU2Y2RmMWUyMzQ5Iiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiODI5MTJjZWEtMzI4ZC00Y2ZkLTg1NTQtZWU2Y2RmMWUyMzQ5In0.t8L9O\_v7KW1akisFYiEntUI6kcYB9Y9Lo\_kXtGUB950",

"token\_type": "Bearer",

"not-before-policy": 0,

"session\_state": "82912cea-328d-4cfd-8554-ee6cdf1e2349",

"scope": "email profile"

### KeyCloak Public Key

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834921956/K4gyRpvEb.png)

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjBojl/HQ8J0BXCtLTnX0hQBLfIflbPclukIFwrFQ2JY9wSACXpOhO2vC6NLu02JO2r9z68VnxTgov8LuCArL/zzr4XZsOATK8bKdT6GI/bcsoCH0yJ0/CJ5go6KIOraQbsGI7rjWW/2If+5xfucJ4apiX1XpDAgKEOLV9tTCwMc+G7zPMFEiVZbS9HPI7BHPkYkHUmpR2K6klP7qSW9PnpFnGz1J6/vkP6yDUKYVkg7cUIV93rVcZvAXNGrLOmgvVAouLFFGgRGnKj+wdUFtRofVeOjYTnFwcot9P2wADQz8IkpD15NmY/l2PgB3uigRi7I83oWAwWVuFhNuxoCYcQIDAQAB

*   jwt-strategy.service.ts

import { Injectable } from '@nestjs/common';

import { PassportStrategy } from '@nestjs/passport';

import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()

export class JwtStrategyService extends PassportStrategy(Strategy, 'jwt') {

constructor() {

*super*({

jwtFromRequest: *ExtractJwt*.fromAuthHeaderAsBearerToken(),

ignoreExpiration: false,

secretOrKey: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjBojl/HQ8J0BXCtLTnX0hQBLfIflbPclukIFwrFQ2JY9wSACXpOhO2vC6NLu02JO2r9z68VnxTgov8LuCArL/zzr4XZsOATK8bKdT6GI/bcsoCH0yJ0/CJ5go6KIOraQbsGI7rjWW/2If+5xfucJ4apiX1XpDAgKEOLV9tTCwMc+G7zPMFEiVZbS9HPI7BHPkYkHUmpR2K6klP7qSW9PnpFnGz1J6/vkP6yDUKYVkg7cUIV93rVcZvAXNGrLOmgvVAouLFFGgRGnKj+wdUFtRofVeOjYTnFwcot9P2wADQz8IkpD15NmY/l2PgB3uigRi7I83oWAwWVuFhNuxoCYcQIDAQAB',

});

}

async validate(*payload*) {

return *payload*;

}

}

### Environmental Variables

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834923323/nLqvXImyK.png)

npm install @nestjs/config --save

*   .env

DB\_CONNECTION=mysql  
DB\_HOST=db  
DB\_USERNAME=root  
DB\_PASSWORD=root  
DB\_DATABASE=fin  
DB\_PORT=3306  
  
JWT\_SECRET="-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAszV9tJjxZ8LKOVcNRw74ZP5Z/ESEVsYYcVbZWkXupi5tEBoTiy3fEeZNTaQoAYZtsl3QIxD3+PPbNbqVa0JwtMo/qRWHT76f8r42eBbYl0GVhPmFMaP86G5/eOmlN4r4Lb2xNvqy5GlVwnNYd44kxgJfdmuMSdOaSVe9ksMHPVl7mVMoVwBAKbQa/YlukfUKAJwjAwbJZCknrPuQbgf3LKOIpo724eGNmC1yKx0ACvVCudvGuJ79KGeu64hi9wdpJu6SnRJdCn2g+K0uzQ4amIw8f5tUaYKKx2kMRd0FOYmr8Kg6Hda1JC49nbx7l/DbqcnZRDL5kHg8Km9v+6vi5QIDAQAB\\n-----END PUBLIC KEY-----"

### Validation Token from Keycloak with NESTJS

HTTP/1.1 200 OK

X-Powered-By: Express

Content-Type: application/json; charset=utf-8

Content-Length: 41

ETag: W/"29-YaKZtQQcSwG1X/7pJH1a82D0fjY"

Date: Wed, 19 Jan 2022 04:41:19 GMT

Connection: close

{

"name": "Luiz Carlos",

"route": "Security"

}

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834925427/xGzMJFjK1.png)

### 8\. Multi-Tenancy Implementation

### KeyCloak Implementation Multi-Tenant

*   One KeyCloak Instance for Each Group of Users **(no Share)**
*   One Realm of KeyCloak for Each Group of Users **(Share Level One)**
*   One Realm of KeyCloak for All Group of Users **(Share Level Two)**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834926974/w3OU-22U-.png)

KeyCloak with One Realm

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834928589/d-Yi_eeVB.png)

KeyCloak with Two Realms

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834930227/E0TYoPMak.png)

Example Multi-Tenant Architecture

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834931645/RJG5KfdNV.png)

KeyCloak Authentication Process

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834933707/CRz7a7B7w.png)

KeyCloak Authentication Plugins

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834935442/1eJVaZw0U.png)

### Add Subdomain with KeyCloak

*   Users
*   Attributes
*   [http://127.0.0.1:8080/auth/admin/master/console/#/realms/fullcycle/users/6f189116-a542-436b-ba5d-40522c1a6ae0/user-attributes](http://127.0.0.1:8080/auth/admin/master/console/#/realms/fullcycle/users/6f189116-a542-436b-ba5d-40522c1a6ae0/user-attributes)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834937083/UMiTFtUnD.png)

subdomain tenant1

### KeyCloak Clients Setup

*   Mappers
*   subdomain

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834938690/TLdHifZhX.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834940245/vW5yr82dA.png)

### Token JWT with KeyCloak Subdomain

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834942144/yNDumec2Q.png)

JWT Token with Subdomain KeyCloak

},  
  "scope": "email profile",  
  "sid": "bae21363-3d77-4f4a-ae35-2a84759f57a2",  
  "email\_verified": false,  
  "name": "User1 Last Name1",  
 ** "subdomain": "tenant1", <<<<<<<<< Subdomain "tenant1"**  
  "preferred\_username": "[user1@user.com](mailto:user1@user.com)",  
  "given\_name": "User1",  
  "family\_name": "Last Name1",  
  "email": "[user1@user.com](mailto:user1@user.com)"  
}

### Case with Multi-Tenant

*   Financial Application
*   Tables
*   transactions = Bill to Pay, Bill to Receive, account\_id
*   accounts = id, name, balance,subdomain

### 9\. Setup MySQL Database with Docker

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834943597/q9e1CfsYn.jpeg)

Use Dockerize with MySQL Container

*   /nest/Dockerfile
*   [https://github.com/Backend-2030/live-multi-tenancy-nest-next-keycloak/blob/main/nest/Dockerfile](https://github.com/Backend-2030/live-multi-tenancy-nest-next-keycloak/blob/main/nest/Dockerfile)

FROM node:14.15.4-alpine3.12

RUN apk add --no-cache bash 

RUN npm install -g @nestjs/cli@8.0.0 

ENV DOCKERIZE\_VERSION v0.6.1  
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE\_VERSION/dockerize-linux-amd64-$DOCKERIZE\_VERSION.tar.gz \\    && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE\_VERSION.tar.gz \\    && rm dockerize-linux-amd64-$DOCKERIZE\_VERSION.tar.gz 

USER node 

  
WORKDIR /home/node/app

*   docker-compose.yaml
*   Dockerize Wait DB MySQL Up

version: '3'

services:

app:

build: .

**entrypoint: dockerize -wait tcp://db3306 -timeout 40s ./entrypoint.sh //<<<< Dockerize Wait DB MySQL Up**

ports:

\- 3000:3000

volumes:

\- .:/home/node/app

extra\_hosts:

\- "host.docker.internal:172.17.0.1"

depends\_on:

\- db

db:

build: ./.docker/mysql

restart: always

tty: true

volumes:

\- ./.docker/dbdata:/var/lib/mysql

environment:

\- MYSQL\_DATABASE=fin

\- MYSQL\_ROOT\_PASSWORD=root

*   .env

*\# JWT\_SECRET="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjBojl/HQ8J0BXCtLTnX0hQBLfIflbPclukIFwrFQ2JY9wSACXpOhO2vC6NLu02JO2r9z68VnxTgov8LuCArL/zzr4XZsOATK8bKdT6GI/bcsoCH0yJ0/CJ5go6KIOraQbsGI7rjWW/2If+5xfucJ4apiX1XpDAgKEOLV9tTCwMc+G7zPMFEiVZbS9HPI7BHPkYkHUmpR2K6klP7qSW9PnpFnGz1J6/vkP6yDUKYVkg7cUIV93rVcZvAXNGrLOmgvVAouLFFGgRGnKj+wdUFtRofVeOjYTnFwcot9P2wADQz8IkpD15NmY/l2PgB3uigRi7I83oWAwWVuFhNuxoCYcQIDAQAB"*

JWT\_SECRET="-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjBojl/HQ8J0BXCtLTnX0hQBLfIflbPclukIFwrFQ2JY9wSACXpOhO2vC6NLu02JO2r9z68VnxTgov8LuCArL/zzr4XZsOATK8bKdT6GI/bcsoCH0yJ0/CJ5go6KIOraQbsGI7rjWW/2If+5xfucJ4apiX1XpDAgKEOLV9tTCwMc+G7zPMFEiVZbS9HPI7BHPkYkHUmpR2K6klP7qSW9PnpFnGz1J6/vkP6yDUKYVkg7cUIV93rVcZvAXNGrLOmgvVAouLFFGgRGnKj+wdUFtRofVeOjYTnFwcot9P2wADQz8IkpD15NmY/l2PgB3uigRi7I83oWAwWVuFhNuxoCYcQIDAQAB\\n-----END PUBLIC KEY-----"

DB\_CONNECTION=mysql

DB\_HOST=db

DB\_USERNAME=root

DB\_PASSWORD=root

DB\_DATABASE=fin

DB\_PORT=3306

*   .docker/mysql/Dockerfile

FROM mysql:5.7

RUN usermod -u 1000 mysql

### Docker Commands

docker-compose up --build

docker-compose up 

### 10\. Setup Sequelize ORM

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834944982/2VabbeoGg.png)

[https://sequelize.org/](https://sequelize.org/)

*   Support NodeJS
*   Support NestJS
*   Support Typescript

docker-compose exec app bash

npm install @nestjs/sequelize sequelize sequelize-typescript

npm install @types/sequelize --save-dev

*   /src/app.module.ts

*// import { HttpModule } from '@nestjs/axios';*

*import* { Module } *from* '@nestjs/common';

*import* { ConfigModule } *from* '@nestjs/config';

*import* { AppController } *from* './app.controller';

*import* { AppService } *from* './app.service';

*import* { AuthModule } *from* './auth/auth.module';

*//decorator - Javascript - Ecmascript 7*

@Module({

imports: \[

**SequelizeModule.forRoot({**

**dialect: process.env.DB\_CONNECTION *as* any,**

**host: process.env.DB\_HOST,**

**port: parseInt(process.env.DB\_PORT),**

**username: process.env.DB\_USERNAME,**

**password: process.env.DB\_PASSWORD,**

**database: process.env.DB\_DATABASE,**

**models: \[Transaction\],**

**autoLoadModels: true,**

**synchronize: true,**

**sync: {**

**alter: true,**

***//  force: true***

**},**

**}),**

ConfigModule.forRoot({isGlobal:true}),

AuthModule\],

controllers: \[AppController\],

providers: \[AppService\],

})

*export* class AppModule {}

Generation Transactions Module

nest g resource

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834946583/Ljm7jUoBO.png)

### Transaction Entity

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834948188/r0ccxOMe1.png)

*   Install MySQL2

npm install mysql2 --save

*   /transactions/entities/transaction.entity.ts

*import* { Model, Column, Table } *from* 'sequelize-typescript';

@Table({tableName:'transactions'})

*export* class Transaction extends Model {

@Column

payment\_date: Date;

@Column

name: string;

@Column

amount: number;

@Column

subdomain: string;

}

### Transactions:

*   Method POST

*POST* http://localhost:3000/transactions

Content-Type: application/json

{

"payment\_date":"2021-01-01",

"name":" Nova Conta1",

"amount": 30

}

HTTP/1.1 201 Created

X-Powered-By: Express

Content-Type: application/json; charset=utf-8

Content-Length: 162

ETag: W/"a2-gMuY8B9fKvcXfE+grN+WpL343KY"

Date: Wed, 19 Jan 2022 23:53:06 GMT

Connection: close

{

"id": 1,

"payment\_date": "2021-01-01T00:00:00.000Z",

"name": " Nova Conta1",

"amount": 30,

"updatedAt": "2022-01-19T23:53:06.120Z",

"createdAt": "2022-01-19T23:53:06.120Z"

}

*   Method GET

*GET* http://localhost:3000/transactions

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZamg3TDNjbzhHNlU4Ykh0Rlg4Q3h1Y3phY0JEY0ZiMHJCRWhCSmRCTzQ4In0.eyJleHAiOjE2NDI2NzI2NjAsImlhdCI6MTY0MjYzNjY2MCwianRpIjoiMTM1NTgwMmItYjhmMy00YTBkLWI2YzItYjQwYTIwZDlmNmI5IiwiaXNzIjoiaHR0cDovL2hvc3QuZG9ja2VyLmludGVybmFsOjgwODAvYXV0aC9yZWFsbXMvZnVsbGN5Y2xlIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjZmMTg5MTE2LWE1NDItNDM2Yi1iYTVkLTQwNTIyYzFhNmFlMCIsInR5cCI6IkJlYXJlciIsImF6cCI6Im5lc3QiLCJzZXNzaW9uX3N0YXRlIjoiZWI1MDlkNTAtYjNhNi00NmVjLThkNDktYWRiZmZmMzAyNTU4IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjMwMDAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJnZXJlbnRlIiwiZGVmYXVsdC1yb2xlcy1mdWxsY3ljbGUiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiJlYjUwOWQ1MC1iM2E2LTQ2ZWMtOGQ0OS1hZGJmZmYzMDI1NTgiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJVc2VyMSBMYXN0IE5hbWUxIiwic3ViZG9tYWluIjoidGVuYW50MSIsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIxQHVzZXIuY29tIiwiZ2l2ZW5fbmFtZSI6IlVzZXIxIiwiZmFtaWx5X25hbWUiOiJMYXN0IE5hbWUxIiwiZW1haWwiOiJ1c2VyMUB1c2VyLmNvbSJ9.Iz8i-qPYKejaEmXbyfyzVjWicA-BsYqfgAJdH09MntJMWVosNXBaVp9JOzKtUvh7Om\_YGk4mDoR32TbLqlnv7o3XAROMDe6MulPyiU9GEkzQBSleb1FqH74Xm58FXQ0JtVb5XOwIGwjIFXvlcohf20AB-gl8uCZNr527Za2n00y5UpsHb85xUK326ZWOnFNdReeuz0w6A9ILzF5mbLuYJcI1\_yNtUlXNzNqMQ4vcqtDXPE19W3xPqRHGlwucGYhaAbv0ozi4DwzRupmmQTwMhn3-c0V2fM4O8xkCJNMZJgmqirQF0JSnYkDVLGMLUkEXVfzvGTDon0eAQQUUP7TsUg

### 11.Create NestJS Module Mult Tenant

docker-compose exec app bash

nest g module tenant

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834949996/tdJ646M4Z.png)

nest g service tenant/tenant

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834951960/yqeYFe8OD.png)

### NestJS Guardian

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834953250/I45vd3yyE.jpeg)

nest g guard tenant/tenant

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834955305/sSxugo79nD.png)

docker-compose exec app bash

rm -rf dist

docker-compose up --build

*   tenant.module.ts

*import* { TenantGuard } *from* './tenant.guard';

*import* { Module, Global } *from* '@nestjs/common';

*import* { TenantService } *from* './tenant/tenant.service';

@Global()

@Module({

providers: \[TenantService, TenantGuard\],

exports: \[TenantService\],

})

*export* class TenantModule {}

### Mult Tenant with Subdomain

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834957421/fKqlJemuY.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834959495/oJgibJMyc.png)

### Problems Resolution

*   HTTPS/SSL
*   [https://stackoverflow.com/questions/59586634/how-to-disable-keycloak-ssl-when-running-keycloak-in-docker](https://stackoverflow.com/questions/59586634/how-to-disable-keycloak-ssl-when-running-keycloak-in-docker)

```
docker container run -p 8443:84  
43 -d -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin jboss/keycloak
```

### 13\. H2 Database Setup

*   [https://www.h2database.com/html/main.html](https://www.h2database.com/html/main.html)
*   [https://dbdb.io/db/h2](https://dbdb.io/db/h2)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834960937/mh01TtKWZ.jpeg)

### Setup Database H2 in Docker Compose

*   [https://github.com/geonetwork/docker-geonetwork/issues/12](https://github.com/geonetwork/docker-geonetwork/issues/12)

A bit cleaner workaround is set the location using the GEONETWORK\_DB\_NAME env var, e.g. we have ours set to a separate location like so:

```
`**GEONETWORK_DB_NAME=/var/lib/geonetwork_db/gn**` **results in a db created at** `**/var/lib/geonetwork_db/gn.h2.db**`
```

*   docker-compose.yaml

environment:

\- KEYCLOAK\_USER=biolabs

\- KEYCLOAK\_PASSWORD=01042021

*#- KEYCLOACK\_IMPORT=/tmp/test-realm-export.json*

\- DB\_VENDOR=h2

**\- GEONETWORK\_DB\_NAME=/var/lib/geonetwork\_db/gn**

ports:

\- 8080:8080

*#- 8443:8443*

### 14\. Keycloak email Setup

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834962547/R7BvDG6-5.png)

### Steps

#### Assign email address to admin account

Use Keycloak Account Management to add email address in *Personal Info*  
The below steps work for Keycloak 13 but UI may change with time

*   Login to Keycloak Security Admin Console using admin credentials
*   Click admin name shown in the top right corner
*   Click Manage account
*   Click Personal Info
*   Enter email address

#### Configure Email Settings

*   Open a realm
*   Under *Realm Settings* > *Email* the following details will work for a Gmail account
*   Host: smtp.gmail.com
*   Port: 587 (for SSL, use 465)
*   From: *admin-email-address*
*   Enable StartTLS: On (for SSL, use *Enable SSL*)
*   Enable Authentication: On
*   Username: *username*
*   Password: *password*

#### Configure Gmail

If the admin account is a Gmail account, the below steps are required

*   Login to the Gmail account in a browser
*   Visit [https://www.google.com/settings/security/lesssecureapps](https://www.google.com/settings/security/lesssecureapps)
*   Change the setting to *On*
*   Visit [https://accounts.google.com/DisplayUnlockCaptcha](https://accounts.google.com/DisplayUnlockCaptcha)
*   Follow on-screen instructions, if any

### 15\. MySQL Integration

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834963849/0FVgsAz_N.jpeg)

### Docker Compose

version: '3'

volumes:

mysql\_data:

driver: local

services:

keycloak:

image: quay.io/keycloak/keycloak:16.1.1

environment:

DB\_VENDOR: MYSQL

DB\_ADDR: mysql

DB\_DATABASE: keycloak

DB\_USER: keycloak

DB\_PASSWORD: password

KEYCLOAK\_USER: admin

KEYCLOAK\_PASSWORD: admin

ports:

\- 8085:8080

depends\_on:

\- mysql

mysql:

image: mysql:5.7

ports:

\- 3306:3306

volumes:

\- mysql\_data:/var/lib/mysql

environment:

MYSQL\_ROOT\_PASSWORD: root

MYSQL\_DATABASE: keycloak

MYSQL\_USER: keycloak

MYSQL\_PASSWORD: password

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834966744/QJHmdJfTD.gif)

KeyCloak MySQL Database

### 16\. Migration Database

From: H2 Database

To: MySQL

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834970187/3GeZ1qDmE.gif)

Migration Database From H2 to MySQL

### 17\. KeyCloak Tests

*   Login
*   Logout
*   Refresh
*   ID Token JSON
*   Access Token Json
*   ID Token
*   Access Token
*   Refresh Token

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1662834972610/_WycBRxaw.png)

### References

*   [https://www.keycloak.org/](https://www.keycloak.org/)
*   [https://openid.net/connect/](https://openid.net/connect/)
*   [https://dbdb.io/](https://dbdb.io/)
*   [https://dev.to/rounakcodes/keycloak-configure-realm-email-settings-gmail-3dfn](https://dev.to/rounakcodes/keycloak-configure-realm-email-settings-gmail-3dfn)
