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:
You can now login to keycloak using the username:password admin:admin (as specified from ./add-user-keycloak.sh
).
Create a Realm example
.
and then add the realm example
:
Navigate to Users and click Add user
:
Create a User someuser
.
Then set the password somepassword
Create a Role example
.
Map the User someuser
into the Role example
.
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
:
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
.
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
:
Another help is to allow CORS, I have done this using a Firefox plugin:
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:
After the user clicks “Login” they will be forwarded to the Keycloak authentication screen. Here we can use our someuser
and somepassword
credentials.
Once authenticated we can see our token info:
And also we should be able to call our locally running nodejs REST service: