Skip to content

Commit

Permalink
KEYCLOAK-1780 documentation + Generic client authentication screen
Browse files Browse the repository at this point in the history
  • Loading branch information
mposolda committed Sep 1, 2015
1 parent fbf62b2 commit be83941
Show file tree
Hide file tree
Showing 15 changed files with 319 additions and 15 deletions.
140 changes: 138 additions & 2 deletions docbook/reference/en/en-US/modules/auth-spi.xml
Expand Up @@ -200,7 +200,7 @@ Forms Subflow - ALTERNATIVE
</orderedlist>
</para>
</section>
<section>
<section id="auth_spi_walkthrough">
<title>Authenticator SPI Walk Through</title>
<para>
In this section, we'll take a look at the Authenticator interface. For this, we are going to implement an authenticator
Expand Down Expand Up @@ -502,7 +502,7 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
bean as well.
</para>
</section>
<section>
<section id="adding_authenticator">
<title>Adding Authenticator to a Flow</title>
<para>
Adding an Authenticator to a flow must be done in the admin console.
Expand Down Expand Up @@ -865,4 +865,140 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
authenticator.
</para>
</section>

<section id="client_authentication">
<title>Authentication of clients</title>
<para>Keycloak actually supports pluggable authentication for <ulink url="http://openid.net/specs/openid-connect-core-1_0.html">OpenID Connect</ulink>
client applications. Authentication of client (application) is used under the hood by the <link linkend="adapter-config">Keycloak adapter</link>
during sending any backchannel requests to the Keycloak server (like the request for exchange code to access token after
successful authentication or request to refresh token). But the client authentication can be also used directly by you during
<link linkend="direct-access-grants">Direct Access grants</link> or during <link linkend="service-accounts">Service account</link> authentication.
</para>
<section>
<title>Default implementations</title>
<para>
Actually Keycloak has 2 builtin implementations of client authentication:
<variablelist>
<varlistentry>
<term>Traditional authentication with client_id and client_secret</term>
<listitem>
<para>
This is default mechanism mentioned in the <ulink url="http://openid.net/specs/openid-connect-core-1_0.html">OpenID Connect</ulink>
or <ulink url="http://tools.ietf.org/html/rfc6749">OAuth2</ulink> specification and Keycloak supports it since it's early days.
The public client needs to include <literal>client_id</literal> parameter with it's ID in the POST request (so it's defacto not authenticated)
and the confidential client needs to include <literal>Authorization: Basic</literal> header with
the clientId and clientSecret used as username and password.
</para>
<para>
For the public/javascript clients, you
don't need to add anything into your keycloak.json configuration file. For the confidential (server) clients, you need to add something like this:
<programlisting><![CDATA[
"credentials": {
"secret": "mysecret"
}
]]></programlisting>

where the <literal>mysecret</literal> needs to be replaced with the real value of client secret. You can obtain it from client admin console.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Authentication with signed JWT</term>
<listitem>
<para>
This is based on the <ulink url="https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03">JWT Bearer Token Profiles for OAuth 2.0</ulink> specification.
The client/adapter generates the <ulink url="https://tools.ietf.org/html/rfc7519">JWT</ulink> and signs it with his private key.
The Keycloak then verifies the signed JWT with the client's public key and authenticates client based on it.
</para>
<para>
To achieve this, you need those steps:
<itemizedlist>
<listitem>Your client needs to have private key and Keycloak needs to have client public key. This can be either:
<itemizedlist>
<listitem>
Generated in Keycloak admin console - In this case, Keycloak will generate pair of keys and it will save
public key and certificate in it's DB. The keystore file with the private key will be downloaded and you need to save it
in the location accessible to your client application
</listitem>
<listitem>
Uploaded in Keycloak admin console - This option is useful if you already has existing private key of your client.
In this case, you just need to upload the public key and certificate to the Keycloak server.
</listitem>
</itemizedlist>
In both cases, the private key is not saved in Keycloak DB, but it's owned exclusively by your client. The Keycloak DB has just public key.
</listitem>
<listitem>
As second step, you need to use the configuration like this in your <literal>keycloak.json</literal> adapter configuration:
<programlisting><![CDATA[
"credentials": {
"jwt": {
"client-keystore-file": "classpath:keystore-client.jks",
"client-keystore-type": "JKS",
"client-keystore-password": "storepass",
"client-key-password": "keypass",
"client-key-alias": "clientkey",
"token-expiration": 10
}
}
]]></programlisting>
The <literal>client-keystore-file</literal> is the location of the keystore file, which is either on classpath
(for example if bundled in the WAR itself) or somewhere on the filesystem. Other options specify type of keystore and password of keystore itself
and of the private key. Last option <literal>token-expiration</literal> is the expiration of JWT in seconds. The token needs to be valid
just for single request, so 10 seconds is usually sufficient.
</listitem>
</itemizedlist>
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>
See the demo example and especially the <literal>examples/preconfigured-demo/service-account</literal>
for the example application showing service accounts authentication with both clientId+clientSecret and with signed JWT.
</para>
</section>

<section>
<title>Implement your own client authenticator</title>
<para>
For plug your own client authenticator, you need to implement few interfaces on both client (adapter) and server side.
<variablelist>
<varlistentry>
<term>Client side</term>
<listitem>
<para>
Here you need to implement <literal>org.keycloak.adapters.authentication.ClientCredentialsProvider</literal> and put the implementation either to:
<itemizedlist>
<listitem>your WAR file into WEB-INF/classes . But in this case, the implementation can be used just for this single WAR application</listitem>
<listitem>Some JAR file, which will be added into WEB-INF/lib of your WAR</listitem>
<listitem>Some JAR file, which will be used as jboss module and configured in jboss-deployment-structure.xml of your WAR.</listitem>
</itemizedlist>
In all cases, you also need to create the file <literal>META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider</literal>
either in the WAR or in your JAR.
</para>
<para>
You also need to configure your clientCredentialsProvider in <literal>keycloak.json</literal> . See the javadoc for more details.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Server side</term>
<listitem>
<para>
Here you need to implement <literal>org.keycloak.authentication.ClientAuthenticatorFactory</literal> and
<literal>org.keycloak.authentication.ClientAuthenticator</literal> . You also need to add the file
<literal>META-INF/services/org.keycloak.authentication.ClientAuthenticatorFactory</literal> with the name of the implementation classes.
See <link linkend="auth_spi_walkthrough">authenticators</link> for more details.
</para>
<para>
Finally you need to configure admin console . You need to create new client authentication flow and define execution
with your authenticator (you can also add the builtin authenticators and configure requirements etc)
and finally configure Clients binding . See <link linkend="adding_authenticator">Adding Authenticator</link> for more details.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
</section>
</chapter>
8 changes: 7 additions & 1 deletion docbook/reference/en/en-US/modules/direct-access.xml
Expand Up @@ -30,7 +30,9 @@
an access token for. You must also pass along the "client_id" of the client you are creating
an access token for. This "client_id" is the Client Id specified in admin console (not it's id from DB!). Depending on
whether your client is <link linkend='access-types'>"public" or "confidential"</link>, you may also have to pass along
it's client secret as well. Finally you need to pass "grant_type" parameter with value "password" .
it's client secret as well. We support pluggable client authentication, so alternatively you can use other form of client credentials like signed JWT assertion.
See <link linkend="client_authentication">Client Authentication</link> section for more details. Finally you need to pass "grant_type"
parameter with value "password" .
</para>
<para>
For public client's, the POST invocation requires form parameters that contain the username,
Expand Down Expand Up @@ -71,6 +73,10 @@ Pragma: no-cache
</programlisting>

</para>
<para>As mentioned above, we support also other means of authenticating clients. In adition to default client_id and client secret,
we also have signed JWT assertion by default. There is possibility to use any other form of client authentication implemented by you. See <link linkend="client_authentication">Client Authentication</link>
section for more details.
</para>
<para>
Here's a Java example using Apache HTTP Client and some Keycloak utility classes.:
<programlisting><![CDATA[
Expand Down
6 changes: 4 additions & 2 deletions docbook/reference/en/en-US/modules/service-accounts.xml
Expand Up @@ -15,8 +15,10 @@

<para>
The REST URL to invoke on is <literal>/{keycloak-root}/realms/{realm-name}/protocol/openid-connect/token</literal>.
Invoking on this URL is a POST request and requires you to post the clientId and clientSecret of the client in <literal>Authorization: Basic</literal> header.
Later we want to add more mechanisms for authenticating clients. You also need to use parameter <literal>grant_type=client_credentials</literal> as per OAuth2 specification.
Invoking on this URL is a POST request and requires you to post the client credentials. By default, client credentials are
represented by clientId and clientSecret of the client in <literal>Authorization: Basic</literal> header, but you can also
authenticate client with signed JWT assertion or any other custom mechanism for client authentication. See
<link linkend="client_authentication">Client Authentication</link> section for more details. You also need to use parameter <literal>grant_type=client_credentials</literal> as per OAuth2 specification.
</para>
<para>
For example the POST invocation to retrieve service account can look like this:
Expand Down
Expand Up @@ -106,7 +106,7 @@ private void serviceAccountLogin(HttpServletRequest req) {
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));

// Add client credentials according to the method configured in keycloak.json file
// Add client credentials according to the method configured in keycloak-client-secret.json or keycloak-client-signed-jwt.json file
Map<String, String> reqHeaders = new HashMap<>();
Map<String, String> reqParams = new HashMap<>();
ClientCredentialsProviderUtils.setClientCredentials(deployment, reqHeaders, reqParams);
Expand Down
Expand Up @@ -666,6 +666,21 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ClientSignedJWTCtrl'
})
.when('/realms/:realm/clients/:client/credentials/:provider', {
templateUrl : resourceUrl + '/partials/client-credentials-generic.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
client : function(ClientLoader) {
return ClientLoader();
},
clientConfigProperties: function(PerClientAuthenticationConfigDescriptionLoader) {
return PerClientAuthenticationConfigDescriptionLoader();
}
},
controller : 'ClientGenericCredentialsCtrl'
})
.when('/realms/:realm/clients/:client/credentials/client-jwt/:keyType/import/:attribute', {
templateUrl : resourceUrl + '/partials/client-credentials-jwt-key-import.html',
resolve : {
Expand Down
Expand Up @@ -93,6 +93,39 @@ module.controller('ClientSignedJWTCtrl', function($scope, $location, realm, clie
};
});

module.controller('ClientGenericCredentialsCtrl', function($scope, $location, realm, client, clientConfigProperties, Client, Notifications) {

console.log('ClientGenericCredentialsCtrl invoked');

$scope.realm = realm;
$scope.client = angular.copy(client);
$scope.clientConfigProperties = clientConfigProperties;
$scope.changed = false;

$scope.$watch('client', function() {
if (!angular.equals($scope.client, client)) {
$scope.changed = true;
}
}, true);

$scope.save = function() {

Client.update({
realm : realm.realm,
client : client.id
}, $scope.client, function() {
$scope.changed = false;
client = $scope.client;
Notifications.success("Client authentication configuration has been saved to the client.");
});
};

$scope.reset = function() {
$scope.client = angular.copy(client);
$scope.changed = false;
};
});

module.controller('ClientIdentityProviderCtrl', function($scope, $location, $route, realm, client, Client, $location, Notifications) {
$scope.realm = realm;
$scope.client = angular.copy(client);
Expand Down
Expand Up @@ -418,6 +418,15 @@ module.factory('AuthenticationConfigDescriptionLoader', function(Loader, Authent
});
});

module.factory('PerClientAuthenticationConfigDescriptionLoader', function(Loader, PerClientAuthenticationConfigDescription, $route, $q) {
return Loader.query(PerClientAuthenticationConfigDescription, function () {
return {
realm: $route.current.params.realm,
provider: $route.current.params.provider
}
});
});

module.factory('ExecutionIdLoader', function($route) {
return function() { return $route.current.params.executionId; };
});
Expand Down
Expand Up @@ -1255,6 +1255,12 @@ module.factory('AuthenticationConfigDescription', function($resource) {
provider: '@provider'
});
});
module.factory('PerClientAuthenticationConfigDescription', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/authentication/per-client-config-description/:provider', {
realm : '@realm',
provider: '@provider'
});
});

module.factory('AuthenticationConfig', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/authentication/config/:config', {
Expand Down
@@ -0,0 +1,24 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">

<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/clients">Clients</a></li>
<li>{{client.clientId}}</li>
</ol>

<kc-tabs-client></kc-tabs-client>

<form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageClients">
<fieldset>
<kc-provider-config realm="realm" config="client.attributes" properties="clientConfigProperties"></kc-provider-config>
</fieldset>

<div class="form-group">
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
<button kc-save data-ng-disabled="!changed">Save</button>
<button kc-reset data-ng-disabled="!changed">Cancel</button>
</div>
</div>
</form>
</div>

<kc-menu></kc-menu>
Expand Up @@ -9,14 +9,14 @@
* (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request
* (See the service-account example from Keycloak demo for more info)
*
* When you implement this SPI on the adapter (application) side, you also need to implement {@link org.keycloak.authentication.ClientAuthenticator} on the server side,
* When you implement this SPI on the adapter (application) side, you also need to implement org.keycloak.authentication.ClientAuthenticator on the server side,
* so your server is able to authenticate client
*
* You must specify a file
* META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider in the WAR that this class is contained in (or in the JAR that is attached to the WEB-INF/lib or as jboss module
* if you want to share the implementation among more WARs). This file must have the fully qualified class name of all your ClientAuthenticatorFactory classes
*
* NOTE: The SPI is not finished and method signatures are still subject to change in future versions (for example to support usecase for
* NOTE: The SPI is not finished and method signatures are still subject to change in future versions (for example to support
* authentication with client certificate)
*
* @see ClientIdAndSecretCredentialsProvider
Expand Down

0 comments on commit be83941

Please sign in to comment.