Setting up KeyCloak with Nest JS Backend


This article will cover the basics of securing an NestJS API with keycloak, I’ll also cover how you can integrate the frontend(React) with this ecosystem and show you some other tricks as well.


Setting up Keycloak


Development → For local development, we are going to use docker for running keycloak and connecting it to a postgres container as well.

              
                volumes:
                postgres_data:
                  driver: local
              
              services:
                postgres_keycloak: # Service name
                  image: postgres:11
                  volumes:
                    - postgres_data:/var/lib/postgresql/data
                  environment:
                    POSTGRES_DB: keycloak
                    POSTGRES_USER: keycloak
                    POSTGRES_PASSWORD: password
                  ports:
                    - "127.0.0.1:5433:5432"
                keycloak:
                  image: quay.io/keycloak/keycloak:18.0.2
                  environment:
                    DB_VENDOR: POSTGRES
                    DB_ADDR: postgres_keycloak # <<< This is the address, change it to your external db ip/domain
                    DB_DATABASE: keycloak
                    DB_USER: keycloak
                    DB_SCHEMA: public
                    DB_PASSWORD: password
                    KEYCLOAK_ADMIN: admin
                    KEYCLOAK_ADMIN_PASSWORD: password
                    JAVA_OPTS: -Dkeycloak.profile=preview
                    KEYCLOAK_LOGLEVEL: DEBUG
                    ROOT_LOGLEVEL: DEBUG
                  command:
                    - start-dev
                  ports:
                    - 8080:8080
                    - 8443:8443
                  depends_on:
                    - postgres_keycloak
            
          

  • We are opening ports 8080, 8443 for docker and also making sure that you can access the postgres instance through 5433 port.
  • As you can see this is using the latest version of keycloak so you might find some differences when running up a jboss based image which is now deprecated.
  • JAVA_OPTS: -Dkeycloak.profile=preview flag passed inside the docker-compose file is used for starting keycloak with preview features on. This will cause to enable authorization, new admin UI and much more features.
  • Make an educated decision if you want to use preview features in production as they might lack support.

Running this -

          
            docker-compose -f docker-compose.yml up -d # Up
            docker-compose -f docker-compose.yml down # Down 
          
         

Common Error when starting keycloak


Keycloak: User with username 'admin' already added.

Resolution

1. stop all containers

2. comment out the two relevant lines

             
              version: "3"

              services:
              keycloak:
                image: quay.io/keycloak/keycloak:latest
                environment:
                  # KEYCLOAK_USER: admin
                  # KEYCLOAK_PASSWORD: pass
                  ...
             
            

3. start all containers

4. wait until keycloak container has successfully starteds

5. stop all containers, again

6. comment back in the two lines from above

             
              version: "3"

              services:
                keycloak:
                  image: quay.io/keycloak/keycloak:latest
                  environment:
                    KEYCLOAK_USER: admin
                    KEYCLOAK_PASSWORD: pass
                    ...
             
            

7. start all containers

This time (and subsequent times) it worked. Keycloak was running and the admin user was registered and working as expected.



Keycloak Configuration


1. Set up Keycloak server

There are multiple ways to setup Keycloak instance. Follow the instructions in the link . Once the setup completed you should login to the Keycloak server using provided admin account credentials.

For reference, this was the docker-compose we used

Keycloak Configuration

2. Create a Realm


A realm secures and manages security meta data for a set of users, applications, and registered OAuth clients. Users can be created confined to a specific realm within the Administration console. Roles can be defined at the realm level. You can also set up user role mappings to assign these permissions to specific users. Create a realm by clicking the  add realm button on the  Select realm drop down. Give a name with your preference and click the Create button.

Keycloak create realm

Add realm

After that you will be redirected to the realm setting page.

Keycloak realm setting

Note: You can change access token and refresh token lifespan by moving to token tab.

Keycloak refresh token

Make sure Demo-Realm  is selected for the below configurations. Avoid using the master realm. You don’t have to create the realm every time. It’s a one time process.


3. Create Clients


Clients are entities that can request Keycloak to authenticate a user. Most often, clients are applications and services that want to use Keycloak to secure themselves and provide a single sign-on solution. Clients can also be entities that just want to request identity information or an access token so that they can securely invoke other services on the network that are secured by Keycloak.
Clients tab allows you to manage your application clients.


Keycloak application clients

Here we have to register out NestJs application as a Keycloak client in Keycoak server.

Client :nest-app 

Keycloak Keycoak server

For the created client set theAccess Type  as bearer-only

 
Keycloak Access Type

Access Types explained,

  • Bearer-only— this is for services that rely solely on the bearer token included in the request and never initiate login on their own. It’s typically used for securing the back-end.
  • Confidential— clients of this type need to provide a secret in order to initiate the login process. Mostly used in OAuth client credential flow.
  • Public — since we have no real way of hiding the secret in a JS-based browser app, this is what we need to stick with.
  • Note — You won’t see the same access types when running the preview version of the app, but you will see the flows when setting up clients. The standered flow should work for backend and frontend both but you can try playing around to confirm your best flow.

4. Create Roles


Roles identify a type or category of user. Admin, user, manager and employee are all typical roles that may exist in an organization. Applications often assign access and permissions to specific roles rather than individual users as dealing with users can be too fine grained and hard to manage. For example, the Admin Console has specific roles which give permission to users to access parts of the Admin Console UI and perform certain actions. There is a global namespace for roles and each client also has its own dedicated namespace where roles can be defined. Realm Roles: Realm-level roles are a global namespace to define your roles. You can see the list of built-in and created roles by clicking the Roles left menu item.Client Roles: Client roles are basically a namespace dedicated to a client. Each client gets its own namespace. Client roles are managed under the Roles tab under each individual client. You interact with this UI the same way you do for realm-level roles.


1. Create client roles admin and user for nest-app client

Keycloak create roles

Client admin role 

Keycloak admin role

Client user role

 
Keycloak  user role

2. Create realm roles  app-admin and  app-user for nest-app  client

 
Keycloak realm roles

Realm admin role

 
Keycloak Realm admin roles

Realm user role

 

Composite Roles: Any realm or client level role can be turned into a composite role. A composite role is a role that has one or more additional roles associated with it. When a composite role is mapped to the user, the user also gains the roles associated with that composite. This inheritance is recursive so any composite of composites also gets inherited.

3. After saving the realm roles enable  Composite Roles and search for client react-web-app  in Client Roles  field. Select admin role and click Add selected . This configuration will assign nest-app, admin client role to the app-admin  realm role. If you have multiple clients with multiple roles, pick and choose the required roles from each client to create realm roles based on the need.

Keycloak Realm user roles

4.Similarly add user client role to app-user realm role.

 
Keycloak similarly role

5. Create Users


Users are entities that are able to log into your system. They can have attributes associated with themselves like email, username, address and phone number. They can be assigned to groups and have specific roles assigned to them.


1. Create three users and assign them following realm roles,

  • User1 with  app-user
  • User2 with  app-admin
  • User3 with  app-user, app-admin

Go toUsers  in left side menu to create users

Keycloak users

2. Go to Credentials tab to set user credentials. 

Keycloak user credentials

3. From the Role Mappings tab assign required roles to the Users.

 
Keycloak role mappings

2. Create realm roles  app-admin and  app-user for nest-app  client

 
Keycloak  Create realm roles 

Like above assign app-user role to User2 and app-user, app-admin roles to User3


What we want with keycloak ?


  • User trying to login to the front-end application (React.js).
  • User will be redirected to the Keycloak server for authentication.
  • If authentication successful, Authenticated user will be redirected to the application. Meantime user will get a JWT (JSON Web Token).
  • Unauthenticated user will be redirected to back to the keycloak login page.
  • With the valid JWT, front-end can access back-end rest api (Node.js/NestJS) by sending the JWT along with the service request.
  • Back-end will communicate with the Keycloak server to validate the token.
  • If the token is valid, back-end will server the request accordingly.
  • If the token is invalid, back-end will respond with error code 401 (Unauthorized).

Securing Nest API with KeyCloak


Application Configuration (NestJs)

I am assuming that you have basic knowledge on how to create a NestJs application with basic setup. We are using nest-keycloak-connect adapter to communicate with Keycloak server.
Keycloak Client Adapers : Keycloak client adapters are libraries that make it very easy to secure applications and services with Keycloak. We call these ‘adapters’ rather than libraries as they provide a tight integration to the underlying platform and framework. This makes adapters easy to use and require less boilerplate code than what is typically required by a library.


Implementation


Add nest Keycloak library to our code base

              npm install nest-keycloak-connect --save
            

Now we need to add Keycloak configuration to NestJs. Here we are adding configuration to the app.module.ts. But if you want you can create a separate module for Keycloak configuration and import that module in app.module.ts.


Here below is my actual setup

1.KeyCloak Service keycloak.service.ts


              
                import { Injectable } from '@nestjs/common';
                import { KeycloakConnectOptions, KeycloakConnectOptionsFactory, PolicyEnforcementMode, TokenValidation } from 'nest-keycloak-connect';

                @Injectable()
               export class KeycloakConfigService implements KeycloakConnectOptionsFactory {
               createKeycloakConnectOptions(): KeycloakConnectOptions | Promise {
                return {
                  authServerUrl: process.env.KEYCLOAK_AUTH_SERVER_URL,
                  realm: process.env.KEYCLOAK_REALM,
                  clientId: process.env.KEYCLOAK_CLIENT_ID,
                  secret: process.env.KEYCLOAK_CLIENT_SECRET,
                  policyEnforcement: PolicyEnforcementMode.PERMISSIVE,
                  tokenValidation: TokenValidation.ONLINE,
                };
              }
            }
              
            

2. KeyCloak Module keycloak.module.ts

              
                import { Global, Module } from '@nestjs/common';
                import { KeycloakConfigService } from './keycloak.service';

                @Global()
                @Module({
                  providers: [KeycloakConfigService],
                   exports: [KeycloakConfigService], // 👈 export PrismaService for DI
                 })
            export class KeyCloakConfigModule {}
              
            

3. Types types.d.ts


              
                interface RealmAccess {
                  [name: string]: ?string[];
                }
                
                interface ResourceAccess {
                  [name: string]: ?{
                    [name: string]: ?string[];
                  }; // Any represent
                }
                
                export interface KeyCloakUserType {
                  exp: number;
                  iat: number;
                  auth_time: number;
                  jti: string;
                  iss: string;
                  aud: string[];
                  sub: string;
                  typ: string;
                  azp: string;
                  session_state: string;
                  acr: string;
                  realm_access: RealmAccess;
                  resource_access: ResourceAccess;
                  scope: string;
                  sid: string;
                  email_verified: boolean;
                  name?: string;
                  preferred_username?: string;
                  given_name?: string;
                  family_name?: string;
                  email: string;
                  // Extra Meta Data in Case needed
                  [otherOptions: string]: unknown;
                }
              
            

4. Auth Guards (NestJS)

              
                import { APP_GUARD } from '@nestjs/core';
                import { AuthGuard, ResourceGuard, RoleGuard } from 'nest-keycloak-connect';

            export const GlobalKeyCloakGuard = [
              // This adds a global level authentication guard,
              // you can also have it scoped
              // if you like.
              //
              // Will return a 401 unauthorized when it is unable to
              // verify the JWT token or Bearer header is missing.
              {
                provide: APP_GUARD,
                useClass: AuthGuard,
              },
              // This adds a global level resource guard, which is permissive.
              // Only controllers annotated with @Resource and
              // methods with @Scopes
              // are handled by this guard.
              {
                provide: APP_GUARD,
                useClass: ResourceGuard,
              },
              // New in 1.1.0
              // This adds a global level role guard, which is permissive.
              // Used by `@Roles` decorator with the
              // optional `@AllowAnyRole` decorator for allowing any
              // specified role passed.
              {
                provide: APP_GUARD,
                useClass: RoleGuard,
              },
            ];
              
            

Here we are using nest-keycloak-connect’s  AuthGuard, ResourceGuard, RoleGuard to protect our endpoints.

Now we add following endpoints to our controller and start the application using npm start.


              
                @Controller()
            export class UserController {
              constructor(private readonly userService: UserService) {}@Get()
              getpublic(): string {
                return `${this.userService.getHello()} from public`;
              }

              @Get('/user')
              getUser(): string {
                return `${this.userService.getHello()} from user`;
              }
              
              @Get('/admin')
              getAdmin(): string {
                return `${this.userService.getHello()} from admin`;
              }

              @Get('/all')
              getAll(): string {
                return `${this.userService.getHello()} from all`;
              }
            }
              
            

You can try to access those endpoints from postman or a browser. You can see you are getting  {“statusCode”:401,”message”:”Unauthorized”} from those endpoints.

To give access those endpoints we can use following annotations..


              
                @Controller()
            export class UserController {
              constructor(private readonly userService: UserService) {}@Get('/public')

              @Unprotected()
              getpublic(): string {
                return `${this.userService.getHello()} from public`;
              }

            @Get('/user')
            @Roles({ roles: ['admin', 'other'] })
              getUser(): string {
                return `${this.userService.getHello()} from user`;
              }

            @Get('/admin')
            @Roles({ roles: ['admin', 'realm:sysadmin'], mode: RoleMatchingMode.ALL })
              getAdmin(): string {
                return `${this.userService.getHello()} from admin`;
              }

            @Get('/all')
            @Roles({ roles: [], mode: RoleMatchingMode.ALL })
            getAll(): string {
                return `${this.userService.getHello()} from all`;
              }
            }
              
            

This is the extracted JWT received from frontend application from  User1 we created when configuring Keycloak. In the resource access section you can see this that user has user client role and in the realm access section user has app-user realm role. We can also use realm role in the @Roles annotation. e.g : @Roles(‘realm:app-user’). In above example we used client role.

Keycloak Created Configuration

Now we can access the /user endpoint with postman with JWT attached in the Authorization header.

Keycloak Authorization

But if we try to access /admin endpoint we are getting 403 because User1 doesn’t have admin role.

Keycloak access roles

We can access access the /all endpoint since it’s allowed for any roles.

Keycloak roles

Volla, we have successfully secured our NestJs REST API using Keycloak.


Managing Keycloak Data


  • As keycloak handles all the db related magic, we are sorted on that end but if you want to use that DB to fetch data. That’s another case.
  • If it’s simple data fetching, we can use the nodejs-admin-keycloak for this task but if you are want to integrate more complex logic with keycloak-db then you will have to use a second database with your ORM for the task.
  • We are using Prisma ORM and ended up with this schema file for the second db aka keycloak connection

Show schema.prisma

              
                generator client {
                  provider = "prisma-client-js"
                }
                
                datasource db {
                  provider = "postgresql"
                  url      = env("KEYCLOAK_DATABASE_URL")
                }
                
                model admin_event_entity {
                  id               String  @id(map: "constraint_admin_event_entity") @db.VarChar(36)
                  admin_event_time BigInt?
                  realm_id         String? @db.VarChar(255)
                  operation_type   String? @db.VarChar(255)
                  auth_realm_id    String? @db.VarChar(255)
                  auth_client_id   String? @db.VarChar(255)
                  auth_user_id     String? @db.VarChar(255)
                  ip_address       String? @db.VarChar(255)
                  resource_path    String? @db.VarChar(2550)
                  representation   String?
                  error            String? @db.VarChar(255)
                  resource_type    String? @db.VarChar(64)
                }
                
                model associated_policy {
                  policy_id                                                                             String                 @db.VarChar(36)
                  associated_policy_id                                                                  String                 @db.VarChar(36)
                  resource_server_policy_associated_policy_associated_policy_idToresource_server_policy resource_server_policy @relation("associated_policy_associated_policy_idToresource_server_policy", fields: [associated_policy_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsr5s213xcx4wnkog82ssrfy")
                  resource_server_policy_associated_policy_policy_idToresource_server_policy            resource_server_policy @relation("associated_policy_policy_idToresource_server_policy", fields: [policy_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrpas14xcx4wnkog82ssrfy")
                
                  @@id([policy_id, associated_policy_id], map: "constraint_farsrpap")
                  @@index([associated_policy_id], map: "idx_assoc_pol_assoc_pol_id")
                }
                
                model authentication_execution {
                  id                  String               @id(map: "constraint_auth_exec_pk") @db.VarChar(36)
                  alias               String?              @db.VarChar(255)
                  authenticator       String?              @db.VarChar(36)
                  realm_id            String?              @db.VarChar(36)
                  flow_id             String?              @db.VarChar(36)
                  requirement         Int?
                  priority            Int?
                  authenticator_flow  Boolean              @default(false)
                  auth_flow_id        String?              @db.VarChar(36)
                  auth_config         String?              @db.VarChar(36)
                  authentication_flow authentication_flow? @relation(fields: [flow_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_auth_exec_flow")
                  realm               realm?               @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_auth_exec_realm")
                
                  @@index([flow_id], map: "idx_auth_exec_flow")
                  @@index([realm_id, flow_id], map: "idx_auth_exec_realm_flow")
                }
                
                model authentication_flow {
                  id                       String                     @id(map: "constraint_auth_flow_pk") @db.VarChar(36)
                  alias                    String?                    @db.VarChar(255)
                  description              String?                    @db.VarChar(255)
                  realm_id                 String?                    @db.VarChar(36)
                  provider_id              String                     @default("basic-flow") @db.VarChar(36)
                  top_level                Boolean                    @default(false)
                  built_in                 Boolean                    @default(false)
                  realm                    realm?                     @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_auth_flow_realm")
                  authentication_execution authentication_execution[]
                
                  @@index([realm_id], map: "idx_auth_flow_realm")
                }
                
                model authenticator_config {
                  id       String  @id(map: "constraint_auth_pk") @db.VarChar(36)
                  alias    String? @db.VarChar(255)
                  realm_id String? @db.VarChar(36)
                  realm    realm?  @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_auth_realm")
                
                  @@index([realm_id], map: "idx_auth_config_realm")
                }
                
                model authenticator_config_entry {
                  authenticator_id String  @db.VarChar(36)
                  value            String?
                  name             String  @db.VarChar(255)
                
                  @@id([authenticator_id, name], map: "constraint_auth_cfg_pk")
                }
                
                model broker_link {
                  identity_provider   String  @db.VarChar(255)
                  storage_provider_id String? @db.VarChar(255)
                  realm_id            String  @db.VarChar(36)
                  broker_user_id      String? @db.VarChar(255)
                  broker_username     String? @db.VarChar(255)
                  token               String?
                  user_id             String  @db.VarChar(255)
                
                  @@id([identity_provider, user_id], map: "constr_broker_link_pk")
                }
                
                model client {
                  id                           String                      @id(map: "constraint_7") @db.VarChar(36)
                  enabled                      Boolean                     @default(false)
                  full_scope_allowed           Boolean                     @default(false)
                  client_id                    String?                     @db.VarChar(255)
                  not_before                   Int?
                  public_client                Boolean                     @default(false)
                  secret                       String?                     @db.VarChar(255)
                  base_url                     String?                     @db.VarChar(255)
                  bearer_only                  Boolean                     @default(false)
                  management_url               String?                     @db.VarChar(255)
                  surrogate_auth_required      Boolean                     @default(false)
                  realm_id                     String?                     @db.VarChar(36)
                  protocol                     String?                     @db.VarChar(255)
                  node_rereg_timeout           Int?                        @default(0)
                  frontchannel_logout          Boolean                     @default(false)
                  consent_required             Boolean                     @default(false)
                  name                         String?                     @db.VarChar(255)
                  service_accounts_enabled     Boolean                     @default(false)
                  client_authenticator_type    String?                     @db.VarChar(255)
                  root_url                     String?                     @db.VarChar(255)
                  description                  String?                     @db.VarChar(255)
                  registration_token           String?                     @db.VarChar(255)
                  standard_flow_enabled        Boolean                     @default(true)
                  implicit_flow_enabled        Boolean                     @default(false)
                  direct_access_grants_enabled Boolean                     @default(false)
                  always_display_in_console    Boolean                     @default(false)
                  client_attributes            client_attributes[]
                  client_node_registrations    client_node_registrations[]
                  protocol_mapper              protocol_mapper[]
                  redirect_uris                redirect_uris[]
                  scope_mapping                scope_mapping[]
                  web_origins                  web_origins[]
                
                  @@unique([realm_id, client_id], map: "uk_b71cjlbenv945rb6gcon438at")
                  @@index([client_id], map: "idx_client_id")
                }
                
                model client_attributes {
                  client_id String  @db.VarChar(36)
                  value     String? @db.VarChar(4000)
                  name      String  @db.VarChar(255)
                  client    client  @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk3c47c64beacca966")
                
                  @@id([client_id, name], map: "constraint_3c")
                  @@index([name], map: "idx_client_att_by_name_value")
                }
                
                model client_auth_flow_bindings {
                  client_id    String  @db.VarChar(36)
                  flow_id      String? @db.VarChar(36)
                  binding_name String  @db.VarChar(255)
                
                  @@id([client_id, binding_name], map: "c_cli_flow_bind")
                }
                
                model client_initial_access {
                  id              String @id(map: "cnstr_client_init_acc_pk") @db.VarChar(36)
                  realm_id        String @db.VarChar(36)
                  timestamp       Int?
                  expiration      Int?
                  count           Int?
                  remaining_count Int?
                  realm           realm  @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_client_init_acc_realm")
                
                  @@index([realm_id], map: "idx_client_init_acc_realm")
                }
                
                model client_node_registrations {
                  client_id String @db.VarChar(36)
                  value     Int?
                  name      String @db.VarChar(255)
                  client    client @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk4129723ba992f594")
                
                  @@id([client_id, name], map: "constraint_84")
                }
                
                model client_scope {
                  id                        String                      @id(map: "pk_cli_template") @db.VarChar(36)
                  name                      String?                     @db.VarChar(255)
                  realm_id                  String?                     @db.VarChar(36)
                  description               String?                     @db.VarChar(255)
                  protocol                  String?                     @db.VarChar(255)
                  client_scope_attributes   client_scope_attributes[]
                  client_scope_role_mapping client_scope_role_mapping[]
                  protocol_mapper           protocol_mapper[]
                
                  @@unique([realm_id, name], map: "uk_cli_scope")
                  @@index([realm_id], map: "idx_realm_clscope")
                }
                
                model client_scope_attributes {
                  scope_id     String       @db.VarChar(36)
                  value        String?      @db.VarChar(2048)
                  name         String       @db.VarChar(255)
                  client_scope client_scope @relation(fields: [scope_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_cl_scope_attr_scope")
                
                  @@id([scope_id, name], map: "pk_cl_tmpl_attr")
                  @@index([scope_id], map: "idx_clscope_attrs")
                }
                
                model client_scope_client {
                  client_id     String  @db.VarChar(255)
                  scope_id      String  @db.VarChar(255)
                  default_scope Boolean @default(false)
                
                  @@id([client_id, scope_id], map: "c_cli_scope_bind")
                  @@index([scope_id], map: "idx_cl_clscope")
                  @@index([client_id], map: "idx_clscope_cl")
                }
                
                model client_scope_role_mapping {
                  scope_id     String       @db.VarChar(36)
                  role_id      String       @db.VarChar(36)
                  client_scope client_scope @relation(fields: [scope_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_cl_scope_rm_scope")
                
                  @@id([scope_id, role_id], map: "pk_template_scope")
                  @@index([scope_id], map: "idx_clscope_role")
                  @@index([role_id], map: "idx_role_clscope")
                }
                
                model client_session {
                  id                         String                       @id(map: "constraint_8") @db.VarChar(36)
                  client_id                  String?                      @db.VarChar(36)
                  redirect_uri               String?                      @db.VarChar(255)
                  state                      String?                      @db.VarChar(255)
                  timestamp                  Int?
                  session_id                 String?                      @db.VarChar(36)
                  auth_method                String?                      @db.VarChar(255)
                  realm_id                   String?                      @db.VarChar(255)
                  auth_user_id               String?                      @db.VarChar(36)
                  current_action             String?                      @db.VarChar(36)
                  user_session               user_session?                @relation(fields: [session_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_b4ao2vcvat6ukau74wbwtfqo1")
                  client_session_auth_status client_session_auth_status[]
                  client_session_note        client_session_note[]
                  client_session_prot_mapper client_session_prot_mapper[]
                  client_session_role        client_session_role[]
                  client_user_session_note   client_user_session_note[]
                
                  @@index([session_id], map: "idx_client_session_session")
                }
                
                model client_session_auth_status {
                  authenticator                                             String         @db.VarChar(36)
                  status                                                    Int?
                  client_session                                            String         @db.VarChar(36)
                  client_session_client_sessionToclient_session_auth_status client_session @relation(fields: [client_session], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "auth_status_constraint")
                
                  @@id([client_session, authenticator], map: "constraint_auth_status_pk")
                }
                
                model client_session_note {
                  name                                               String         @db.VarChar(255)
                  value                                              String?        @db.VarChar(255)
                  client_session                                     String         @db.VarChar(36)
                  client_session_client_sessionToclient_session_note client_session @relation(fields: [client_session], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk5edfb00ff51c2736")
                
                  @@id([client_session, name], map: "constraint_5e")
                }
                
                model client_session_prot_mapper {
                  protocol_mapper_id                                        String         @db.VarChar(36)
                  client_session                                            String         @db.VarChar(36)
                  client_session_client_sessionToclient_session_prot_mapper client_session @relation(fields: [client_session], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_33a8sgqw18i532811v7o2dk89")
                
                  @@id([client_session, protocol_mapper_id], map: "constraint_cs_pmp_pk")
                }
                
                model client_session_role {
                  role_id                                            String         @db.VarChar(255)
                  client_session                                     String         @db.VarChar(36)
                  client_session_client_sessionToclient_session_role client_session @relation(fields: [client_session], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_11b7sgqw18i532811v7o2dv76")
                
                  @@id([client_session, role_id], map: "constraint_5")
                }
                
                model client_user_session_note {
                  name                                                    String         @db.VarChar(255)
                  value                                                   String?        @db.VarChar(2048)
                  client_session                                          String         @db.VarChar(36)
                  client_session_client_sessionToclient_user_session_note client_session @relation(fields: [client_session], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_cl_usr_ses_note")
                
                  @@id([client_session, name], map: "constr_cl_usr_ses_note")
                }
                
                model component {
                  id               String             @id(map: "constr_component_pk") @db.VarChar(36)
                  name             String?            @db.VarChar(255)
                  parent_id        String?            @db.VarChar(36)
                  provider_id      String?            @db.VarChar(36)
                  provider_type    String?            @db.VarChar(255)
                  realm_id         String?            @db.VarChar(36)
                  sub_type         String?            @db.VarChar(255)
                  realm            realm?             @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_component_realm")
                  component_config component_config[]
                
                  @@index([provider_type], map: "idx_component_provider_type")
                  @@index([realm_id], map: "idx_component_realm")
                }
                
                model component_config {
                  id           String    @id(map: "constr_component_config_pk") @db.VarChar(36)
                  component_id String    @db.VarChar(36)
                  name         String    @db.VarChar(255)
                  value        String?   @db.VarChar(4000)
                  component    component @relation(fields: [component_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_component_config")
                
                  @@index([component_id], map: "idx_compo_config_compo")
                }
                
                model composite_role {
                  composite                                              String        @db.VarChar(36)
                  child_role                                             String        @db.VarChar(36)
                  keycloak_role_composite_role_child_roleTokeycloak_role keycloak_role @relation("composite_role_child_roleTokeycloak_role", fields: [child_role], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_gr7thllb9lu8q4vqa4524jjy8")
                  keycloak_role_composite_role_compositeTokeycloak_role  keycloak_role @relation("composite_role_compositeTokeycloak_role", fields: [composite], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_a63wvekftu8jo1pnj81e7mce2")
                
                  @@id([composite, child_role], map: "constraint_composite_role")
                  @@index([composite], map: "idx_composite")
                  @@index([child_role], map: "idx_composite_child")
                }
                
                model credential {
                  id              String       @id(map: "constraint_f") @db.VarChar(36)
                  salt            Bytes?
                  type            String?      @db.VarChar(255)
                  user_id         String?      @db.VarChar(36)
                  created_date    BigInt?
                  user_label      String?      @db.VarChar(255)
                  secret_data     String?
                  credential_data String?
                  priority        Int?
                  user_entity     user_entity? @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_pfyr0glasqyl0dei3kl69r6v0")
                
                  @@index([user_id], map: "idx_user_credential")
                }
                
                /// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by the Prisma Client.
                model databasechangelog {
                  id            String   @db.VarChar(255)
                  author        String   @db.VarChar(255)
                  filename      String   @db.VarChar(255)
                  dateexecuted  DateTime @db.Timestamp(6)
                  orderexecuted Int
                  exectype      String   @db.VarChar(10)
                  md5sum        String?  @db.VarChar(35)
                  description   String?  @db.VarChar(255)
                  comments      String?  @db.VarChar(255)
                  tag           String?  @db.VarChar(255)
                  liquibase     String?  @db.VarChar(20)
                  contexts      String?  @db.VarChar(255)
                  labels        String?  @db.VarChar(255)
                  deployment_id String?  @db.VarChar(10)
                
                  @@ignore
                }
                
                model databasechangeloglock {
                  id          Int       @id(map: "pk_databasechangeloglock")
                  locked      Boolean
                  lockgranted DateTime? @db.Timestamp(6)
                  lockedby    String?   @db.VarChar(255)
                }
                
                model default_client_scope {
                  realm_id      String  @db.VarChar(36)
                  scope_id      String  @db.VarChar(36)
                  default_scope Boolean @default(false)
                  realm         realm   @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_r_def_cli_scope_realm")
                
                  @@id([realm_id, scope_id], map: "r_def_cli_scope_bind")
                  @@index([realm_id], map: "idx_defcls_realm")
                  @@index([scope_id], map: "idx_defcls_scope")
                }
                
                model event_entity {
                  id           String  @id(map: "constraint_4") @db.VarChar(36)
                  client_id    String? @db.VarChar(255)
                  details_json String? @db.VarChar(2550)
                  error        String? @db.VarChar(255)
                  ip_address   String? @db.VarChar(255)
                  realm_id     String? @db.VarChar(255)
                  session_id   String? @db.VarChar(255)
                  event_time   BigInt?
                  type         String? @db.VarChar(255)
                  user_id      String? @db.VarChar(255)
                
                  @@index([realm_id, event_time], map: "idx_event_time")
                }
                
                model federated_identity {
                  identity_provider  String      @db.VarChar(255)
                  realm_id           String?     @db.VarChar(36)
                  federated_user_id  String?     @db.VarChar(255)
                  federated_username String?     @db.VarChar(255)
                  token              String?
                  user_id            String      @db.VarChar(36)
                  user_entity        user_entity @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk404288b92ef007a6")
                
                  @@id([identity_provider, user_id], map: "constraint_40")
                  @@index([federated_user_id], map: "idx_fedidentity_feduser")
                  @@index([user_id], map: "idx_fedidentity_user")
                }
                
                model federated_user {
                  id                  String  @id(map: "constr_federated_user") @db.VarChar(255)
                  storage_provider_id String? @db.VarChar(255)
                  realm_id            String  @db.VarChar(36)
                }
                
                model fed_user_attribute {
                  id                  String  @id(map: "constr_fed_user_attr_pk") @db.VarChar(36)
                  name                String  @db.VarChar(255)
                  user_id             String  @db.VarChar(255)
                  realm_id            String  @db.VarChar(36)
                  storage_provider_id String? @db.VarChar(36)
                  value               String? @db.VarChar(2024)
                
                  @@index([user_id, realm_id, name], map: "idx_fu_attribute")
                }
                
                model fed_user_consent {
                  id                      String  @id(map: "constr_fed_user_consent_pk") @db.VarChar(36)
                  client_id               String? @db.VarChar(255)
                  user_id                 String  @db.VarChar(255)
                  realm_id                String  @db.VarChar(36)
                  storage_provider_id     String? @db.VarChar(36)
                  created_date            BigInt?
                  last_updated_date       BigInt?
                  client_storage_provider String? @db.VarChar(36)
                  external_client_id      String? @db.VarChar(255)
                
                  @@index([user_id, client_storage_provider, external_client_id], map: "idx_fu_cnsnt_ext")
                  @@index([user_id, client_id], map: "idx_fu_consent")
                  @@index([realm_id, user_id], map: "idx_fu_consent_ru")
                }
                
                model fed_user_consent_cl_scope {
                  user_consent_id String @db.VarChar(36)
                  scope_id        String @db.VarChar(36)
                
                  @@id([user_consent_id, scope_id], map: "constraint_fgrntcsnt_clsc_pm")
                }
                
                model fed_user_credential {
                  id                  String  @id(map: "constr_fed_user_cred_pk") @db.VarChar(36)
                  salt                Bytes?
                  type                String? @db.VarChar(255)
                  created_date        BigInt?
                  user_id             String  @db.VarChar(255)
                  realm_id            String  @db.VarChar(36)
                  storage_provider_id String? @db.VarChar(36)
                  user_label          String? @db.VarChar(255)
                  secret_data         String?
                  credential_data     String?
                  priority            Int?
                
                  @@index([user_id, type], map: "idx_fu_credential")
                  @@index([realm_id, user_id], map: "idx_fu_credential_ru")
                }
                
                model fed_user_group_membership {
                  group_id            String  @db.VarChar(36)
                  user_id             String  @db.VarChar(255)
                  realm_id            String  @db.VarChar(36)
                  storage_provider_id String? @db.VarChar(36)
                
                  @@id([group_id, user_id], map: "constr_fed_user_group")
                  @@index([user_id, group_id], map: "idx_fu_group_membership")
                  @@index([realm_id, user_id], map: "idx_fu_group_membership_ru")
                }
                
                model fed_user_required_action {
                  required_action     String  @default(" ") @db.VarChar(255)
                  user_id             String  @db.VarChar(255)
                  realm_id            String  @db.VarChar(36)
                  storage_provider_id String? @db.VarChar(36)
                
                  @@id([required_action, user_id], map: "constr_fed_required_action")
                  @@index([user_id, required_action], map: "idx_fu_required_action")
                  @@index([realm_id, user_id], map: "idx_fu_required_action_ru")
                }
                
                model fed_user_role_mapping {
                  role_id             String  @db.VarChar(36)
                  user_id             String  @db.VarChar(255)
                  realm_id            String  @db.VarChar(36)
                  storage_provider_id String? @db.VarChar(36)
                
                  @@id([role_id, user_id], map: "constr_fed_user_role")
                  @@index([user_id, role_id], map: "idx_fu_role_mapping")
                  @@index([realm_id, user_id], map: "idx_fu_role_mapping_ru")
                }
                
                model group_attribute {
                  id             String         @id(map: "constraint_group_attribute_pk") @default("sybase-needs-something-here") @db.VarChar(36)
                  name           String         @db.VarChar(255)
                  value          String?        @db.VarChar(255)
                  group_id       String         @db.VarChar(36)
                  keycloak_group keycloak_group @relation(fields: [group_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_group_attribute_group")
                
                  @@index([group_id], map: "idx_group_attr_group")
                }
                
                model group_role_mapping {
                  role_id        String         @db.VarChar(36)
                  group_id       String         @db.VarChar(36)
                  keycloak_group keycloak_group @relation(fields: [group_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_group_role_group")
                
                  @@id([role_id, group_id], map: "constraint_group_role")
                  @@index([group_id], map: "idx_group_role_mapp_group")
                }
                
                model identity_provider {
                  internal_id                String                     @id(map: "constraint_2b") @db.VarChar(36)
                  enabled                    Boolean                    @default(false)
                  provider_alias             String?                    @db.VarChar(255)
                  provider_id                String?                    @db.VarChar(255)
                  store_token                Boolean                    @default(false)
                  authenticate_by_default    Boolean                    @default(false)
                  realm_id                   String?                    @db.VarChar(36)
                  add_token_role             Boolean                    @default(true)
                  trust_email                Boolean                    @default(false)
                  first_broker_login_flow_id String?                    @db.VarChar(36)
                  post_broker_login_flow_id  String?                    @db.VarChar(36)
                  provider_display_name      String?                    @db.VarChar(255)
                  link_only                  Boolean                    @default(false)
                  realm                      realm?                     @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk2b4ebc52ae5c3b34")
                  identity_provider_config   identity_provider_config[]
                
                  @@unique([provider_alias, realm_id], map: "uk_2daelwnibji49avxsrtuf6xj33")
                  @@index([realm_id], map: "idx_ident_prov_realm")
                }
                
                model identity_provider_config {
                  identity_provider_id String            @db.VarChar(36)
                  value                String?
                  name                 String            @db.VarChar(255)
                  identity_provider    identity_provider @relation(fields: [identity_provider_id], references: [internal_id], onDelete: NoAction, onUpdate: NoAction, map: "fkdc4897cf864c4e43")
                
                  @@id([identity_provider_id, name], map: "constraint_d")
                }
                
                model identity_provider_mapper {
                  id                String              @id(map: "constraint_idpm") @db.VarChar(36)
                  name              String              @db.VarChar(255)
                  idp_alias         String              @db.VarChar(255)
                  idp_mapper_name   String              @db.VarChar(255)
                  realm_id          String              @db.VarChar(36)
                  realm             realm               @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_idpm_realm")
                  idp_mapper_config idp_mapper_config[]
                
                  @@index([realm_id], map: "idx_id_prov_mapp_realm")
                }
                
                model idp_mapper_config {
                  idp_mapper_id            String                   @db.VarChar(36)
                  value                    String?
                  name                     String                   @db.VarChar(255)
                  identity_provider_mapper identity_provider_mapper @relation(fields: [idp_mapper_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_idpmconfig")
                
                  @@id([idp_mapper_id, name], map: "constraint_idpmconfig")
                }
                
                model keycloak_group {
                  id                 String               @id(map: "constraint_group") @db.VarChar(36)
                  name               String?              @db.VarChar(255)
                  parent_group       String               @db.VarChar(36)
                  realm_id           String?              @db.VarChar(36)
                  group_attribute    group_attribute[]
                  group_role_mapping group_role_mapping[]
                
                  @@unique([realm_id, parent_group, name], map: "sibling_names")
                }
                
                model keycloak_role {
                  id                                                      String           @id(map: "constraint_a") @db.VarChar(36)
                  client_realm_constraint                                 String?          @db.VarChar(255)
                  client_role                                             Boolean          @default(false)
                  description                                             String?          @db.VarChar(255)
                  name                                                    String?          @db.VarChar(255)
                  realm_id                                                String?          @db.VarChar(255)
                  client                                                  String?          @db.VarChar(36)
                  realm                                                   String?          @db.VarChar(36)
                  realm_keycloak_roleTorealm                              realm?           @relation(fields: [realm], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_6vyqfe4cn4wlq8r6kt5vdsj5c")
                  composite_role_composite_role_child_roleTokeycloak_role composite_role[] @relation("composite_role_child_roleTokeycloak_role")
                  composite_role_composite_role_compositeTokeycloak_role  composite_role[] @relation("composite_role_compositeTokeycloak_role")
                  role_attribute                                          role_attribute[]
                
                  @@unique([name, client_realm_constraint], map: "UK_J3RWUVD56ONTGSUHOGM184WW2-2")
                  @@index([client], map: "idx_keycloak_role_client")
                  @@index([realm], map: "idx_keycloak_role_realm")
                }
                
                model migration_model {
                  id          String  @id(map: "constraint_migmod") @db.VarChar(36)
                  version     String? @db.VarChar(36)
                  update_time BigInt  @default(0)
                
                  @@index([update_time], map: "idx_update_time")
                }
                
                model offline_client_session {
                  user_session_id         String  @db.VarChar(36)
                  client_id               String  @db.VarChar(255)
                  offline_flag            String  @db.VarChar(4)
                  timestamp               Int?
                  data                    String?
                  client_storage_provider String  @default("local") @db.VarChar(36)
                  external_client_id      String  @default("local") @db.VarChar(255)
                
                  @@id([user_session_id, client_id, client_storage_provider, external_client_id, offline_flag], map: "constraint_offl_cl_ses_pk3")
                  @@index([client_id, offline_flag], map: "idx_offline_css_preload")
                  @@index([user_session_id], map: "idx_us_sess_id_on_cl_sess")
                }
                
                model offline_user_session {
                  user_session_id      String  @db.VarChar(36)
                  user_id              String  @db.VarChar(255)
                  realm_id             String  @db.VarChar(36)
                  created_on           Int
                  offline_flag         String  @db.VarChar(4)
                  data                 String?
                  last_session_refresh Int     @default(0)
                
                  @@id([user_session_id, offline_flag], map: "constraint_offl_us_ses_pk2")
                  @@index([user_id, realm_id, offline_flag], map: "idx_offline_uss_by_user")
                  @@index([realm_id, offline_flag, user_session_id], map: "idx_offline_uss_by_usersess")
                  @@index([created_on], map: "idx_offline_uss_createdon")
                  @@index([offline_flag, created_on, user_session_id], map: "idx_offline_uss_preload")
                }
                
                model policy_config {
                  policy_id              String                 @db.VarChar(36)
                  name                   String                 @db.VarChar(255)
                  value                  String?
                  resource_server_policy resource_server_policy @relation(fields: [policy_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fkdc34197cf864c4e43")
                
                  @@id([policy_id, name], map: "constraint_dpc")
                }
                
                model protocol_mapper {
                  id                     String                   @id(map: "constraint_pcm") @db.VarChar(36)
                  name                   String                   @db.VarChar(255)
                  protocol               String                   @db.VarChar(255)
                  protocol_mapper_name   String                   @db.VarChar(255)
                  client_id              String?                  @db.VarChar(36)
                  client_scope_id        String?                  @db.VarChar(36)
                  client                 client?                  @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_pcm_realm")
                  client_scope           client_scope?            @relation(fields: [client_scope_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_cli_scope_mapper")
                  protocol_mapper_config protocol_mapper_config[]
                
                  @@index([client_scope_id], map: "idx_clscope_protmap")
                  @@index([client_id], map: "idx_protocol_mapper_client")
                }
                
                model protocol_mapper_config {
                  protocol_mapper_id String          @db.VarChar(36)
                  value              String?
                  name               String          @db.VarChar(255)
                  protocol_mapper    protocol_mapper @relation(fields: [protocol_mapper_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_pmconfig")
                
                  @@id([protocol_mapper_id, name], map: "constraint_pmconfig")
                }
                
                model realm {
                  id                           String                      @id(map: "constraint_4a") @db.VarChar(36)
                  access_code_lifespan         Int?
                  user_action_lifespan         Int?
                  access_token_lifespan        Int?
                  account_theme                String?                     @db.VarChar(255)
                  admin_theme                  String?                     @db.VarChar(255)
                  email_theme                  String?                     @db.VarChar(255)
                  enabled                      Boolean                     @default(false)
                  events_enabled               Boolean                     @default(false)
                  events_expiration            BigInt?
                  login_theme                  String?                     @db.VarChar(255)
                  name                         String?                     @unique(map: "uk_orvsdmla56612eaefiq6wl5oi") @db.VarChar(255)
                  not_before                   Int?
                  password_policy              String?                     @db.VarChar(2550)
                  registration_allowed         Boolean                     @default(false)
                  remember_me                  Boolean                     @default(false)
                  reset_password_allowed       Boolean                     @default(false)
                  social                       Boolean                     @default(false)
                  ssl_required                 String?                     @db.VarChar(255)
                  sso_idle_timeout             Int?
                  sso_max_lifespan             Int?
                  update_profile_on_soc_login  Boolean                     @default(false)
                  verify_email                 Boolean                     @default(false)
                  master_admin_client          String?                     @db.VarChar(36)
                  login_lifespan               Int?
                  internationalization_enabled Boolean                     @default(false)
                  default_locale               String?                     @db.VarChar(255)
                  reg_email_as_username        Boolean                     @default(false)
                  admin_events_enabled         Boolean                     @default(false)
                  admin_events_details_enabled Boolean                     @default(false)
                  edit_username_allowed        Boolean                     @default(false)
                  otp_policy_counter           Int?                        @default(0)
                  otp_policy_window            Int?                        @default(1)
                  otp_policy_period            Int?                        @default(30)
                  otp_policy_digits            Int?                        @default(6)
                  otp_policy_alg               String?                     @default("HmacSHA1") @db.VarChar(36)
                  otp_policy_type              String?                     @default("totp") @db.VarChar(36)
                  browser_flow                 String?                     @db.VarChar(36)
                  registration_flow            String?                     @db.VarChar(36)
                  direct_grant_flow            String?                     @db.VarChar(36)
                  reset_credentials_flow       String?                     @db.VarChar(36)
                  client_auth_flow             String?                     @db.VarChar(36)
                  offline_session_idle_timeout Int?                        @default(0)
                  revoke_refresh_token         Boolean                     @default(false)
                  access_token_life_implicit   Int?                        @default(0)
                  login_with_email_allowed     Boolean                     @default(true)
                  duplicate_emails_allowed     Boolean                     @default(false)
                  docker_auth_flow             String?                     @db.VarChar(36)
                  refresh_token_max_reuse      Int?                        @default(0)
                  allow_user_managed_access    Boolean                     @default(false)
                  sso_max_lifespan_remember_me Int                         @default(0)
                  sso_idle_timeout_remember_me Int                         @default(0)
                  default_role                 String?                     @db.VarChar(255)
                  authentication_execution     authentication_execution[]
                  authentication_flow          authentication_flow[]
                  authenticator_config         authenticator_config[]
                  client_initial_access        client_initial_access[]
                  component                    component[]
                  default_client_scope         default_client_scope[]
                  identity_provider            identity_provider[]
                  identity_provider_mapper     identity_provider_mapper[]
                  keycloak_role                keycloak_role[]
                  realm_attribute              realm_attribute[]
                  realm_default_groups         realm_default_groups[]
                  realm_enabled_event_types    realm_enabled_event_types[]
                  realm_events_listeners       realm_events_listeners[]
                  realm_required_credential    realm_required_credential[]
                  realm_smtp_config            realm_smtp_config[]
                  realm_supported_locales      realm_supported_locales[]
                  required_action_provider     required_action_provider[]
                  user_federation_mapper       user_federation_mapper[]
                  user_federation_provider     user_federation_provider[]
                
                  @@index([master_admin_client], map: "idx_realm_master_adm_cli")
                }
                
                model realm_attribute {
                  name     String  @db.VarChar(255)
                  realm_id String  @db.VarChar(36)
                  value    String?
                  realm    realm   @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_8shxd6l3e9atqukacxgpffptw")
                
                  @@id([name, realm_id], map: "constraint_9")
                  @@index([realm_id], map: "idx_realm_attr_realm")
                }
                
                model realm_default_groups {
                  realm_id String @db.VarChar(36)
                  group_id String @unique(map: "con_group_id_def_groups") @db.VarChar(36)
                  realm    realm  @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_def_groups_realm")
                
                  @@id([realm_id, group_id], map: "constr_realm_default_groups")
                  @@index([realm_id], map: "idx_realm_def_grp_realm")
                }
                
                model realm_enabled_event_types {
                  realm_id String @db.VarChar(36)
                  value    String @db.VarChar(255)
                  realm    realm  @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_h846o4h0w8epx5nwedrf5y69j")
                
                  @@id([realm_id, value], map: "constr_realm_enabl_event_types")
                  @@index([realm_id], map: "idx_realm_evt_types_realm")
                }
                
                model realm_events_listeners {
                  realm_id String @db.VarChar(36)
                  value    String @db.VarChar(255)
                  realm    realm  @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_h846o4h0w8epx5nxev9f5y69j")
                
                  @@id([realm_id, value], map: "constr_realm_events_listeners")
                  @@index([realm_id], map: "idx_realm_evt_list_realm")
                }
                
                model realm_localizations {
                  realm_id String @db.VarChar(255)
                  locale   String @db.VarChar(255)
                  texts    String
                
                  @@id([realm_id, locale])
                }
                
                model realm_required_credential {
                  type       String  @db.VarChar(255)
                  form_label String? @db.VarChar(255)
                  input      Boolean @default(false)
                  secret     Boolean @default(false)
                  realm_id   String  @db.VarChar(36)
                  realm      realm   @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_5hg65lybevavkqfki3kponh9v")
                
                  @@id([realm_id, type], map: "constraint_92")
                }
                
                model realm_smtp_config {
                  realm_id String  @db.VarChar(36)
                  value    String? @db.VarChar(255)
                  name     String  @db.VarChar(255)
                  realm    realm   @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_70ej8xdxgxd0b9hh6180irr0o")
                
                  @@id([realm_id, name], map: "constraint_e")
                }
                
                model realm_supported_locales {
                  realm_id String @db.VarChar(36)
                  value    String @db.VarChar(255)
                  realm    realm  @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_supported_locales_realm")
                
                  @@id([realm_id, value], map: "constr_realm_supported_locales")
                  @@index([realm_id], map: "idx_realm_supp_local_realm")
                }
                
                model redirect_uris {
                  client_id String @db.VarChar(36)
                  value     String @db.VarChar(255)
                  client    client @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_1burs8pb4ouj97h5wuppahv9f")
                
                  @@id([client_id, value], map: "constraint_redirect_uris")
                  @@index([client_id], map: "idx_redir_uri_client")
                }
                
                model required_action_config {
                  required_action_id String  @db.VarChar(36)
                  value              String?
                  name               String  @db.VarChar(255)
                
                  @@id([required_action_id, name], map: "constraint_req_act_cfg_pk")
                }
                
                model required_action_provider {
                  id             String  @id(map: "constraint_req_act_prv_pk") @db.VarChar(36)
                  alias          String? @db.VarChar(255)
                  name           String? @db.VarChar(255)
                  realm_id       String? @db.VarChar(36)
                  enabled        Boolean @default(false)
                  default_action Boolean @default(false)
                  provider_id    String? @db.VarChar(255)
                  priority       Int?
                  realm          realm?  @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_req_act_realm")
                
                  @@index([realm_id], map: "idx_req_act_prov_realm")
                }
                
                model resource_attribute {
                  id                       String                   @id(map: "res_attr_pk") @default("sybase-needs-something-here") @db.VarChar(36)
                  name                     String                   @db.VarChar(255)
                  value                    String?                  @db.VarChar(255)
                  resource_id              String                   @db.VarChar(36)
                  resource_server_resource resource_server_resource @relation(fields: [resource_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_5hrm2vlf9ql5fu022kqepovbr")
                }
                
                model resource_policy {
                  resource_id              String                   @db.VarChar(36)
                  policy_id                String                   @db.VarChar(36)
                  resource_server_policy   resource_server_policy   @relation(fields: [policy_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrpp213xcx4wnkog82ssrfy")
                  resource_server_resource resource_server_resource @relation(fields: [resource_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrpos53xcx4wnkog82ssrfy")
                
                  @@id([resource_id, policy_id], map: "constraint_farsrpp")
                  @@index([policy_id], map: "idx_res_policy_policy")
                }
                
                model resource_scope {
                  resource_id              String                   @db.VarChar(36)
                  scope_id                 String                   @db.VarChar(36)
                  resource_server_resource resource_server_resource @relation(fields: [resource_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrpos13xcx4wnkog82ssrfy")
                  resource_server_scope    resource_server_scope    @relation(fields: [scope_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrps213xcx4wnkog82ssrfy")
                
                  @@id([resource_id, scope_id], map: "constraint_farsrsp")
                  @@index([scope_id], map: "idx_res_scope_scope")
                }
                
                model resource_server {
                  id                          String                        @id(map: "pk_resource_server") @db.VarChar(36)
                  allow_rs_remote_mgmt        Boolean                       @default(false)
                  policy_enforce_mode         String                        @db.VarChar(15)
                  decision_strategy           Int                           @default(1) @db.SmallInt
                  resource_server_perm_ticket resource_server_perm_ticket[]
                  resource_server_policy      resource_server_policy[]
                  resource_server_resource    resource_server_resource[]
                  resource_server_scope       resource_server_scope[]
                }
                
                model resource_server_perm_ticket {
                  id                       String                   @id(map: "constraint_fapmt") @db.VarChar(36)
                  owner                    String                   @db.VarChar(255)
                  requester                String                   @db.VarChar(255)
                  created_timestamp        BigInt
                  granted_timestamp        BigInt?
                  resource_id              String                   @db.VarChar(36)
                  scope_id                 String?                  @db.VarChar(36)
                  resource_server_id       String                   @db.VarChar(36)
                  policy_id                String?                  @db.VarChar(36)
                  resource_server_policy   resource_server_policy?  @relation(fields: [policy_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrpo2128cx4wnkog82ssrfy")
                  resource_server_resource resource_server_resource @relation(fields: [resource_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrho213xcx4wnkog83sspmt")
                  resource_server          resource_server          @relation(fields: [resource_server_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrho213xcx4wnkog82sspmt")
                  resource_server_scope    resource_server_scope?   @relation(fields: [scope_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrho213xcx4wnkog84sspmt")
                
                  @@unique([owner, requester, resource_server_id, resource_id, scope_id], map: "uk_frsr6t700s9v50bu18ws5pmt")
                }
                
                model resource_server_policy {
                  id                                                                               String                        @id(map: "constraint_farsrp") @db.VarChar(36)
                  name                                                                             String                        @db.VarChar(255)
                  description                                                                      String?                       @db.VarChar(255)
                  type                                                                             String                        @db.VarChar(255)
                  decision_strategy                                                                String?                       @db.VarChar(20)
                  logic                                                                            String?                       @db.VarChar(20)
                  resource_server_id                                                               String                        @db.VarChar(36)
                  owner                                                                            String?                       @db.VarChar(255)
                  resource_server                                                                  resource_server               @relation(fields: [resource_server_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrpo213xcx4wnkog82ssrfy")
                  associated_policy_associated_policy_associated_policy_idToresource_server_policy associated_policy[]           @relation("associated_policy_associated_policy_idToresource_server_policy")
                  associated_policy_associated_policy_policy_idToresource_server_policy            associated_policy[]           @relation("associated_policy_policy_idToresource_server_policy")
                  policy_config                                                                    policy_config[]
                  resource_policy                                                                  resource_policy[]
                  resource_server_perm_ticket                                                      resource_server_perm_ticket[]
                  scope_policy                                                                     scope_policy[]
                
                  @@unique([name, resource_server_id], map: "uk_frsrpt700s9v50bu18ws5ha6")
                  @@index([resource_server_id], map: "idx_res_serv_pol_res_serv")
                }
                
                model resource_server_resource {
                  id                          String                        @id(map: "constraint_farsr") @db.VarChar(36)
                  name                        String                        @db.VarChar(255)
                  type                        String?                       @db.VarChar(255)
                  icon_uri                    String?                       @db.VarChar(255)
                  owner                       String                        @db.VarChar(255)
                  resource_server_id          String                        @db.VarChar(36)
                  owner_managed_access        Boolean                       @default(false)
                  display_name                String?                       @db.VarChar(255)
                  resource_server             resource_server               @relation(fields: [resource_server_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrho213xcx4wnkog82ssrfy")
                  resource_attribute          resource_attribute[]
                  resource_policy             resource_policy[]
                  resource_scope              resource_scope[]
                  resource_server_perm_ticket resource_server_perm_ticket[]
                  resource_uris               resource_uris[]
                
                  @@unique([name, owner, resource_server_id], map: "uk_frsr6t700s9v50bu18ws5ha6")
                  @@index([resource_server_id], map: "idx_res_srv_res_res_srv")
                }
                
                model resource_server_scope {
                  id                          String                        @id(map: "constraint_farsrs") @db.VarChar(36)
                  name                        String                        @db.VarChar(255)
                  icon_uri                    String?                       @db.VarChar(255)
                  resource_server_id          String                        @db.VarChar(36)
                  display_name                String?                       @db.VarChar(255)
                  resource_server             resource_server               @relation(fields: [resource_server_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrso213xcx4wnkog82ssrfy")
                  resource_scope              resource_scope[]
                  resource_server_perm_ticket resource_server_perm_ticket[]
                  scope_policy                scope_policy[]
                
                  @@unique([name, resource_server_id], map: "uk_frsrst700s9v50bu18ws5ha6")
                  @@index([resource_server_id], map: "idx_res_srv_scope_res_srv")
                }
                
                model resource_uris {
                  resource_id              String                   @db.VarChar(36)
                  value                    String                   @db.VarChar(255)
                  resource_server_resource resource_server_resource @relation(fields: [resource_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_resource_server_uris")
                
                  @@id([resource_id, value], map: "constraint_resour_uris_pk")
                }
                
                model role_attribute {
                  id            String        @id(map: "constraint_role_attribute_pk") @db.VarChar(36)
                  role_id       String        @db.VarChar(36)
                  name          String        @db.VarChar(255)
                  value         String?       @db.VarChar(255)
                  keycloak_role keycloak_role @relation(fields: [role_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_role_attribute_id")
                
                  @@index([role_id], map: "idx_role_attribute")
                }
                
                model scope_mapping {
                  client_id String @db.VarChar(36)
                  role_id   String @db.VarChar(36)
                  client    client @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_ouse064plmlr732lxjcn1q5f1")
                
                  @@id([client_id, role_id], map: "constraint_81")
                  @@index([role_id], map: "idx_scope_mapping_role")
                }
                
                model scope_policy {
                  scope_id               String                 @db.VarChar(36)
                  policy_id              String                 @db.VarChar(36)
                  resource_server_policy resource_server_policy @relation(fields: [policy_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrasp13xcx4wnkog82ssrfy")
                  resource_server_scope  resource_server_scope  @relation(fields: [scope_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_frsrpass3xcx4wnkog82ssrfy")
                
                  @@id([scope_id, policy_id], map: "constraint_farsrsps")
                  @@index([policy_id], map: "idx_scope_policy_policy")
                }
                
                model user_attribute {
                  name        String      @db.VarChar(255)
                  value       String?     @db.VarChar(255)
                  user_id     String      @db.VarChar(36)
                  id          String      @id(map: "constraint_user_attribute_pk") @default("sybase-needs-something-here") @db.VarChar(36)
                  user_entity user_entity @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_5hrm2vlf9ql5fu043kqepovbr")
                
                  @@index([user_id], map: "idx_user_attribute")
                  @@index([name, value], map: "idx_user_attribute_name")
                }
                
                model user_consent {
                  id                        String                      @id(map: "constraint_grntcsnt_pm") @db.VarChar(36)
                  client_id                 String?                     @db.VarChar(255)
                  user_id                   String                      @db.VarChar(36)
                  created_date              BigInt?
                  last_updated_date         BigInt?
                  client_storage_provider   String?                     @db.VarChar(36)
                  external_client_id        String?                     @db.VarChar(255)
                  user_entity               user_entity                 @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_grntcsnt_user")
                  user_consent_client_scope user_consent_client_scope[]
                
                  @@unique([client_id, client_storage_provider, external_client_id, user_id], map: "uk_jkuwuvd56ontgsuhogm8uewrt")
                  @@index([user_id], map: "idx_user_consent")
                }
                
                model user_consent_client_scope {
                  user_consent_id String       @db.VarChar(36)
                  scope_id        String       @db.VarChar(36)
                  user_consent    user_consent @relation(fields: [user_consent_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_grntcsnt_clsc_usc")
                
                  @@id([user_consent_id, scope_id], map: "constraint_grntcsnt_clsc_pm")
                  @@index([user_consent_id], map: "idx_usconsent_clscope")
                }
                
                model user_entity {
                  id                          String                  @id(map: "constraint_fb") @db.VarChar(36)
                  email                       String?                 @db.VarChar(255)
                  email_constraint            String?                 @db.VarChar(255)
                  email_verified              Boolean                 @default(false)
                  enabled                     Boolean                 @default(false)
                  federation_link             String?                 @db.VarChar(255)
                  first_name                  String?                 @db.VarChar(255)
                  last_name                   String?                 @db.VarChar(255)
                  realm_id                    String?                 @db.VarChar(255)
                  username                    String?                 @db.VarChar(255)
                  created_timestamp           BigInt?
                  service_account_client_link String?                 @db.VarChar(255)
                  not_before                  Int                     @default(0)
                  credential                  credential[]
                  federated_identity          federated_identity[]
                  user_attribute              user_attribute[]
                  user_consent                user_consent[]
                  user_group_membership       user_group_membership[]
                  user_required_action        user_required_action[]
                  user_role_mapping           user_role_mapping[]
                
                  @@unique([realm_id, email_constraint], map: "uk_dykn684sl8up1crfei6eckhd7")
                  @@unique([realm_id, username], map: "uk_ru8tt6t700s9v50bu18ws5ha6")
                  @@index([email], map: "idx_user_email")
                }
                
                model user_federation_config {
                  user_federation_provider_id String                   @db.VarChar(36)
                  value                       String?                  @db.VarChar(255)
                  name                        String                   @db.VarChar(255)
                  user_federation_provider    user_federation_provider @relation(fields: [user_federation_provider_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_t13hpu1j94r2ebpekr39x5eu5")
                
                  @@id([user_federation_provider_id, name], map: "constraint_f9")
                }
                
                model user_federation_mapper {
                  id                            String                          @id(map: "constraint_fedmapperpm") @db.VarChar(36)
                  name                          String                          @db.VarChar(255)
                  federation_provider_id        String                          @db.VarChar(36)
                  federation_mapper_type        String                          @db.VarChar(255)
                  realm_id                      String                          @db.VarChar(36)
                  user_federation_provider      user_federation_provider        @relation(fields: [federation_provider_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_fedmapperpm_fedprv")
                  realm                         realm                           @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_fedmapperpm_realm")
                  user_federation_mapper_config user_federation_mapper_config[]
                
                  @@index([federation_provider_id], map: "idx_usr_fed_map_fed_prv")
                  @@index([realm_id], map: "idx_usr_fed_map_realm")
                }
                
                model user_federation_mapper_config {
                  user_federation_mapper_id String                 @db.VarChar(36)
                  value                     String?                @db.VarChar(255)
                  name                      String                 @db.VarChar(255)
                  user_federation_mapper    user_federation_mapper @relation(fields: [user_federation_mapper_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_fedmapper_cfg")
                
                  @@id([user_federation_mapper_id, name], map: "constraint_fedmapper_cfg_pm")
                }
                
                model user_federation_provider {
                  id                     String                   @id(map: "constraint_5c") @db.VarChar(36)
                  changed_sync_period    Int?
                  display_name           String?                  @db.VarChar(255)
                  full_sync_period       Int?
                  last_sync              Int?
                  priority               Int?
                  provider_name          String?                  @db.VarChar(255)
                  realm_id               String?                  @db.VarChar(36)
                  realm                  realm?                   @relation(fields: [realm_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_1fj32f6ptolw2qy60cd8n01e8")
                  user_federation_config user_federation_config[]
                  user_federation_mapper user_federation_mapper[]
                
                  @@index([realm_id], map: "idx_usr_fed_prv_realm")
                }
                
                model user_group_membership {
                  group_id    String      @db.VarChar(36)
                  user_id     String      @db.VarChar(36)
                  user_entity user_entity @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_user_group_user")
                
                  @@id([group_id, user_id], map: "constraint_user_group")
                  @@index([user_id], map: "idx_user_group_mapping")
                }
                
                model username_login_failure {
                  realm_id                String  @db.VarChar(36)
                  username                String  @db.VarChar(255)
                  failed_login_not_before Int?
                  last_failure            BigInt?
                  last_ip_failure         String? @db.VarChar(255)
                  num_failures            Int?
                
                  @@id([realm_id, username], map: "CONSTRAINT_17-2")
                }
                
                model user_required_action {
                  user_id         String      @db.VarChar(36)
                  required_action String      @default(" ") @db.VarChar(255)
                  user_entity     user_entity @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_6qj3w1jw9cvafhe19bwsiuvmd")
                
                  @@id([required_action, user_id], map: "constraint_required_action")
                  @@index([user_id], map: "idx_user_reqactions")
                }
                
                model user_role_mapping {
                  role_id     String      @db.VarChar(255)
                  user_id     String      @db.VarChar(36)
                  user_entity user_entity @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_c4fqv34p1mbylloxang7b1q3l")
                
                  @@id([role_id, user_id], map: "constraint_c")
                  @@index([user_id], map: "idx_user_role_mapping")
                }
                
                model user_session {
                  id                   String              @id(map: "constraint_57") @db.VarChar(36)
                  auth_method          String?             @db.VarChar(255)
                  ip_address           String?             @db.VarChar(255)
                  last_session_refresh Int?
                  login_username       String?             @db.VarChar(255)
                  realm_id             String?             @db.VarChar(255)
                  remember_me          Boolean             @default(false)
                  started              Int?
                  user_id              String?             @db.VarChar(255)
                  user_session_state   Int?
                  broker_session_id    String?             @db.VarChar(255)
                  broker_user_id       String?             @db.VarChar(255)
                  client_session       client_session[]
                  user_session_note    user_session_note[]
                }
                
                model user_session_note {
                  user_session                                 String       @db.VarChar(36)
                  name                                         String       @db.VarChar(255)
                  value                                        String?      @db.VarChar(2048)
                  user_session_user_sessionTouser_session_note user_session @relation(fields: [user_session], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk5edfb00ff51d3472")
                
                  @@id([user_session, name], map: "constraint_usn_pk")
                }
                
                model web_origins {
                  client_id String @db.VarChar(36)
                  value     String @db.VarChar(255)
                  client    client @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_lojpho213xcx4wnkog82ssrfy")
                
                  @@id([client_id, value], map: "constraint_web_origins")
                  @@index([client_id], map: "idx_web_orig_client")
                }
              
            

Setting up Keycloak with React


  • 1. Similar to NestJS keycloak setup, create a new client called react -web-app
  • 2. For the front-end client (react-web-app) we set  Access Type to public and Redirect URL to application url.
  • 3. Make similar roles as NestJS client and also connect users with the same roles.

Front-end Application Configuration (React.js)


Keycloak Client Adapers : Keycloak client adapters are libraries that make it very easy to secure applications and services with Keycloak. We call these ‘adapters’ rather than libraries as they provide a tight integration to the underlying platform and framework. This makes adapters easy to use and require less boilerplate code than what is typically required by a library.


1. Install keycloak-js dependency to the react app.npm install keycloak-js 

2. We can implement our authentication logic using the standard Keycloak APIs init method call which returns a promise containing authentication details.

Keycloak install keycloak-js

login-required is one of two possible values to be passed as an onLoad parameter. This will authenticate the client if the user has already logged into Keycloak, or redirect the browser to the login page if he hasn’t. The other option is check-sso : this will only authenticate the client if the user has already logged in, otherwise the client will remain unauthenticated without automatic redirection.

Here we need to store the access token received from authenticated Keycolak object to call back-end Rest APIs. We can put it into either global variable or local storage. We are proceeding with the global variable scenario.

Keycloak login-required

4. After user authenticated to the react-web-app , For each Back-end API call we have to send the access token in the header. Let’s add it to an interceptor.

Keycloak user authenticated

add the token stored in the global variable


3. Get the client related info JSON from the Keycloak server and put it into the public/keycloak.json .

Keycloak public/keycloak.json
Keycloak json

That’s it for the front-end application configuration.


Keycloakify


  • Keycloak does not allow the user to have their custom login pages for any of the authentication flow. This might be the only con for working with keycloak.
  • Although keycloak does enable you to work with themes, the themes and ftl based themes which require a lot of HTML, CSS and JS work for them to show a proper UI.
  • Keycloakify is package that helps in making those login pages with utilising react.
  • Docs - https://docs.keycloakify.dev/
  • After generating login files, you will have to attach a docker volume based on existing theme location and docker theme location. Check the above tutorial for clarification

Setup KeyCloak on EC2


  • Use the same docker-compose file as you are using for development.
  • You will an https required error as ec2 does not allow unsecured connection, follow this post to fix - https://stackoverflow.com/questions/49859066/keycloak-docker-https-required
  • Only use this setup on development between frontend and backend teams.
  • Keycloak docs have the proper deployment guide, follow that that final deployment

Read more blogs here