Creating JSON Web Tokens (JWT) using Keycloak

This article explores creation of JSON Web Tokens (JWT) using Keycloak.

1. About JSON Web Tokens

JSON Web Token (JWT) is a JSON-based open standard (RFC 7519) for creating access tokens that assert some number of claims.

Wikipedia

2. About Keycloak

Keycloak is an open source Identity and Access Management solution aimed at modern applications and services.

Keycloak has features such as single-sign on, identity brokering, social login, user federation and authorization. It supports standard protocols such as OpenID Connect, OAuth 2.0 and SAML.

3. Prerequisites

  1. cURL and jq must be installed

4. Installing Keycloak server

  1. Set environment variables

    $ export KC_VERSION="3.4.3.Final"
    $ export KC_HOST="localhost" (1)
    $ export KC_PORT="8383"
    $ export KC_ADMIN_PORT="9990" (2)
    $ export KC_HOME_PARENT_DIR="/opt" (3)
    $ export KC_HOME="${KC_HOME_PARENT_DIR}/keycloak-${KC_VERSION}"
    $ export KC_ADMIN_USER="john.doe"
    $ export KC_ADMIN_PASSWORD="JohnsSecret#1"
    $ export KC_REALM="myrealm"
    $ export KC_CLIENT_NAME="myclient"
    $ export KC_ROLE_NAME="myrole"
    $ export KC_USER="jane.doe"
    $ export KC_USER_PASSWORD="JanesSecret#1"
    $ export KC_USER_EMAIL="jane@doe.net"
    $ export KC_USER_FIRSTNAME="Jane"
    $ export KC_USER_LASTNAME="Doe"
    1 We will be operating Keycloak server from the host on which it is running.
    2 9990 is the default Keycloak admin port.
    3 Use a directory of your choice.
You can use a port and directory of your choice.
  1. If you use an HTTP proxy, set environment variables so that curl knows about the proxy.

  2. Download Keycloak archive

    $ mkdir -p ${KC_HOME_PARENT_DIR}
    
    $ pushd ${KC_HOME_PARENT_DIR}
    
    $ curl https://downloads.jboss.org/keycloak/${KC_VERSION}/keycloak-${KC_VERSION}.tar.gz \
          --output keycloak-${KC_VERSION}.tar.gz
    
    $ popd
  3. Unpack Keycloak archive

    $ pushd ${KC_HOME_PARENT_DIR}
    
    $ tar xzf keycloak-${KC_VERSION}.tar.gz
    
    $ popd
  4. Delete Keycloak archive

    $ rm ${KC_HOME_PARENT_DIR}/keycloak-${KC_VERSION}.tar.gz

5. Starting Keycloak server

$ nohup \
      ${KC_HOME}/bin/standalone.sh \
      -b 0.0.0.0 \
      -Djboss.http.port=${KC_PORT} &
Keycloak requires Java.

6. Adding data to Keycloak

  1. Create an admin user in Keycloak

    $ ${KC_HOME}/bin/add-user-keycloak.sh \
          --realm master \
          --user "${KC_ADMIN_USER}" \
          --password "${KC_ADMIN_PASSWORD}"
    You can use a username and password of your choice.
  2. Restart Keycloak server

    $ ${KC_HOME}/bin/jboss-cli.sh \
          --connect controller=${KC_HOST}:${KC_ADMIN_PORT} \
          --command=':shutdown(restart=true)'
  3. Login into Keycloak server as admin

    $ ${KC_HOME}/bin/kcadm.sh \
          config credentials \
          --server http://${KC_HOST}:${KC_PORT}/auth \
          --realm master \
          --user "${KC_ADMIN_USER}" \
          --password "${KC_ADMIN_PASSWORD}"
  4. Create a realm in Keycloak

    $ ${KC_HOME}/bin/kcadm.sh \
          create realms \
          --set realm="${KC_REALM}" \
          --set enabled=true
  5. Create a client in Keycloak.

    $ ${KC_HOME}/bin/kcadm.sh \
          create clients \
          --target-realm ${KC_REALM} \
          --set clientId="${KC_CLIENT_NAME}" \
          --set enabled=true \
          --set directAccessGrantsEnabled=true
  6. Create a Keycloak role.

    $ ${KC_HOME}/bin/kcadm.sh \
          create roles \
          --target-realm ${KC_REALM} \
          --set name="${KC_ROLE_NAME}"
  7. Create a user in Keycloak.

    $ ${KC_HOME}/bin/kcadm.sh \
          create users \
          --target-realm ${KC_REALM} \
          --set username="${KC_USER}" \
          --set email="${KC_USER_EMAIL}" \
          --set firstName="${KC_USER_FIRSTNAME}" \
          --set lastName="${KC_USER_LASTNAME}" \
          --set enabled=true
  8. Configure a password for the user in Keycloak

    $ ${KC_HOME}/bin/kcadm.sh \
          set-password \
          --target-realm ${KC_REALM} \
          --username ${KC_USER} \
          --new-password ${KC_USER_PASSWORD}
  9. Add role to the user in Keycloak

    $ ${KC_HOME}/bin/kcadm.sh \
          add-roles \
          --target-realm ${KC_REALM} \
          --uusername ${KC_USER} \
          --rolename ${KC_ROLE_NAME}

7. Obtaining JWT from Keycloak

  1. Obtain access token for admin user

    $ admin_access_token=`curl \
          "http://${KC_HOST}:${KC_PORT}/auth/realms/master/protocol/openid-connect/token" \
          -X POST \
          -d "grant_type=password" \
          -d "client_id=admin-cli" \
          -d "username=${KC_ADMIN_USER}" \
          -d "password=${KC_ADMIN_PASSWORD}" \
          --silent \
          | jq -r '.access_token'`
  2. Obtain client id

    $ client_id=`curl \
          "http://${KC_HOST}:${KC_PORT}/auth/admin/realms/${KC_REALM}/clients/" \
          -H "Authorization: Bearer ${admin_access_token}" \
          --silent \
          | jq --arg client_name "${KC_CLIENT_NAME}" -r '.[] | select (.clientId == $client_name).id'`
  3. Obtain client secret

    $ client_secret=`curl \
          "http://${KC_HOST}:${KC_PORT}/auth/admin/realms/${KC_REALM}/clients/${client_id}/client-secret" \
          -H "Authorization: Bearer ${admin_access_token}" \
          --silent \
          | jq -r '.value'`
  4. Obtain access token for the user. This is the JWT that can be sent, along with the request, to the server.

    $ curl \
          -X POST \
          "http://${KC_HOST}:${KC_PORT}/auth/realms/${KC_REALM}/protocol/openid-connect/token" \
          -d "grant_type=password" \
          -d "username=${KC_USER}" \
          -d "password=${KC_USER_PASSWORD}" \
          -d "client_id=${KC_CLIENT_NAME}" \
          -d "client_secret=${client_secret}" \
          --silent \
          | jq -r '.access_token'

Following is a sample JWT obtained by following the steps mentioned above:

eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJBaDBGRkdObExDel80UlNyNFVXbEZzVXVVSHZ3MkdSOUtPZ2pGT1FQVVNRIn0.eyJqdGkiOiI4NDhlYmQ4NC1hYzA1LTQ1MzktYmFmNi00Mzc0OWFjMTk1MmIiLCJleHAiOjE1MzA0NDgwNTAsIm5iZiI6MCwiaWF0IjoxNTMwNDQ3NzUwLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgzODMvYXV0aC9yZWFsbXMvbXlyZWFsbSIsImF1ZCI6Im15Y2xpZW50Iiwic3ViIjoiZDFiNWIyNjktMmM3MC00OTNlLThiOWMtZGM4YmNkZGQwMmUyIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoibXljbGllbnQiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJiYWU1ZjRjMS1iZGUwLTQwYjEtYTI3MS04MjJlMmNiNzdlMTAiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInVtYV9hdXRob3JpemF0aW9uIiwibXlyb2xlIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwibmFtZSI6IkphbmUgRG9lIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiamFuZS5kb2UiLCJnaXZlbl9uYW1lIjoiSmFuZSIsImZhbWlseV9uYW1lIjoiRG9lIiwiZW1haWwiOiJqYW5lQGRvZS5uZXQifQ.UrY2-mSVsH4oVkc741QK4VVvROksVAN-NCDcIIS_QV-4sxpBr1W0zK5SrslWLQhFEwNerboEgPZ2mi0XqX5XZwnAJmP5yvkWCiEafyolnsZ_tNkbOkIoG_lNEzrnLhdHerBNJ_0WrX1v9EoAK6r9KmqvoiHH7javmsUiM_J1NYJ9KZVy826LqBFUlLr8irWYzjG-9SJSLqCDjXkwSdruJrDD-gJWwRSB6jYRHtHvDXy3sep9jGPmeISY8VZcpViGEp4j_4W67h42cSbNi1dV73iwlUcgdgR1jntoh6peoWCtdz4JJBPJMqjTdhJ_O-wohnCbcFkQdS8PfSjvBkhx8A

Following is the decoded version of a sample JWT:

Header
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "Ah0FFGNlLCz_4RSr4UWlFsUuUHvw2GR9KOgjFOQPUSQ"
}
Payload
{
  "jti": "848ebd84-ac05-4539-baf6-43749ac1952b",
  "exp": 1530448050,
  "nbf": 0,
  "iat": 1530447750,
  "iss": "http://localhost:8383/auth/realms/myrealm",
  "aud": "myclient",
  "sub": "d1b5b269-2c70-493e-8b9c-dc8bcddd02e2",
  "typ": "Bearer",
  "azp": "myclient",
  "auth_time": 0,
  "session_state": "bae5f4c1-bde0-40b1-a271-822e2cb77e10",
  "acr": "1",
  "allowed-origins": [],
  "realm_access": {
    "roles": [
      "uma_authorization",
      "myrole"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "name": "Jane Doe",
  "preferred_username": "jane.doe",
  "given_name": "Jane",
  "family_name": "Doe",
  "email": "jane@doe.net"
}

8. Cleanup

  1. Stopping Keycloak server

    $ ${KC_HOME}/bin/jboss-cli.sh \
          --connect controller=${KC_HOST}:${KC_ADMIN_PORT} \
          --command=':shutdown(restart=false)'

References