This page contains information, code snippets and other information regarding using Keycloak with mod_auth_openidc.
Running on Azure
The code for the HEE Docker container is here;
https://github.com/Health-Education-England/TIS-DEVOPS/tree/master/docker/images/keycloak
There is a Jenkins job that will rebuild the Keyclock Docker image;
https://build-hee.transformcloud.net/jenkins/job/keycloak-docker/
When this job completes, docker-compose runs to restart the stack on the dev server;
https://build-hee.transformcloud.net/jenkins/job/keycloak-dev-deploy/
The service should then be available at this address;
https://dev-apps.lin.nhs.uk/auth/
Running locally
Keycloak is available from keycloak.org. It is an application embedded in a JBoss WildFly JEE container. The easiest way to get it working is to use an existing docker container. This container is set up to use with a MySQL datastore. To run Keycloak with a dockerized version of MySQL
Run MySQL, if you don't have a local MySQL server running then you can create one with the following commands;
$ docker run --name mysql -e MYSQL_DATABASE=keycloak -e MYSQL_USER=keycloak -e MYSQL_PASSWORD=29UTYZ735L0T8i7h6657Di71H -e MYSQL_ROOT_PASSWORD=password -d mysql
If you want to create a database in an existing instead MySQL database then try the following;
create database keycloak; grant all on keycloak.* to keycloak@'%' identified by '29UTYZ735L0T8i7h6657Di71H'; flush privileges;
Add a DNS entry in your /etc/hosts file pointing to mysql.lin.nhs.uk where aaa.bbb.ccc.ddd is the IP address that your MySQL server is listening on.
$ echo "aaa.bbb.ccc.ddd mysql.lin.nhs.uk " >> /etc/hosts
Start Keycloak using the docker-compose file for the stack https://github.com/Health-Education-England/TIS-DEVOPS/blob/master/docker/stacks/keycloak/docker-compose.yml
$ cd $TIS-DEVOPS/docker/stacks/keycloak $ docker-compose up -d
Keycloak will create and populate the required database tables on initial startup. The admin console should then be available via http://localhost:8087/auth , click on the 'Admin Console' link using the and login using admin:admin.
Tasks
Adding a realm
Initially, Keycloak has only one admin realm, which should be used for admin purposes only so we must add a non-admin realm. From the admin console, below the Keycloak logo on the left, click on "Master" with the down arrow symbol and select the "Add realm" button. Let's call the new realm heeadmin and save it.
Adding a client
Once the new realm has been added, we need to add a client. This client is the account that will be used by Apache to call into Keycloak to validate the authorisation code that Keycloak passes via the browser for logged in users.
Click on the Client option on the left-hand menu and then the Create button at the top right above the list of existing clients. Let's call the new client "apache" and set the access type to "confidential". Enter a redirect URL (towards the bottom of the page) within the URL namespace that will be protected by Keycloak and save. You can also put just the host's root URL and a wildcard (e.g. http://server/*). Save the client.
On the credentials tab (second tab at the top of the page), make a note of the client secret: it's a UUID that will be needed when setting up Apache.
Adding users and groups
Click on the group menu item on the left-hand menu and then at the top right click "New" to add new groups.
Click on the user menu item on the left-hand menu and then at the top right "Add user". Enter the desired username and save. On the credentials tab you can then enter a new password. Turn off the temporary password feature and reset the password. Go to the group tab at the top and add groups to the created user.
Installing mod_auth_openidc
The latest release of the module and its dependencies is available at https://github.com/pingidentity/mod_auth_openidc/releases/latest (2.0.0 at the time of writing).
The Apache module relies on the cjose library (for decoding JWTs) and libhiredis for the optional Redis shared session cache. (I had a small problem when installing on Fedora 24 as the binary release required libhiredis.so.0.12 but the installed version on my machine was 0.13. I got around this by creating a symbolic link from 0.12 to 0.13 on an assumption of backward compatibility. You might not be affected by this.)
Validate JWT Token
When making a request through Keycloak a header called OIDC_access_token will be added to the response headers. The access token can be validated using;
curl http://localhost:8087/auth/realms/lin/protocol/openid-connect/token/introspect \ -d client_id=revalidation \ -d client_secret=longpassword \ -d "token=${ACCESS_TOKEN}"
Configuring Apache
The Apache configuration needs to be set up to talk to Keycloak.
The main elements to configure manually are:
Directive | Value |
---|---|
OIDCProviderMetadataURL | The URL for the OpenID Connect configuration on Keycloak. https://dev-apps.lin.nhs.uk/auth/realms/heeadmin/.well-known/openid-configuration |
OIDCClientID | The name of the client created when setting up Keycloak |
OIDCClientSecret | The secret for the client (available from the client's credentials page). |
OIDCRedirectURI | A redirect URL within the area of the redirect URL set up on the Keycloak client page |
ServerName | The Apache virtual host. (Apache will default to the first virtual host in a file if no virtual host name matches) |
ProxyPass | The URL of the back-end application you want to protect |
ProxyPassReverse | The same as ProxyPass. (This is used by Apache to change the Location header in 302 responses) |
Setting up permissions
Access control rules can be put into the Apache config file to limit access to certain URLs to users with a given set of permissions (granted via groups).
<Location />
AuthType openid-connect
Require claim groups:supervisor
ProxyPass http://localhost:8082/
ProxyPassReverse http://localhost:8082/
</Location>
Note that Apache's authorisation by default looks for any of the Require rules to pass (i.e. it ORs the rules). If you want to enforce all of the claims (such as being a member of two groups) then you should use a <RequireAll> block. Rules based on HTTP methods can also be defined either with "Require method GET" or with <Limit GET>. Be careful that <Limit methods...> applies the contained rules only to the named methods so you may want to use <LimitExcept methods...> instead. See the Apache documentation at https://httpd.apache.org/docs/2.4/mod/mod_authz_core.html for further details on Require and https://httpd.apache.org/docs/2.4/mod/core.html#limit for details on Limit.
Automation
Keycloak has an admin REST API. The documentation for it is available at http://www.keycloak.org/docs/rest-api/index.html
Get an admin token
In order to use the admin REST API an admin token is required. This can be obtained as follows:
$ TOKEN=$(curl -s 'http://localhost:8087/auth/realms/master/protocol/openid-connect/token' -d "client_id=admin-cli&username=admin&password=admin&grant_type=password" | jq -r .access_token)
(jq is a JSON parser that can extract values from JSON. The -r parameter means "raw output" and removes the quotes from the returned value.)
Create a realm
A realm can be created from a JSON template and added using the following:
curl -i 'http://localhost:8087/auth/admin/realms' \ -H "Authorization: bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"realm":"lin", "enabled":true}'
This will return the URL of the new realm as an HTTP Location header.
Create a group
curl -i 'http://localhost:8087/auth/admin/realms/lin/groups' \ -H "Authorization: bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"admins"}'
Create a client
curl -i 'http://localhost:8087/auth/admin/realms/lin/clients' \ -H "Authorization: bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"clientId":"revalidation","redirectUris":["https://dev-api.transformcloud.net/revalidation/"], "secret":"longpassword"}'
This will return the URL of the new client as an HTTP Location header.
Create a user
curl -i 'http://localhost:8087/auth/admin/realms/lin/users' \ -H "Authorization: bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"username":"jamesH","enabled":true,"email":"jamesH@example.com","attributes":{"gmc_id":["1125"],"NTN":["245/FGS/819"]}}'
Adding user to group
curl -i 'http://localhost:8087/auth/admin/realms/lin/users/{id}/groups/{groupId}' \ -H "Authorization: bearer $TOKEN" \ -X PUT
Remove user from group
curl -i 'http://localhost:8087/auth/admin/realms/lin/users/{id}/groups/{groupId}' \ -H "Authorization: bearer $TOKEN" \ -X DELETE
Integration
Keyclock needs to be added to the service's proxy path, this can be done by changing the TIS-DEVOPS/ansible/vars/api-gateway.yml and adding an attribute called require_auth to the proxy path definition, e.g.
applications: - { port: "8080", path: "revalidation", require_auth: true}
- Rebuild api-gateway for your target platform.
- Try hitting the service and you should be bounced to a login page. When you login with valid credentials you should be returned to the correct location with the following headers in place;
GET /test/ HTTP/1.1 Host: dev-api.transformcloud.net Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36 Referer: https://dev-api.transformcloud.net/auth/realms/lin/protocol/openid-connect/auth?response_type=code&scope=openid&client_id=revalidation&state=B0Lm2UsfMwGy_9sy5C62ymqxONQ&redirect_uri=https%3A%2F%2Fdev-api.transformcloud.net%2Ftest%2Ftest&nonce=iFCESi2NDbvM2xcBQJ7jrLLbxP5szHCZa7k5rwQwwfY Accept-Encoding: gzip, deflate, sdch Accept-Language: en-US,en;q=0.8 Cookie: session=592cebb9-85db-48ae-9483-b065f86649ec OIDC_CLAIM_family_name: Hudson OIDC_CLAIM_sub: b1702d1c-c8bb-4882-99f7-2c7760681b05 OIDC_CLAIM_roles: [view-profile, manage-account, judges, admin, uma_authorization, offline_access] OIDC_CLAIM_name: James Hudson OIDC_CLAIM_groups: East of England OIDC_CLAIM_given_name: James OIDC_CLAIM_preferred_username: jamesh OIDC_CLAIM_nbf: 0 OIDC_CLAIM_jti: 268c6c62-05f9-4d8d-b2b2-04cd353cf162 OIDC_CLAIM_session_state: 55ba70b9-2f15-4068-8f4f-4fd23564e24c OIDC_CLAIM_typ: ID OIDC_CLAIM_exp: 1476971360 OIDC_CLAIM_iss: https://dev-api.transformcloud.net/auth/realms/lin OIDC_CLAIM_iat: 1476971060 OIDC_CLAIM_aud: revalidation OIDC_CLAIM_auth_time: 1476971060 OIDC_CLAIM_azp: revalidation OIDC_CLAIM_nonce: iFCESi2NDbvM2xcBQJ7jrLLbxP5szHCZa7k5rwQwwfY OIDC_CLAIM_acr: 1 OIDC_access_token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJHVFNIYkRwN0JSeVhORTQ2cWZtVFFvZ1lrOFF2MERldENNSEVjNkFFeDhzIn0.eyJqdGkiOiJlYzFkMjU1Mi1lNDJjLTRhMWUtYmIyOC0yMTlkNGM3MTY5M2IiLCJleHAiOjE0NzY5NzEzNjAsIm5iZiI6MCwiaWF0IjoxNDc2OTcxMDYwLCJpc3MiOiJodHRwczovL2Rldi1hcGkudHJhbnNmb3JtY2xvdWQubmV0L2F1dGgvcmVhbG1zL2xpbiIsImF1ZCI6InJldmFsaWRhdGlvbiIsInN1YiI6ImIxNzAyZDFjLWM4YmItNDg4Mi05OWY3LTJjNzc2MDY4MWIwNSIsInR5cCI6IkJlYXJlciIsImF6cCI6InJldmFsaWRhdGlvbiIsIm5vbmNlIjoiaUZDRVNpMk5EYnZNMnhjQlFKN2pyTExieFA1c3pIQ1phN2s1cndRd3dmWSIsImF1dGhfdGltZSI6MTQ3Njk3MTA2MCwic2Vzc2lvbl9zdGF0ZSI6IjU1YmE3MGI5LTJmMTUtNDA2OC04ZjRmLTRmZDIzNTY0ZTI0YyIsImFjciI6IjEiLCJjbGllbnRfc2Vzc2lvbiI6ImJlNjQ4ODY0LWE3Y2UtNDBlMS05MGQ4LTNkY2VkOWZlYTY5MSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwczovL2Rldi1hcGkudHJhbnNmb3JtY2xvdWQubmV0Il0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJhZG1pbiIsInVtYV9hdXRob3JpemF0aW9uIiwianVkZ2VzIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsInZpZXctcHJvZmlsZSJdfX0sInJvbGVzIjoiW3ZpZXctcHJvZmlsZSwgbWFuYWdlLWFjY291bnQsIGp1ZGdlcywgYWRtaW4sIHVtYV9hdXRob3JpemF0aW9uLCBvZmZsaW5lX2FjY2Vzc10iLCJuYW1lIjoiSmFtZXMgSHVkc29uIiwiZ3JvdXBzIjpbIkVhc3Qgb2YgRW5nbGFuZCJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqYW1lc2giLCJnaXZlbl9uYW1lIjoiSmFtZXMiLCJmYW1pbHlfbmFtZSI6Ikh1ZHNvbiJ9.VxPtvyu8jlgsHumUEKEttM27jsOirn26KokGEp9MfoiOe-Z1L_IiEs-KYsdzW2J2Fwx7amgGidlvfD0uU_EuEaoU0Wrt1uuWRHMzaVztbc1ekl0vIqk7YYvz9I84ngKug8YITgTg3ZlKLOhBUrSVeT9Pz9mFTrJZhKfX7XARVsOc2HZJqgmMG5IYitZfD5uti0enuD9EfYNqnCv_6cEbc45lFNSAMjcyJWSkNN9VPEo-_NSZQrLVmOB3oNZ5vetsw5ijb6y9TQUcrDzUu6qu74_J3n2w9PrrRXVmYeYphetNZGE2LyBScJyMuYvzu6oAik2banzLc9jGiw22tGEuQQ OIDC_access_token_expires: 1476971360 X-Forwarded-Proto: https X-Forwarded-Port: 443 X-Forwarded-For: 89.16.226.104 X-Forwarded-Host: dev-api.transformcloud.net X-Forwarded-Server: dev-api.transformcloud.net Connection: Keep-Alive
Programmatic Access
To access an API programmatically, a new Keycloak user needs to be added to the LIN realm. The username and password can then be exchanged for a JWT token to make a request to a protected service. The following script show how a JWT token can be acquired and how that token is used to make an authenticated request, in this case, our jamesh user;
#! /bin/bash # Step 1 export TOKEN=$(curl -s 'https://dev-apps.lin.nhs.uk/auth/realms/lin/protocol/openid-connect/token' -d "client_id=api-tokens&username=jamesh&password=j4m3srul3z&grant_type=password" | jq -r .access_token) # Step 2 - optional curl https://dev-apps.lin.nhs.uk/auth/realms/lin/protocol/openid-connect/token/introspect \ -d client_id=<client id> \ -d client_secret=<client secret> \ -d "token=${TOKEN}" \ | python -m json.tool # Step 3 curl -i -H "Authorization: bearer ${TOKEN}" \ https://dev-apps.lin.nhs.uk/api/revalidation/health
Use use a password grant to exchange a username and password for a JWT token. The response from this command will be a JSON document with the access token with it;
{ "access_token": "<long string value>", "expires_in": 300, "id_token": "<long string value>", "not-before-policy": 0, "refresh_expires_in": 1800, "refresh_token": "<long string value>", "session_state": "<uuid>", "token_type": "bearer" }
- In this step, we show how the token is validated using a client id and secret. This step will be completed by mod_auth_openidc and is included for completeness.
In this step, we add the "Authorization: Bearer <access token>" header to our call. Apache will intercept the call, extract the JWT and validate it before forwarding the request to the target API.
GET /test/ HTTP/1.1 Host: dev-apps.lin.nhs.uk User-Agent: curl/7.49.1 Accept: */* Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJrcEk5UC1hQ3JaTXJ4cG5aeWNnNnlISk9VZ3g0a2hUYS04TlJyMkRhY0g0In0.eyJqdGkiOiI4MTNhNzUzMS04NmIzLTRjOTgtODY4ZS01NzA5M2E4NDY2MmQiLCJleHAiOjE0ODA2MDkyNzQsIm5iZiI6MCwiaWF0IjoxNDgwNjA4OTc0LCJpc3MiOiJodHRwczovL2Rldi1hcHBzLmxpbi5uaHMudWsvYXV0aC9yZWFsbXMvbGluIiwiYXVkIjoiYWRtaW4tY2xpIiwic3ViIjoiNGY5YWRhY2MtZjEyNC00M2FmLTkyZDMtYjVlZDc3NjhlYTU0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYWRtaW4tY2xpIiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZDM0M2ZiYmUtZjYxMS00YTg3LWE0ZTktODg0ODI0ZTYzN2VkIiwiYWNyIjoiMSIsImNsaWVudF9zZXNzaW9uIjoiODZkZTk0NTQtYmY1Ny00ZDM2LThjYjItOWU0ZjllMDA3MTQ2IiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVzb3VyY2VfYWNjZXNzIjp7fSwibmFtZSI6IkphbWVzIEh1ZHNvbiIsInByZWZlcnJlZF91c2VybmFtZSI6ImphbWVzaCIsImdpdmVuX25hbWUiOiJKYW1lcyIsImZhbWlseV9uYW1lIjoiSHVkc29uIn0.F_wFf1T_kEdcZDlG_H45lk5uukN26trTsPHcko-RF7Zk9VXLjPX6HcExASUmxMqMTBRrbYuLDDPWGZ7OoKHIp-bG-3wR3XqGDTVgQ-DbzarAtEDKsNbAzReh008uo_U3j9biwKHcWNOAFuSde5ZUma5qWS6jvpV750lwCb7WU8xikci9lD_WK6xW_H1B_KW8WJ4SpoH7qNI1OITFtMOQBx06z2q-DQVc_3bISwDKx-sFj6MFOr-0OcXz935H1OJICFYjljquY5q-6ZkB0bwVKVpOKd22q7cnAT50bzAbXCQifja0Jr9qVUytq79QxEDeGeAo40WzPO_a7PPTkfvHdw OIDC_CLAIM_name: James Hudson OIDC_CLAIM_allowed-origins: OIDC_CLAIM_typ: Bearer OIDC_CLAIM_azp: admin-cli OIDC_CLAIM_jti: 813a7531-86b3-4c98-868e-57093a84662d OIDC_CLAIM_sub: 4f9adacc-f124-43af-92d3-b5ed7768ea54 OIDC_CLAIM_nbf: 0 OIDC_CLAIM_auth_time: 0 OIDC_CLAIM_session_state: d343fbbe-f611-4a87-a4e9-884824e637ed OIDC_CLAIM_exp: 1480609274 OIDC_CLAIM_client_session: 86de9454-bf57-4d36-8cb2-9e4f9e007146 OIDC_CLAIM_iat: 1480608974 OIDC_CLAIM_iss: https://dev-apps.lin.nhs.uk/auth/realms/lin OIDC_CLAIM_aud: admin-cli OIDC_CLAIM_given_name: James OIDC_CLAIM_family_name: Hudson OIDC_CLAIM_acr: 1 OIDC_CLAIM_preferred_username: jamesh OIDC_CLAIM_username: jamesh OIDC_CLAIM_resource_access: {} OIDC_CLAIM_client_id: admin-cli OIDC_CLAIM_active: 1 OIDC_access_token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJrcEk5UC1hQ3JaTXJ4cG5aeWNnNnlISk9VZ3g0a2hUYS04TlJyMkRhY0g0In0.eyJqdGkiOiI4MTNhNzUzMS04NmIzLTRjOTgtODY4ZS01NzA5M2E4NDY2MmQiLCJleHAiOjE0ODA2MDkyNzQsIm5iZiI6MCwiaWF0IjoxNDgwNjA4OTc0LCJpc3MiOiJodHRwczovL2Rldi1hcHBzLmxpbi5uaHMudWsvYXV0aC9yZWFsbXMvbGluIiwiYXVkIjoiYWRtaW4tY2xpIiwic3ViIjoiNGY5YWRhY2MtZjEyNC00M2FmLTkyZDMtYjVlZDc3NjhlYTU0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYWRtaW4tY2xpIiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiZDM0M2ZiYmUtZjYxMS00YTg3LWE0ZTktODg0ODI0ZTYzN2VkIiwiYWNyIjoiMSIsImNsaWVudF9zZXNzaW9uIjoiODZkZTk0NTQtYmY1Ny00ZDM2LThjYjItOWU0ZjllMDA3MTQ2IiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVzb3VyY2VfYWNjZXNzIjp7fSwibmFtZSI6IkphbWVzIEh1ZHNvbiIsInByZWZlcnJlZF91c2VybmFtZSI6ImphbWVzaCIsImdpdmVuX25hbWUiOiJKYW1lcyIsImZhbWlseV9uYW1lIjoiSHVkc29uIn0.F_wFf1T_kEdcZDlG_H45lk5uukN26trTsPHcko-RF7Zk9VXLjPX6HcExASUmxMqMTBRrbYuLDDPWGZ7OoKHIp-bG-3wR3XqGDTVgQ-DbzarAtEDKsNbAzReh008uo_U3j9biwKHcWNOAFuSde5ZUma5qWS6jvpV750lwCb7WU8xikci9lD_WK6xW_H1B_KW8WJ4SpoH7qNI1OITFtMOQBx06z2q-DQVc_3bISwDKx-sFj6MFOr-0OcXz935H1OJICFYjljquY5q-6ZkB0bwVKVpOKd22q7cnAT50bzAbXCQifja0Jr9qVUytq79QxEDeGeAo40WzPO_a7PPTkfvHdw X-Forwarded-Proto: https X-Forwarded-For: 89.16.226.104 X-Forwarded-Host: dev-apps.lin.nhs.uk X-Forwarded-Server: dev-apps.lin.nhs.uk Connection: Keep-Alive
LIN Keycloak Theme
We have updated the Keycloak theme for the login screens, the new code is held in https://github.com/Health-Education-England/TIS-KEYCLOAK-THEME
To update the theme, the customised Docker image needs to be updated to include that code. There is a job to rebuild in Jenkins https://build-hee.transformcloud.net/jenkins/job/keycloak-docker/
Styling updates to keycloak theme
It is possible to tweak the styling for a keycloak theme without having to run keycloak locally via using charles https://www.charlesproxy.com/download/ which is a web proxy application. This is achieved by locally mapping the remote css file responsible for the theme to a local css file on your computer.
For example the remote css file https://stage-apps.lin.nhs.uk/auth/resources/2.2.2.final-snapshot/login/lin/css/login.css can be mapped to /Users/XXX/Sites/HEE/keycloak/themes/lin/login/resources/css/login.css which essentially means you can locally work on the css and once done can push your changes to the git repo mentioned above as well as triggering the jenkins job https://build-hee.transformcloud.net/jenkins/job/keycloak-dev-deploy/
Steps to map a remote file locally
- Enable SSL proxing for the host
2. Choose file to map
3. Point to local file
Useful Links
http://paulbakker.io/java/jwt-keycloak-angular2/
https://github.com/pingidentity/mod_auth_openidc
https://jwt.io/ (Useful UI for viewing the content of a JWT)
Add Comment