Skip to main content

Keycloak Authentication - Setup on Debian 12, Production Mode, TLS Encryption, CLI Administration, Keycloak Quickstart Node.js Example Application

1746 words·
Keycloak Debian Node.js Java
Table of Contents

In this tutorial I’m using a Debian 12 server with the following IP “192.168.30.70” for the Keycloak deployment and another Debian 12 server with the IP “192.168.30.72” for the test application.

Bare Metal Version
#

Install Java
#

# Download and extract the OpenJDK Archive
cd /tmp && wget https://download.java.net/java/GA/jdk22.0.1/c7ec1332f7bb44aeba2eb341ae18aca4/8/GPL/openjdk-22.0.1_linux-x64_bin.tar.gz &&
tar -xzf openjdk-22.0.1_linux-x64_bin.tar.gz

# Move files
sudo mkdir -p /usr/lib/jvm &&
sudo mv /tmp/jdk-22.0.1 /usr/lib/jvm/

# Set environment variables: For the current user
echo 'export JAVA_HOME=/usr/lib/jvm/jdk-22.0.1' >> ~/.bashrc &&
echo 'export PATH=$PATH:$JAVA_HOME/bin' >> ~/.bashrc

# Apply the changes
source ~/.bashrc
# Verify installation
java -version

Install Keycloak
#

# Find latest Keycloak version
https://www.keycloak.org/downloads
# Install Keycloak
cd /tmp && wget https://github.com/keycloak/keycloak/releases/download/24.0.3/keycloak-24.0.3.tar.gz &&
tar -xvzf keycloak-24.0.3.tar.gz &&
sudo mv keycloak-24.0.3 /opt/keycloak

keycloak.conf
#

# Open the configuration file
sudo vi /opt/keycloak/conf/keycloak.conf

Let’s Encrypt Version:

# Set the hostname to your FQDN
hostname=keycloak.jklug.work

# Define HTTP port
http-port=8080

# Define HTTPS port
https-port=8443

# Paths to Let's Encrypt certificate
https-certificate-file=/etc/letsencrypt/live/keycloak.jklug.work/fullchain.pem
https-certificate-key-file=/etc/letsencrypt/live/keycloak.jklug.work/privkey.pem

# Enable HTTPS and disable HTTP if desired
https-enabled=true
http-enabled=true

Self Signed Certificate Version (For Testing):

# Allow all hostnames
hostname-strict=false

# Define HTTP port
http-port=8080

# Define HTTPS port
https-port=8443

# Define certificate path
https-certificate-file=${kc.home.dir}/conf/kc.crt.pem
https-certificate-key-file=${kc.home.dir}/conf/kc.key.pem

# Enable HTTPS and disable HTTP if desired
https-enabled=true
http-enabled=true

Certificate
#

Let’s Encrypt Certificate
#

Note: I’m using a Let’s Encrypt Wildcard Certificate in this tutorial.

# Create certificate directory and place the certificates
sudo mkdir -p /etc/letsencrypt/live/keycloak.jklug.work/

Create a Self Signed Certificate
#

Note: Without the self signed certificate it’s not possible to start Keycloak, even if you want to use HTTP only.

# Create self signed certificate
cd /opt/keycloak/conf && openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout kc.key.pem -out kc.crt.pem

System User and Group
#

# Create a system user and group for Keycloak
sudo addgroup --system keycloak &&
sudo adduser --system --ingroup keycloak --shell /sbin/nologin keycloak

# Change ownership
sudo chown -R keycloak:keycloak /opt/keycloak

Note: “Not creating `/nonexistent’” means no home directory was created for the user.

Build Configuration
#

# Optional: List script options
/opt/keycloak/bin/kc.sh --help
# Start Keycloak & build the configuration files
sudo -E -u keycloak /opt/keycloak/bin/kc.sh start

# Stop Keycloak
Strg + c

-E This option tells sudo to preserve the environment / environment variables

Verify Configuration file:

# Optional: Verify configuration file
sudo -E -u keycloak /opt/keycloak/bin/kc.sh show-config

Systemd Service Unit
#

# Configure Keycloak to run in production mode
sudo vi /etc/systemd/system/keycloak.service
[Unit]
Description=The Keycloak Server
After=syslog.target network.target
Before=httpd.service

[Service]
Environment=LAUNCH_JBOSS_IN_BACKGROUND=1
Environment="JAVA_HOME=/usr/lib/jvm/jdk-22.0.1"
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$JAVA_HOME/bin"
Environment="KEYCLOAK_ADMIN=admin"
Environment="KEYCLOAK_ADMIN_PASSWORD=myadminpw"
User=keycloak
Group=keycloak
LimitNOFILE=102642
PIDFile=/run/keycloak/keycloak.pid
ExecStart=/opt/keycloak/bin/kc.sh start --optimized
StandardOutput=journal

[Install]
WantedBy=multi-user.target

Note: Define the admin user and password for the webinterface.

Start and Enable Service
#

# Reload systemd daemon / reload configuration files
sudo systemctl daemon-reload

# Start service and enable after boot
sudo systemctl enable keycloak && sudo systemctl start keycloak

# Check status
sudo systemctl status keycloak

Check Logs
#

# Check logs
sudo journalctl -u keycloak

# Check logs: Last 20 lines
sudo journalctl -u keycloak -n 20

Keycloak
#

Access the Admin Console
#

## Access Keycloak Admin Console: HTTP
http://192.168.30.70:8080/

## Access Keycloak Admin Console: HTTPS
https://keycloak.jklug.work:8443/

# Login with user & pw defined in the service unit

Keycloak CLI
#

Set Env Variable
#

# Set env variable
echo 'export KC_HOME=/opt/keycloak/bin' >> ~/.bashrc

# Apply the changes
source ~/.bashrc

Connect to the API
#

# Login to admin CLI: This prompts for the admin pw
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh config credentials --server http://192.168.30.70:8080 --realm master --user admin --config /opt/keycloak/config/kcadm.config

Create a Realm
#

  • Each realm is isolated from the other realms and has its own configuration, applications and users.

  • The name of the realm is used in it’s URL, so don’t use any special characters.

# Create a new realm
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh create realms -s realm=jkw-realm-1 -s enabled=true -o --config /opt/keycloak/config/kcadm.config

Group
#

Create Group
#

# Create a group
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh create groups -r jkw-realm-1 -s name=group-1 --config /opt/keycloak/config/kcadm.config

# Shell output:
Created new group with id '5c220a73-bfb1-4401-a705-79707005fc98

Retrieve Group ID
#

# Retrieve group ID
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh get groups -r jkw-realm-1 --fields id,name  --config /opt/keycloak/config/kcadm.config

# Shell output:
[ {
  "id" : "5c220a73-bfb1-4401-a705-79707005fc98",
  "name" : "group-1"
} ]

Realm Role
#

Create Realm Role
#

# Create Realm Role
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh create roles -r jkw-realm-1 -s name=jkw-role-1 -s description="jkw role 1" --config /opt/keycloak/config/kcadm.config

# Shell output:
Created new role with id 'jkw-role-1'

Retrieve Realm Role ID
#

# Retrieve realm role ID
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh get roles -r jkw-realm-1 --fields id,name --config /opt/keycloak/config/kcadm.config

# Shell output:
[ {
  "id" : "c3450c71-6fdf-4060-bd80-ff98fafc8eaf",
  "name" : "jkw-role-1"
}, {
  "id" : "74274001-f67c-4297-b122-ca0906ed0af8",
  "name" : "default-roles-jkw-realm-1"
}, {
  "id" : "4b781f46-5f56-4e0e-9077-03bdc7d72c9f",
  "name" : "offline_access"
}, {
  "id" : "38cedfc6-4ecc-497c-93f5-06735f3c8bf0",
  "name" : "uma_authorization"
} ]

User
#

Create User
#

# Create & and enable a user in a the new realm
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh create users -r jkw-realm-1 -s username=jkw-01 -s enabled=true --config /opt/keycloak/config/kcadm.config

# Shell output:
Created new user with id '69f5d537-2eda-411f-8774-d636651a1a66'

Set User PW
#

# Define a password for the user
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh set-password -r jkw-realm-1 --username jkw-01 --new-password 'myuserpw' --config /opt/keycloak/config/kcadm.config

Retrieve User ID
#

# Retrieve user ID
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh get users -r jkw-realm-1 --query username=jkw-01 --config /opt/keycloak/config/kcadm.config

# Shell output:
[ {
  "id" : "69f5d537-2eda-411f-8774-d636651a1a66",
  "username" : "jkw-01",
  "firstName" : "testuser",
  "lastName" : "bla",
  "email" : "juergen@jklug.work",
  "emailVerified" : false,
  "createdTimestamp" : 1715121272348,
  "enabled" : true,
  "totp" : false,
  "disableableCredentialTypes" : [ ],
  "requiredActions" : [ ],
  "notBefore" : 0,
  "access" : {
    "manageGroupMembership" : true,
    "view" : true,
    "mapRoles" : true,
    "impersonate" : true,
    "manage" : true
  }
} ]

Add User to Group
#

# Add user to group: Syntax
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh update users/<userID>/groups/<groupID> -r jkw-realm-1 --config /opt/keycloak/config/kcadm.config

# Add user to group: Example
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh update users/69f5d537-2eda-411f-8774-d636651a1a66/groups/5c220a73-bfb1-4401-a705-79707005fc98 -r jkw-realm-1 --config /opt/keycloak/config/kcadm.config

Assign Realm Role to User
#

# Assign realm role to user: Syntax
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh add-roles -r jkw-realm-1 --uusername <username> --rolename <role-name>

# Assign realm role to user: Example
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh add-roles -r jkw-realm-1 --uusername jkw-01 --rolename jkw-role-1 --config /opt/keycloak/config/kcadm.config

Test User / Keycloak Account Console
#

# Open the Keycloak Account Console: HTTP
http://192.168.30.70:8080/realms/jkw-realm-1/account

# Open the Keycloak Account Console: HTTPS
https://keycloak.jklug.work:8443/realms/jkw-realm-1/account

# User:
jkw-01

# Password:
myuserpw

Node.js Test Application
#

Keycloak is providing example applications in several languages to test the Keycloak authentication, in this tutorial I’m using the Node.js version.

Keycloak Realm
#

Import the realm configuration file realm-import.json from the “Create realm” section in the Admin Console to create a new realm called quickstart, this also creates the following users and roles:

| Username | Password | Roles | | — | — | | alice | alice | user | | admin | admin | admin |

# Download realm-import.json
https://github.com/keycloak/keycloak-quickstarts/blob/latest/nodejs/resource-server/config/realm-import.json

realm-import.json

{
  "realm": "quickstart",
  "enabled": true,
  "clients": [
    {
      "clientId": "resource-server",
      "enabled": true,
      "bearerOnly": false
    },
    {
      "clientId": "test-cli",
      "enabled": true,
      "publicClient": true,
      "directAccessGrantsEnabled": true
    }
  ],
  "users" : [
    {
      "username" : "alice",
      "enabled": true,
      "email" : "alice@keycloak.org",
      "firstName": "Alice",
      "lastName": "Liddel",
      "credentials" : [
        { "type" : "password",
          "value" : "alice" }
      ],
      "realmRoles": [ "user", "offline_access"  ],
      "clientRoles": {
        "account": [ "manage-account" ]
      }
    },
    {
      "username" : "admin",
      "enabled": true,
      "email" : "test@admin.org",
      "firstName": "Admin",
      "lastName": "Test",
      "credentials" : [
        { "type" : "password",
          "value" : "admin" }
      ],
      "realmRoles": [ "user","admin" ],
      "clientRoles": {
        "realm-management": [ "realm-admin" ],
        "account": [ "manage-account" ]
      }
    }
  ],
  "roles" : {
    "realm" : [
      {
        "name": "user",
        "description": "User privileges"
      },
      {
        "name": "admin",
        "description": "Administrator privileges"
      }
    ]
  }
}

Or use the CLI to import the quickstart ream:

# Create the quickstart realm
sudo -E -u keycloak /opt/keycloak/bin/kcadm.sh create realms -f /tmp/realm-import.json --config /opt/keycloak/config/kcadm.config

# Shell output:
Created new realm with id 'quickstart'

Install Prerequisites
#

# Install git, Node.js, Node Package Manager
sudo apt install git nodejs npm -y

# Clone git project
cd && git clone https://github.com/keycloak/keycloak-quickstarts.git

# Change directory
cd keycloak-quickstarts/nodejs/resource-server

# Install necessary Node.js dependencies listed in package.json
npm install

keycloak.json
#

# Edit the keycloak.json file
vi ~/keycloak-quickstarts/nodejs/resource-server/keycloak.json

Change the IP and port of the Keycloak server:

HTTP Version:

{
  "realm": "quickstart",
  "bearer-only": true,
  "auth-server-url": "http://192.168.30.70:8080",
  "ssl-required": "external",
  "resource": "resource-server"
}

TLS Version:

{
  "realm": "quickstart",
  "bearer-only": true,
  "auth-server-url": "https://keycloak.jklug.work:8443",
  "ssl-required": "all",
  "resource": "resource-server"
}

Start the Node.js App
#

# Start the application
npm start

Access the Quickstart App
#

Test the access to the quickstart application from a browser:

# Requires no authentication
http://192.168.30.72:3000/public

# Can be invoked by users with the user role
http://192.168.30.72:3000/secured

# Can be invoked by users with the admin role
http://192.168.30.72:3000/admin

Create Hosts Entry
#

Since I’m using a wildcard certificate and my domain is not accessible from the outside, it’s necessary to create an hosts entry.

# Open the hots file
sudo vi /etc/hosts

# Add an entry for the keycloak server
192.168.30.70 keycloak.jklug.work

Obtain the bearer token
#

# Install curl, jq JSON Processor
sudo apt install curl jq -y
# Obtain the bearer token for the user "alice": HTTPS Version
export access_token_alice=$(\
curl -X POST https://keycloak.jklug.work:8443/realms/quickstart/protocol/openid-connect/token \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'client_id=test-cli' \
-d 'username=alice&password=alice&grant_type=password' | jq --raw-output '.access_token' \
)

# Obtain the bearer token for the user "alice": HTTP Version
export access_token_alice=$(\
curl -X POST http://192.168.30.70:8080/realms/quickstart/protocol/openid-connect/token \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'client_id=test-cli' \
-d 'username=alice&password=alice&grant_type=password' | jq --raw-output '.access_token' \
)
# Obtain the bearer token for the user "admin": HTTPS Version
export access_token_admin=$(\
curl -X POST https://keycloak.jklug.work:8443/realms/quickstart/protocol/openid-connect/token \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'client_id=test-cli' \
-d 'username=admin&password=admin&grant_type=password' | jq --raw-output '.access_token' \
)

# Obtain the bearer token for the user "admin": HTTP Version
export access_token_admin=$(\
curl -X POST http://192.168.30.70:8080/realms/quickstart/protocol/openid-connect/token \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'client_id=test-cli' \
-d 'username=admin&password=admin&grant_type=password' | jq --raw-output '.access_token' \
)

Note: The access_token variable holds the token in the memory of the current shell session.

Test the Access
#

# Test Public
curl http://localhost:3000/public

# Shell output:
{"message":"public"}
# Test Alice
curl http://localhost:3000/secured \
  -H "Authorization: Bearer "$access_token_alice

# Shell output:
{"message":"secured"}
# Test Admin
curl http://localhost:3000/admin \
  -H "Authorization: Bearer "$access_token_admin

# Shell output:
{"message":"admin"}

Links #

# Find latest OpenJDK version
https://jdk.java.net/

# Keycloak: Admin CLI
https://www.keycloak.org/docs/latest/server_admin/#admin-cli

# Keycloak: keycloak.conf options
https://www.keycloak.org/server/all-config
# GitHub: Keycloak Quickstart Applications
https://github.com/keycloak/keycloak-quickstarts

# GitHub: Keycloak Node.js Quickstart Application
https://github.com/keycloak/keycloak-quickstarts/blob/latest/nodejs/resource-server/README.md

# GitHub: Keycloak Example Realm JSON Import
https://github.com/keycloak/keycloak-quickstarts/blob/latest/nodejs/resource-server/config/realm-import.json