Keycloak IdP for SSO

To explore :

official website

Server IdP ( identity provider) for the SSO (single sign-on) Application written in Java, runs on a WildFly server.

Installation and configuration

Pre requises (see documentation) :

Fetch the software :

sudo su
add-apt-repository ppa:webupd8team/java
apt update
apt install default-jre oracle-java8-installer

Fetch the software :

cd /var/www
unzip -d .
cd keycloak-xxx.Final
unzip -d .
unzip -d .
rm *.zip

Add the user admin :

./bin/ -u username

if it returns the message No content to map due to end-of-input, then create a temporary admin user tempuser with the password dkPK62wdofPEK56-_&SO :

nano ./standalone/configuration/keycloak-add-user.json

Put this :

[ {
  "realm" : "master",
  "users" : [{
    "username" : "tempuser",
    "enabled" : true,
    "emailVerified" : true,
    "credentials" : [ {
      "type" : "password",
      "hashedSaltedValue" : "IhS5rpj4PV20PURXYtQktPAYDbI5ATQdafxquCWs1mKwJtuvxhW2DQ0QGNuaQV42MXLSkrLeAyjf4UzlIxm04g==",
      "salt" : "wue854XfHBduqKFcgm9tNQ==",
      "hashIterations" : 100000,
      "algorithm" : "pbkdf2-sha256"
    } ],
    "realmRoles" : [ "admin" ]
  } ]
} ]

Start the server :


When you experience an error at startup, check if it is not a port which is already taken, like port 8080 (we use 8081) or 9990 (we use 9991) In tat case change the config file (at the end) :

nano standalone/configuration/standalone.xml

Access server from outside

Open the ports (adapt the numbers) :

ufw allow 8081
ufw allow 9991

Launch the server on the IP of the network card of the machine :


note the IP of the network card (network adapter)

nano standalone/configuration/standalone.xml

search this phrase : and change with the IP address of the network card. Idem for jboss.bind.address

Start the server and go to the address x.x.x.x:8081

Condigure SSL to access to the server with HTTPS

1- nano /usr/local/bin/ and put (adjust the 3 variables, YOURPASSWORD musn't contain any special char or space) :



# Create the SSL cerficat with Lets'Encrypt
certbot certonly --renew-by-default --standalone -d $SITE --pre-hook "service apache2 stop" --post-hook "service apache2 start"

# move to the correct let's encrypt directory
cd /etc/letsencrypt/live/$SITE

# copy the files
cp cert.pem /etc/ssl/certs/$SITE.cert.pem
cp fullchain.pem /etc/ssl/certs/$SITE.fullchain.pem
cp privkey.pem /etc/ssl/private/$SITE.privkey.pem

# adjust permissions of the private key
chown :ssl-cert /etc/ssl/private/$SITE.privkey.pem
chmod 640 /etc/ssl/private/$SITE.privkey.pem

# Add certificate to Wildfly for Keycloak
rm cert_and_key.pkcs12
rm keycloak.jks
# Merge the public keys, intermediate and private in a file pkcs12
openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out cert_and_key.pkcs12 -CAfile chain.pem -caname root -name $SITE -password pass:secret
# Generate the file jks (for java) from the pkcs12 and assign the passwords
keytool -importkeystore -srckeystore cert_and_key.pkcs12 -srcstoretype PKCS12 -srcstorepass secret -destkeystore keycloak.jks -deststorepass $YOURPASSWORD -destkeypass $YOURPASSWORD
# Copy this file into the Keycloak config folder
cp keycloak.jks $KEYCLOAKPATH/standalone/configuration

2- Make the script readable and execute it :

chmod +x /usr/local/bin/

3- Remember to manually execute it before certificate expiration and reload Keycloak after running /usr/local/bin/

4- Configure WildFly to accept HTTPS requests

    cd /var/www/keycloak/keycloak-xxx.Final
    nano standalone/configuration/standalone.xml

For Keycloak 3.2, add the following lines inside <security-realms> ... </security-realms> and replace YOURPASSWORD :

    <security-realm name="UndertowRealm">
                <keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="YOURPASSWORD" />

For Keycloak 3.3, replace

<keystore path="application.keystore" relative-to="jboss.server.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>


<keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="YOURPASSWORD"/>


<http-listener name="default" socket-binding="http" redirect-socket="https"/>


    <http-listener name="default" socket-binding="http" proxy-address-forwarding="true" redirect-socket="proxy-https" />

Add the following line inside <socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}"> ... </socket-binding-group> :

    <socket-binding name="proxy-https" port="443"/>

If needed :

6- Open port 8443 in the firewall and start the server

ufw allow 8443

Access the server here : https://domain.ext:8443

Configure the MySQL database

Official documentation

sudo su
cd /var/www/keycloak
mkdir ./modules/system/layers/keycloak/org/mysql
mkdir ./modules/system/layers/keycloak/org/mysql/main

Download the Java module of MySQL Unzip and copy the file mysql-connector-java-x.x.x-bin.jar to ./modules/system/layers/keycloak/org/mysql/main then rename it to mysql-connector-java-bin.jar and make it executable : chmod +x modules/system/layers/keycloak/org/mysql/main/mysql-connector-java-bin.jar

Create the file /{DOSSIER-KEYCLOAK}/modules/system/layers/keycloak/org/mysql/main/module.xml and put :

<?xml version="1.0" ?>
<module xmlns="urn:jboss:module:1.3" name="org.mysql">

        <resource-root path="mysql-connector-java-bin.jar"/>

        <module name="javax.api"/>
        <module name="javax.transaction.api"/>

Create the database idp_keycloak (don't use - char) with utf8_general_ci, create user idp-keycloak and give him the rights of this this base.

Caution ! Avoid special characters in the MySQL password like the & char.

Edit the file standalone/configuration/standalone.xml, replace what is between <subsystem xmlns="urn:jboss:domain:datasources:x.x"> and </subsystem> with (replace MYSQL-USER-PASSWORD) :

                <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
                <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
                    <driver name="mysql" module="org.mysql">
                    <driver name="h2" module="com.h2database.h2">

Then, before restarting the server, add again a new admin user (because he was registered in the old database) :

    bin/ -u username

Launch the Widfly server when the machine starts

sudo nano /etc/rc.local

and add : /var/www/keycloak/keycloak-server/bin/ &

Currently under development

    cd /var/www/keycloak
    unzip -d .
    cd bin
    ./ --file=adapter-install-offline.cli

Add an IdP provider

Example with Facebook

Realm configuration

Configure :

  • Realm settings :
    • Login : all "on" except "email as username" or "Edit username" ; require SSL -> all requests
    • Email
    • Theme : enable internationalization and select "fr" only
    • Security Defenses (if iframe) : replace Content-Security-Policy by frame-src 'self' domain.ext sub.domain.ext; frame-ancestors 'self' domain.ext sub.domain.ext; object-src 'self' domain.ext sub.domain.ext;
    • Tokens : SSO session max : 365 days / Offline Session Idle : 400 days
  • Authentification :
    • Passowrd Policy : Digits 1 / Minimum Length : 8 / Not Username / Uppercase Characters : 1

Mass import users

Create a json file containing, for example (password is dkPK62wdofPEK56-_&SO) :

  "realm": "master",
  "users" : [
      "username" : "test1",
      "email" : "test1@mail.ext",
      "firstName" : "Toto1",
      "lastName" : "Titi1",
      "enabled" : true,
      "emailVerified" : true,
      "credentials" : [ {
        "type" : "password",
        "hashedSaltedValue" : "IhS5rpj4PV20PURXYtQktPAYDbI5ATQdafxquCWs1mKwJtuvxhW2DQ0QGNuaQV42MXLSkrLeAyjf4UzlIxm04g==",
        "salt" : "wue854XfHBduqKFcgm9tNQ==",
        "hashIterations" : 100000,
        "algorithm" : "pbkdf2-sha256"
      } ],
      "realmRoles" : [ "offline_access", "uma_authorization" ]
      "username" : "test2",
      "email" : "test2@mail.ext",
      "firstName" : "Toto2",
      "lastName" : "Titi2",
      "enabled" : true,
      "emailVerified" : true,
      "credentials" : [ {
        "type" : "password",
        "hashedSaltedValue" : "IhS5rpj4PV20PURXYtQktPAYDbI5ATQdafxquCWs1mKwJtuvxhW2DQ0QGNuaQV42MXLSkrLeAyjf4UzlIxm04g==",
        "salt" : "wue854XfHBduqKFcgm9tNQ==",
        "hashIterations" : 100000,
        "algorithm" : "pbkdf2-sha256"
      } ],
      "realmRoles" : [ "offline_access", "uma_authorization" ]

And import it in the admin user interface (Manage -> Import)

Create a new client

Infos : https://domain.ext:8443/auth/realms/master/.well-known/openid-configuration

More info :

The Reaml X.509 Certificate can be found in Realm settings -> Keys -> Certificate

Logout URL : https://domain.ext:8443/auth/realms/master/protocol/openid-connect/logout?redirect_uri=encodedURL

OpenId Connect

  • Client Protocol : openid-connect
  • Access Type : confidential
  • Standard Flow Enabled : ON
  • Implicit Flow Enabled : OFF
  • Direct Access Grants Enabled : ON
  • Service Accounts Enabled : OFF
  • Authorization Enabled : OFF
  • Root URL : htts://domain.ext
  • Valid Redirect URIs : htts://domain.ext/*
  • Fine Grain OpenID Connect Configuration :
    • User Info Signed Response Algorithm : unsigned
    • Request Object Signature Algorithm : any

Wordpress OpenID Connect Login

  1. Create an OpenId Connect client
  2. Install OpenId Connect Generic plugin
  3. Configure :
  4. Go to "Settings" -> "Permalinks" and save again the configuration (even if no changes)
  5. Add the button : [openid_connect_generic_login_button]
  6. In openid-connect-generic-client-wrapper.php : replace the pattern of preg_replace by '/[^a-zA-Z\-\_0-9]/'
  7. In openid-connect-generic-login-form.php : replace Login with OpenID Connect by Login / Sign up
  8. in openid-connect-generic-client.php : remove all the if after // check the client request state

If doesn't work because of "cURL connection refused" see this workaround

If you want users to connect to Wordpress throw an iframe, add this in the theme function.php :

// Allow to connect from an iframe
remove_action( 'login_init', 'send_frame_options_header' );
remove_action( 'admin_init', 'send_frame_options_header' );

Wordpress SAML OneLogin plugin

Change "master" with your Realm name if different, and 8443 with your custom port if different

In OneLogin settings :

  • IdP Identity id : https://URL:8443/auth/realms/master
  • Single Sign On Service Url : https://URL:8443/auth/realms/master/protocol/saml
  • X.509 Certificate : Keycloak -> Realm settings -> Keys -> Certificate
    • Username : username
    • E-mail : email
    • First Name : first_name
    • Last Name : last_name
    • Role : Role
  • ROLE PRECEDENCE : Administrator : 1, Editor : 2, etc.
  • Service Provider Entity Id : Keycloak -> Client -> Client ID
  • Sign AuthnRequest : Checked
  • Sign LogoutRequest : Checked
  • Sign LogoutResponse : Checked
  • NameIDFormat : ... SAML:2.0 ... persistent
  • Service Provider X.509 Certificate & Service Provider Private Key : put a temporary cerficificate
  • Signature Algorithm & Digest Algorithm: RSA_SHA256

Click on "Go to the metadata of this SP" (top right) : download in a XML file

In KeyCloak : add a new client

  • Select file : the XML file and save

If you need to configure manually :

Tab Mappers

  • Roles list -> edit :
    • Role attribute name : Role
    • SAML Attribute NameFormat : unspecified
    • Single Role Attribute : ON
  • Create :
    • Name : username
    • Mapper Type : User Property
    • Property : username
    • Friendly Name : username
    • SAML Attribute Name : username
    • SAML Attribute NameFormat : unspecified
  • Create again for : email, first_name, last_name

Keycloak -> Client -> Client Id -> SAML Keys -> Generate new keys

Copy values in Onelogin plugin -> Service Provider X.509 Certificate & Service Provider Private Key


OpenID-Connect-PHP GitHub repository

It's a simple library that allows an application to authenticate a user through the basic OpenID Connect flow.

Install :

composer require jumbojett/openid-connect-php

Create a test script test.php

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);

require "./vendor/autoload.php";

$oidc = new OpenIDConnectClient('',

$userInfo = $oidc->requestUserInfo();
$name = $userInfo->preferred_username; // see KeyCloak client Mappers tab (field "Token Claim Name")

    <title>Example OpenID Connect Client Use</title>
        body {
            font-family: 'Lucida Grande', Verdana, Arial, sans-serif;

        <p>Hello <?php echo $name; ?></p>
        <p>UserInfo array : <?php print_r($userInfo); ?></p>


Yii2 OpenIdConnect



composer require --prefer-dist yiisoft/yii2-authclient "~2.1.0"

Edit config/web.php and add in 'compenents' :

        'authClientCollection' => [
            'class' => 'yii\authclient\Collection',
            'clients' => [
                'YourName' => [
                    'class' => 'yii\authclient\clients\Keycloak',
                    'issuerUrl' => 'https://domain.ext:8443/auth/realms/master',
                    'clientId' => 'KeycloakClientId',
                    'clientSecret' => '*************',



Open the file controllers/SiteController.php, add use app\components\AuthHandler; at the beginning of the file, and theses 2 functions inside class SiteController extends Controller :

    public function actions()
        return [
            'auth' => [
                'class' => 'yii\authclient\AuthAction',
                'successCallback' => [$this, 'onAuthSuccess'],

    public function onAuthSuccess($client)
        (new AuthHandler($client))->handle();

The class class AuthHandler must be in components/AuthHandler.php (create components folder if not exist) and create the file AuthHandler.php containing the implementation describe here

Add the widget in a view file (eg. views/site/login.php) :

<?= yii\authclient\widgets\AuthChoice::widget([
     'baseAuthUrl' => ['site/auth'],
     'popupMode' => false,
]) ?>

Install OpenId Connect


sudo apt update;
sudo apt install libgmp-dev php-gmp;
sudo service apache2 reload;
composer require --prefer-dist "spomky-labs/jose:~5.0.6"

The class OpenIdConnect extends OAuth2 See OpenId Connect specifications

Create the Auth.php file if not exist

nano models/Auth.php :

namespace app\models;
use Yii;
class Auth extends \yii\db\ActiveRecord
    public static function tableName()
        return 'user_auth';

    public function rules()
        return [
            ['user_id', 'source', 'source_id'], 'required'],
------------.html)['user_id'], 'integer'],
------------.html)['source', 'source_id'], 'string', 'max' => 255]

    public function attributeLabels()
        return [
            'id' => 'ID',
            'user_id' => 'User ID',
            'source' => 'Source',
            'source_id' => 'Source ID',

    public function getUser()
        return $this->hasOne(User::className(), ['id' => 'user_id']);

Create a Keycloak client

Create the file vendor/yiisoft/yii2-authclient/clients/Keycloak.php containing :


namespace yii\authclient\clients;

use yii\authclient\OpenIdConnect;

class Keycloak extends OpenIdConnect
    public function applyAccessTokenToRequest($request, $accessToken)
        $data = $request->getData();
        $data['Authorization'] = 'Bearer ' . $accessToken->getToken();

    protected function defaultName()
        return 'keycloak';

    protected function defaultTitle()
        return 'Centralized authentification';



At the end of function requestTokens($code) :

$return = parent::fetchAccessToken($authCode, $params);
file_put_contents("/var/www/", serialize($return));
return $return;

cat /var/www/

Copy and paste the token (just after access_token";s:1746:") in

Node.Js with Oauth2

Symfony OpenIdConnect

  1. Install Symfony 2.8 demo
  2. Install OpenId Connect Relying Party Bundle but replace "gree/jose": "0.1.7" with "gree/jose" : "~2.0"

Example of config for :

    base_url: ""
    client_id: "sso-symfony-marc-fun"         #OpenID Connect client id given by the OpenId Connect Provider
    client_secret: "xxxxxxxx-xxxx-xxxx-xxxxx-xxxxxxxx" #OpenID Connect client secret given by the OpenId Connect Provider
    issuer: "" #URL of the OpenID Connect Provider
    endpoints_url:                  #Part of the URL of the OpenID Connect Provider
        authorization: "/auth"
        token: "/token"
        userinfo: "/userinfo"
        logout: "/logout"
    display: "page"                   #How the authentication form will be display to the enduser
    scope: "email profile openid offline_access" #List of the scope you need
    authentication_ttl: 300         #Maximum age of the authentication
    token_ttl: 300                  #Maximum age for tokenID
    jwk_url: "" #URL to the Json Web Key of OpenID Connect Provider
    jwk_cache_ttl: 86400             #Validity periods in second where the JWK store in cache is valid
    enabled_state: true             #Enable the use of the state value. This is useful for mitigate replay attack
    enabled_nonce: true             #Enable the use of the nonce value. This is useful for mitigate replay attack
    enduserinfo_request_method: "POST" #Define the method (POST, GET) used to request the Enduserinfo Endpoint of the OIDC Provider
    redirect_after_logout: ""           #URI or route name used for redirect user after a logout

Edit vendor/waldo/openid-connect-relying-party-bundle/Waldo/OpenIdConnect/RelyingPartyBundle/OpenIdConnect/Constraint/IDTokenValidator.php and comment $this->errors[] = "Issuer are not the same"; because Keycloak return "" and the script is comparing to the value of the issuer in config.yml wich is ""

To show the result of SSO, edit vendor/waldo/openid-connect-relying-party-bundle/Waldo/OpenIdConnect/RelyingPartyBundle/OpenIdConnect/ResourceOwner/AbstractGenericOICResourceOwner.php and after $oicToken->setRawTokenData($content); add :

echo '<pre>'; print_r($content); echo '</pre>'; exit;

The logout URL is <a href="{{ path('_oic_rp_logout') }}">Logout</a>

----public-function-attributelabels() ----{ --------return-[ ------------'id'-=>-'id', ------------'user_id'-=>-'user-id', ------------'source'-=>-'source', ------------'source_id'-=>-'source-id', --------]; ----}

----public-function-getuser() ----{ --------return-$this->hasone(user::classname(),-['id'-=>-'user_id']); ----} }






class-keycloak-extends-openidconnect { ----public-function-applyaccesstokentorequest($request,-$accesstoken) ----{ --------$data-=-$request->getdata(); --------$data['authorization']-=-'bearer-'-.-$accesstoken->gettoken(); --------$request->setheaders($data); ----}

----protected-function-defaultname() ----{ --------return-'keycloak'; ----}

----protected-function-defaulttitle() ----{ --------return-'centralized-authentification'; ----} }




$return-=-parent::fetchaccesstoken($authcode,-$params); file_put_contents("/var/www/",-serialize($return)); return-$return;







----base_url:-"" ----client_id:-"sso-symfony-marc-fun"---------#openid-connect-client-id-given-by-the-openid-connect-provider ----client_secret:-"xxxxxxxx-xxxx-xxxx-xxxxx-xxxxxxxx"-#openid-connect-client-secret-given-by-the-openid-connect-provider ----issuer:-""-#url-of-the-openid-connect-provider ----endpoints_url:------------------#part-of-the-url-of-the-openid-connect-provider --------authorization:-"/auth" --------token:-"/token" --------userinfo:-"/userinfo" --------logout:-"/logout" ----display:-"page"-------------------#how-the-authentication-form-will-be-display-to-the-enduser ----scope:-"email-profile-openid-offline_access"-#list-of-the-scope-you-need ----authentication_ttl:-300---------#maximum-age-of-the-authentication ----token_ttl:-300------------------#maximum-age-for-tokenid ----jwk_url:-""-#url-to-the-json-web-key-of-openid-connect-provider ----jwk_cache_ttl:-86400-------------#validity-periods-in-second-where-the-jwk-store-in-cache-is-valid ----enabled_state:-true-------------#enable-the-use-of-the-state-value.-this-is-useful-for-mitigate-replay-attack ----enabled_nonce:-true-------------#enable-the-use-of-the-nonce-value.-this-is-useful-for-mitigate-replay-attack ----enduserinfo_request_method:-"post"-#define-the-method-(post,-get)-used-to-request-the-enduserinfo-endpoint-of-the-oidc-provider ----redirect_after_logout:-""-----------#uri-or-route-name-used-for-redirect-user-after-a-logout




';-exit; ```


-- -- -- -- -- -- -- -- -- -- -- .html)

You can join us at any moment on the chat.