This is effectively a locally running example of what is currently available on the excellent Keycloak course on Katacoda: https://www.katacoda.com/keycloak/courses/keycloak.

In order to get keycloak up and running I have used a virtual machine running RHEL8. I have given this machine the hostname rhel8-keycloak.

Setting up the virtual machine with Keycloak

Here are the steps I used to install java, keycloak, configure the firewall, and run keycloak:

sudo hostnamectl set-hostname rhel8-keycloak
sudo dnf install java-1.8.0-openjdk -y
sudo firewall-cmd --add-port=8443/tcp --permanent
sudo firewall-cmd --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
curl https://downloads.jboss.org/keycloak/8.0.1/keycloak-8.0.1.zip --output keycloak-8.0.1.zip
unzip keycloak-8.0.1.zip
cd keycloak-8.0.1/bin
./add-user-keycloak.sh -r master -u admin -p admin
./standalone.sh -b 0.0.0.0

Setting up a Keycloak Realm, Role, and User

From your workstation navigate to https://rhel8-keycloak:8443/auth/. This should take you to the Keycloak screen:

Keycloak Screen

You can now login to keycloak using the username:password admin:admin (as specified from ./add-user-keycloak.sh).

Create a Realm example.

Click add realm

and then add the realm example:

Create example realm

Navigate to Users and click Add user:

Navigate to Users

Create a User someuser.

Create someuser

Then set the password somepassword

Set password

Create a Role example.

Create example role

Map the User someuser into the Role example.

Add someuser to example role

Now we are ready to create a nodejs service that will authenticate with Keycloak.

Securing a Node JS Service

Firstly we need to add a new client to Keycloak. Add a new client called nodejs-app:

Add nodejs-app Client

Secondly we need to get a nodeJS app up and running, configured to use keycloak for authentication. We need to git clone, npm install, and npm start.

[user@localhost temp]$ git clone https://github.com/welshstew/keycloak-simple-examples.git
Cloning into 'keycloak-simple-examples'...
...
Unpacking objects: 100% (16/16), done.
[user@localhost temp]$ cd keycloak-simple-examples/keycloak-nodejs-example/
[user@localhost keycloak-nodejs-example]$ npm install
npm WARN deprecated istanbul@0.4.5: This module is no longer maintained, try this instead:
...
added 405 packages from 426 contributors and audited 1198 packages in 7.66s
found 26 vulnerabilities (2 low, 19 moderate, 5 high)
run `npm audit fix` to fix them, or `npm audit` for details
[user@localhost keycloak-nodejs-example]$ npm start
> keycloak-nodejs-example@0.0.1 start /home/user/temp/keycloak-simple-examples/keycloak-nodejs-example
> node app.js
Started at port 8080

Note that the keycloak.json file in the application defines the realm and auth-server-url variables.

{
"realm": "example",
"bearer-only": true,
"auth-server-url": "http://rhel8-keycloak:8080/auth",
"ssl-required": "external",
"resource": "keycloak-nodejs-example"
}

Now try curling the service…

[user@host ~]$ curl -v http://localhost:8080/service/public
* Trying ::1:8080...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /service/public HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.66.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Access-Control-Allow-Origin: *
< Content-Type: application/json; charset=utf-8
< Content-Length: 20
< ETag: W/"14-3n5gaqLD6Q0gb1aNXoWwKtKvV74"
* Added cookie connect.sid="s%3Alz-s4HNUYSqSF9hwhhME0PXoDs6hS-nb.gepKW50c7RTRVSk0bHmbkL8sVyZ9bpUbbqUzq0B4qAM" for domain localhost, path /, expire 0
< Set-Cookie: connect.sid=s%3Alz-s4HNUYSqSF9hwhhME0PXoDs6hS-nb.gepKW50c7RTRVSk0bHmbkL8sVyZ9bpUbbqUzq0B4qAM; Path=/; HttpOnly
< Date: Tue, 07 Jan 2020 15:17:47 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
{"message":"public"}

There is also a protected url that required authentication, the curl to this should show access denied:

[user@localhost ~]$ curl -v http://localhost:8080/service/secured
* Trying ::1:8080...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /service/secured HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.66.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< X-Powered-By: Express
< Access-Control-Allow-Origin: *
* Added cookie connect.sid="s%3AvHUMm1RYupreOC1TGSUc_905-QIYOiFs.1Hs%2BBRUZMDtT4OyEssLDcYi6ZVQ0kO5j6ZY7ybWPfBM" for domain localhost, path /, expire 0
< Set-Cookie: connect.sid=s%3AvHUMm1RYupreOC1TGSUc_905-QIYOiFs.1Hs%2BBRUZMDtT4OyEssLDcYi6ZVQ0kO5j6ZY7ybWPfBM; Path=/; HttpOnly
< Date: Tue, 07 Jan 2020 19:31:43 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
* Connection #0 to host localhost left intact
Access denied

The app.js file shows how this is secured against the example realm:

app.get('/service/secured', keycloak.protect('realm:example'), function (req, res) {
  res.json({message: 'secured'});
});

Now we need to get a token from Keycloak, we will authenticate in order to get a token as follows:

[user@localhost ~]$ export access_token=$(curl -s -X POST http://rhel8-keycloak:8080/auth/realms/example/protocol/openid-connect/token -H 'content-type: application/x-www-form-urlencoded' -d 'username=someuser&password=somepassword&grant_type=password&client_id=nodejs-app' | jq --raw-output '.access_token')
[user@localhost ~]$ echo $access_token
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwi..REDACTED>>

Now using this access token we will be able to authenticate against the service:

curl -v http://localhost:8080/service/secured -H "Authorization: Bearer "$access_token

[user@localhost ~]$ curl -v http://localhost:8080/service/secured -H "Authorization: Bearer "$access_token
* Trying ::1:8080...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /service/secured HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.66.0
> Accept: */*
> Authorization: Bearer SAASASASCACAC.eyJqdGkiOiI5M2J...
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Access-Control-Allow-Origin: *
< Content-Type: application/json; charset=utf-8
< Content-Length: 21
< ETag: W/"15-rh1jDGSQEcF6OuQ7lLT0bxrS72Y"
* Added cookie connect.sid="s%3AYmBAub4GF3P4qNWfvjBLrtbKFSL9_du5.vfmjVwu4oUoXYa7j2JsSjlpQL4BdLHnG2mUp0ok1hjQ" for domain localhost, path /, expire 0
< Set-Cookie: connect.sid=s%3AYmBAub4GF3P4qNWfvjBLrtbKFSL9_du5.vfmjVwu4oUoXYa7j2JsSjlpQL4BdLHnG2mUp0ok1hjQ; Path=/; HttpOnly
< Date: Tue, 07 Jan 2020 19:44:23 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact

Securing the Keycloak Playground Frontend

In this section we look at configuring the keycloak-app-example which is a simple web app, and will authenticate against the example realm, but it will prompt the user in order to do so.

We need to create another new Client in Keycloak called web-client. It is important that we add the asterix * for Valid Redirect URIs.

Create Web Client

and we export the configuration of the client (web-client.json) from Keycloak, this will be a json file that we overwrite in the keycloak-app-example:

Export Web Client JSON

Another help is to allow CORS, I have done this using a Firefox plugin:

CORS Everywhere

Now we start the application with python:

[user@localhost keycloak-app-example]$ python -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

We will be presented with the web screen:

Playground Frontend Initial

After the user clicks “Login” they will be forwarded to the Keycloak authentication screen. Here we can use our someuser and somepassword credentials.

Auth Challenge

Once authenticated we can see our token info:

Web Running

And also we should be able to call our locally running nodejs REST service:

Service Call Success


codergists