Keycloak
-
Keycloak IdP for SSO
To explore : http://www.keycloak.org/docs/latest/securing_apps/index.html#_mod_auth_openidc
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-installerFetch the software :
cd /var/www wget https://downloads.jboss.org/keycloak/xxx.Final/keycloak-xxx.Final.zip unzip keycloak-xxx.Final.zip -d . cd keycloak-xxx.Final wget https://downloads.jboss.org/keycloak/xxx.Final/adapters/keycloak-oidc/keycloak-wildfly-adapter-dist-xxx.Final.zip unzip keycloak-wildfly-adapter-dist-xxx.Final.zip -d . rm *.zipAdd the user admin :
./bin/add-user-keycloak.sh -u usernameif it returns the message
No content to map due to end-of-input, then create a temporary admin usertempuserwith the passworddkPK62wdofPEK56-_&SO:nano ./standalone/configuration/keycloak-add-user.jsonPut 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 :
./bin/standalone.shWhen 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.xmlAccess server from outside
Open the ports (adapt the numbers) :
ufw allow 8081 ufw allow 9991Launch the server on the IP of the network card of the machine :
ifconfignote the IP of the network card (network adapter)
nano standalone/configuration/standalone.xmlsearch this phrase : jboss.bind.address.management: and change 127.0.0.1 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
Configure SSL to access to the server with HTTPS
With Apache reverse proxy.
nano standalone/configuration/standalone.xmlIn
<http-listener, addproxy-address-forwarding="true"to have :<http-listener name="default" socket-binding="http" redirect-socket="proxy-https" proxy-address-forwarding="true" enable-http2="true"/>sudo a2enmod headers cd /etc/apache2/sites-availableCreate the file
keycloak.domain.ext.conf<VirtualHost *:80> ServerName keycloak.domain.ext ServerAdmin email@domain.ext ProxyPass / http://000.000.000.000:8081/ ProxyPassReverse / http://000.000.000.000:8081/ ProxyRequests Off ProxyPreserveHost Off ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>a2ensite keycloak.domain.ext.conf service apache2 reload certbot --apache -d keycloak.domain.extEdit
keycloak.domain.ext-le-ssland add inside the<VirtualHost *:443>tag :ProxyPreserveHost On SSLProxyEngine On SSLProxyCheckPeerCN on SSLProxyCheckPeerExpire on RequestHeader set X-Forwarded-Proto "https" RequestHeader set X-Forwarded-Port "443"service apache2 reloadConfigure the MySQL database
sudo su cd /var/www/keycloak mkdir ./modules/system/layers/keycloak/com/mysql mkdir ./modules/system/layers/keycloak/com/mysql/mainDownload the Java module of MySQL (select "Platform independant"). Latest working version is : 5.1.45. So you can directely download with
wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.45.zip.Unzip and copy the file
mysql-connector-java-x.x.x-bin.jarto./modules/system/layers/keycloak/com/mysql/main, rename it tomysql-connector-java-bin.jarand make it executable :chmod +x modules/system/layers/keycloak/com/mysql/main/mysql-connector-java-bin.jarnano ./modules/system/layers/keycloak/com/mysql/main/module.xmland put :<?xml version="1.0" ?> <module xmlns="urn:jboss:module:1.3" name="com.mysql"> <resources> <resource-root path="mysql-connector-java-bin.jar"/> </resources> <dependencies> <module name="javax.api"/> <module name="javax.transaction.api"/> </dependencies> </module>Create the database
idp_keycloak(don't use-char) withutf8mb4_general_ci, create useridp_keycloakand 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) :<datasources> <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}"> <connection-url>jdbc:mysql://localhost/idp_keycloak?useSSL=false&characterEncoding=UTF-8</connection-url> <driver>mysql</driver> <pool> <max-pool-size>20</max-pool-size> </pool> <security> <user-name>idp_keycloak</user-name> <password>MYSQL-USER-PASSWORD</password> </security> </datasource> <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true"> <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url> <driver>h2</driver> <security> <user-name>sa</user-name> <password>sa</password> </security> </datasource> <drivers> <driver name="mysql" module="com.mysql"> <driver-class>com.mysql.jdbc.Driver</driver-class> </driver> <driver name="h2" module="com.h2database.h2"> <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class> </driver> </drivers> </datasources>Then, before restarting the server, add again a new admin user (because he was registered in the old database) :
bin/add-user-keycloak.sh -u usernameLaunch the Widfly server when the machine starts
sudo nano /etc/rc.localand add :
/var/www/keycloak/keycloak-server/bin/standalone.sh &Currently under development
cd /var/www/keycloak wget https://downloads.jboss.org/keycloak/3.2.1.Final/adapters/keycloak-oidc/keycloak-wildfly-adapter-dist-3.2.1.Final.zip unzip keycloak-wildfly-adapter-dist-3.2.1.Final.zip -d . rm keycloak-wildfly-adapter-dist-3.2.1.Final.zip cd bin ./jboss-cli.sh --file=adapter-install-offline.cliAdd an IdP provider
Realm configuration
Configure :
- Realm settings :
- Login : all "on" except "email as username" or "Edit username" ; require SSL -> all requests
- 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
Create a custom theme
Official documentation Tutorial
To create theme, remove caching
nano standalone/configuration/standalone.xml:<theme> <staticMaxAge>-1</staticMaxAge> <cacheThemes>false</cacheThemes> <cacheTemplates>false</cacheTemplates>select the custom theme
For the welcome page :
nano standalone/configuration/standalone.xml:<theme> <welcomeTheme>custom-theme</welcomeTheme>For the other pages : go in the administration -> Domain configuration -> theme
When your theme is ready, put back caching
nano standalone/configuration/standalone.xml:<theme> <staticMaxAge>2592000</staticMaxAge> <cacheThemes>true</cacheThemes> <cacheTemplates>true</cacheTemplates>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 :
- https://www.infoq.com/fr/articles/introduction-openid-connect
- http://www.keycloak.org/docs-api/3.2/rest-api/
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
- Create an OpenId Connect client
- Install OpenId Connect Generic plugin
- Configure :
- copy/paste the examples (For "OpenID Scope" (more info here)
- for the URLs : https://domain.ext:8443/auth/realms/master/.well-known/openid-configuration (Login Endpoint URL = .../auth)
- check "Identify with User Name, "Link Existing Users", "Redirect Back to Origin Page" and "Alternate Redirect URI"
- for "Identity Key" : if you connect with a username, use
preferred_usernameotherwise use Keyclok id :subfor the Identity Key
- Go to "Settings" -> "Permalinks" and save again the configuration (even if no changes)
- Add the button :
[openid_connect_generic_login_button] - In
openid-connect-generic-client-wrapper.php: replace the pattern ofpreg_replaceby'/[^a-zA-Z\-\_0-9]/' - In
openid-connect-generic-login-form.php: replaceLogin with OpenID ConnectbyLogin / Sign up - in
openid-connect-generic-client.php:
- remove all the if after
// check the client request state - replace :
$id_token_claim = json_decode( base64_decode( $tmp[1] ), TRUE );by :
$id_token_claim = json_decode( base64_decode( str_replace( // because token is encoded in base64 URL (and not just base64) array('-', '_'), array('+', '/'), $tmp[1] ) ) , TRUE );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
- ATTRIBUTE MAPPING :
- 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 :
- Client Id : give any custom name
- Save
- Client protocol : SAML
- Include AuthnStatement : ON
- Sign Documents : ON
- Signature Algorithm : RSA_SHA256
- Canonicalization Method : EXCLUSIVE
- Client Signature Required : ON
- Force POST Binding : ON
- Front Channel Logout : ON
- Name ID Format : persistent
- Valid Redirect URIs : https://wordpress_URL/wp-login.php?saml_acs
- Fine Grain SAML Endpoint Configuration :
- Assertion Consumer Service POST Binding URL : https://wordpress_URL/wp-login.php?saml_acs
- Logout Service Redirect Binding URL : https://wordpress_URL/wp-login.php?saml_sls
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
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-phpCreate a test script
test.php<?php ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); require "./vendor/autoload.php"; $oidc = new OpenIDConnectClient('https://idp.make.social:8443/auth/realms/master', 'OpenID-Connect-PHP', 'b16fbf2b-7aa3-4f02-a488-d3e04f26cb3e'); $oidc->authenticate(); $userInfo = $oidc->requestUserInfo(); $name = $userInfo->preferred_username; // see KeyCloak client Mappers tab (field "Token Claim Name") ?> <html> <head> <title>Example OpenID Connect Client Use</title> <style> body { font-family: 'Lucida Grande', Verdana, Arial, sans-serif; } </style> </head> <body> <div> <p>Hello <?php echo $name; ?></p> <p>UserInfo array : <?php print_r($userInfo); ?></p> </div> </body> </html>Yii2 OpenIdConnect
Install
composer require --prefer-dist yiisoft/yii2-authclient "~2.1.0"Edit
config/web.phpand 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' => '*************', ], ], ],Start
Open the file
controllers/SiteController.php, adduse app\components\AuthHandler;at the beginning of the file, and theses 2 functions insideclass 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 AuthHandlermust be incomponents/AuthHandler.php(create components folder if not exist) and create the fileAuthHandler.phpcontaining the implementation describe hereAdd 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:<?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'], [['user_id'], 'integer'], [['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.phpcontaining :<?php namespace yii\authclient\clients; use yii\authclient\OpenIdConnect; class Keycloak extends OpenIdConnect { public function applyAccessTokenToRequest($request, $accessToken) { $data = $request->getData(); $request->setHeaders($data); } protected function defaultName() { return 'keycloak'; } protected function defaultTitle() { return 'Centralized authentification'; } }Debug
vendor/yiisoft/yii2-authclient/OpenIdConnect.php
At the end of
function requestTokens($code):$return = parent::fetchAccessToken($authCode, $params); file_put_contents("/var/www/test.make.social/public_html/test.txt", serialize($return)); return $return;cat /var/www/test.make.social/public_html/test.txtCopy and paste the token (just after
access_token";s:1746:") in https://jwt.io/Node.Js with Oauth2
Symfony OpenIdConnect
- Install Symfony 2.8 demo
- Install OpenId Connect Relying Party Bundle but replace
"gree/jose": "0.1.7"with"gree/jose" : "~2.0"
Example of config for :
base_url: "https://sso-synfony.marc.fun" 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: "https://login.lescommuns.org:8443/auth/realms/master/protocol/openid-connect" #URL of the OpenID Connect Provider endpoints_url: #Part of the URL of the OpenID Connect Provider 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: "https://login.lescommuns.org:8443/auth/realms/master/protocol/openid-connect/certs" #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: "https://sso-synfony.marc.fun" #URI or route name used for redirect user after a logoutEdit
vendor/waldo/openid-connect-relying-party-bundle/Waldo/OpenIdConnect/RelyingPartyBundle/OpenIdConnect/Constraint/IDTokenValidator.phpand comment$this->errors[] = "Issuer are not the same";because Keycloak return "https://login.lescommuns.org:8443/auth/realms/master" and the script is comparing to the value of the issuer inconfig.ymlwich is "https://login.lescommuns.org:8443/auth/realms/master/protocol/openid-connect"To show the result of SSO, edit
vendor/waldo/openid-connect-relying-party-bundle/Waldo/OpenIdConnect/RelyingPartyBundle/OpenIdConnect/ResourceOwner/AbstractGenericOICResourceOwner.phpand 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>Upgrading
https://www.keycloak.org/docs/latest/release_notes/ https://www.keycloak.org/docs/latest/upgrading
- Prepare an other instance of Keycloak (see above), including Widfly and MySQL adaptors. Do not add users.
- Copy database (to a new one),
standalonedirectory (updatestandalone.xmlwith new database name) and custom theme from the old Keycloak. - Stop the old server to avoid ports conflicts.
- When the new server is lunched, standalone.xml file and database are - automatically updated for the new version.
Links to explore
- http://blog.keycloak.org/2015/04/running-keycloak-cluster-with-docker.html
- http://rhelblog.redhat.com/2016/03/22/red-hat-federation-story-ipsilon-keycloak-a-clash-of-the-titans/
- http://rhelblog.redhat.com/2016/04/26/why-use-sssd-instead-of-a-direct-ldap-configuration-for-applications/
- https://keycloak.gitbooks.io/server-adminstration-guide/content/topics/user-federation/sssd.html
- https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html
- https://github.com/keycloak/keycloak-js-bower
- https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/nodejs-adapter.html
- https://github.com/keycloak/keycloak-nodejs-connect
- https://github.com/keycloak/keycloak-nodejs-auth-utils
- http://blog.keycloak.org/2015/08/improved-ldap-integration-with-keycloak.html
- http://keycloak-user.88327.x6.nabble.com/keycloak-user-Create-user-from-keycloak-UI-with-FreeIPA-backend-td1653.html
- Realm settings :