keycloak-aplcache

Changes

pom.xml 13(+12 -1)

services/src/main/java/org/keycloak/services/resources/admin/AdminMessagesLoader.java 107(+0 -107)

Details

diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java
new file mode 100644
index 0000000..ba382f6
--- /dev/null
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java
@@ -0,0 +1,13 @@
+package org.keycloak.client.registration;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+abstract class ClientRepresentationMixIn {
+
+    @JsonIgnore
+    String registrationAccessToken;
+
+}
diff --git a/client-registration/cli/pom.xml b/client-registration/cli/pom.xml
new file mode 100755
index 0000000..13b8a8f
--- /dev/null
+++ b/client-registration/cli/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-client-registration-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-client-registration-cli</artifactId>
+    <name>Keycloak Client Registration CLI</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-client-registration-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.aesh</groupId>
+            <artifactId>aesh</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
new file mode 100644
index 0000000..53c0b88
--- /dev/null
+++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
@@ -0,0 +1,27 @@
+package org.keycloak.client.registration.cli;
+
+import org.jboss.aesh.console.AeshConsole;
+import org.jboss.aesh.console.AeshConsoleBuilder;
+import org.jboss.aesh.console.Prompt;
+import org.jboss.aesh.console.settings.Settings;
+import org.jboss.aesh.console.settings.SettingsBuilder;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientRegistrationCLI {
+
+    public static void main(String[] args) {
+
+        Settings settings = new SettingsBuilder().logging(true).create();
+        AeshConsole aeshConsole = new AeshConsoleBuilder().settings(settings)
+                .prompt(new Prompt("[aesh@rules]$ "))
+//                .command()
+                .create();
+
+        aeshConsole.start();
+    }
+
+
+}
+
diff --git a/client-registration/pom.xml b/client-registration/pom.xml
new file mode 100755
index 0000000..2141228
--- /dev/null
+++ b/client-registration/pom.xml
@@ -0,0 +1,19 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+
+    <name>Keycloak Client Registration Parent</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>keycloak-client-registration-parent</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>api</module>
+        <module>cli</module>
+    </modules>
+</project>
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
index aed99fc..c53eb4b 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
@@ -59,7 +59,7 @@
         <addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
 
         <addColumn tableName="CLIENT">
-            <column name="REGISTRATION_SECRET" type="VARCHAR(255)"/>
+            <column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
         </addColumn>
 
     </changeSet>
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java
new file mode 100644
index 0000000..4c18b3d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java
@@ -0,0 +1,36 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessCreatePresentation {
+
+    private Integer expiration;
+
+    private Integer count;
+
+    public ClientInitialAccessCreatePresentation() {
+    }
+
+    public ClientInitialAccessCreatePresentation(Integer expiration, Integer count) {
+        this.expiration = expiration;
+        this.count = count;
+    }
+
+    public Integer getExpiration() {
+        return expiration;
+    }
+
+    public void setExpiration(Integer expiration) {
+        this.expiration = expiration;
+    }
+
+    public Integer getCount() {
+        return count;
+    }
+
+    public void setCount(Integer count) {
+        this.count = count;
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java
new file mode 100644
index 0000000..d8021ad
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java
@@ -0,0 +1,67 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessPresentation {
+
+    private String id;
+
+    private String token;
+
+    private Integer timestamp;
+
+    private Integer expiration;
+
+    private Integer count;
+
+    private Integer remainingCount;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public Integer getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(Integer timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public Integer getExpiration() {
+        return expiration;
+    }
+
+    public void setExpiration(Integer expiration) {
+        this.expiration = expiration;
+    }
+
+    public Integer getCount() {
+        return count;
+    }
+
+    public void setCount(Integer count) {
+        this.count = count;
+    }
+
+    public Integer getRemainingCount() {
+        return remainingCount;
+    }
+
+    public void setRemainingCount(Integer remainingCount) {
+        this.remainingCount = remainingCount;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/util/TokenUtil.java b/core/src/main/java/org/keycloak/util/TokenUtil.java
index 0d103a6..10b6c20 100644
--- a/core/src/main/java/org/keycloak/util/TokenUtil.java
+++ b/core/src/main/java/org/keycloak/util/TokenUtil.java
@@ -19,6 +19,7 @@ public class TokenUtil {
 
     public static final String TOKEN_TYPE_OFFLINE = "Offline";
 
+
     public static boolean isOfflineTokenRequested(String scopeParam) {
         if (scopeParam == null) {
             return false;
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index a9e74f7..09192ae 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -109,6 +109,7 @@ realm-tab-email=Email
 realm-tab-themes=Themes
 realm-tab-cache=Cache
 realm-tab-tokens=Tokens
+realm-tab-client-initial-access=Initial Access Tokens
 realm-tab-security-defenses=Security Defenses
 realm-tab-general=General
 add-realm=Add Realm
@@ -470,3 +471,17 @@ identity-provider-mappers=Identity Provider Mappers
 create-identity-provider-mapper=Create Identity Provider Mapper
 add-identity-provider-mapper=Add Identity Provider Mapper
 client.description.tooltip=Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description}
+
+expires=Expires
+expiration=Expiration
+count=Count
+remainingCount=Remaining count
+created=Created
+back=Back
+initial-access-tokens=Initial Access Tokens
+add-initial-access-tokens=Add Initial Access Token
+initial-access-token=Initial Access Token
+initial-access.copyPaste.tooltip=Copy/paste the initial access token before navigating away from this page as it's not posible to retrieve later
+continue=Continue
+initial-access-token.confirm.title=Copy Initial Access Token
+initial-access-token.confirm.text=Please copy and paste the initial access token before confirming as it can't be retrieved later
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index fe74ff2..9972eb1 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -176,6 +176,27 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmTokenDetailCtrl'
         })
+        .when('/realms/:realm/client-initial-access', {
+            templateUrl : resourceUrl + '/partials/client-initial-access.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                clientInitialAccess : function(ClientInitialAccessLoader) {
+                    return ClientInitialAccessLoader();
+                }
+            },
+            controller : 'ClientInitialAccessCtrl'
+        })
+        .when('/realms/:realm/client-initial-access/create', {
+            templateUrl : resourceUrl + '/partials/client-initial-access-create.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                }
+            },
+            controller : 'ClientInitialAccessCreateCtrl'
+        })
         .when('/realms/:realm/keys-settings', {
             templateUrl : resourceUrl + '/partials/realm-keys.html',
             resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 6cd5bea..a837f4f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -1986,6 +1986,65 @@ module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, flow
 
 });
 
+module.controller('ClientInitialAccessCtrl', function($scope, realm, clientInitialAccess, ClientInitialAccess, Dialog, Notifications, $route) {
+    $scope.realm = realm;
+    $scope.clientInitialAccess = clientInitialAccess;
+
+    $scope.remove = function(id) {
+        Dialog.confirmDelete(id, 'initial access token', function() {
+            ClientInitialAccess.remove({ realm: realm.realm, id: id }, function() {
+                Notifications.success("The initial access token was deleted.");
+                $route.reload();
+            });
+        });
+    }
+});
+
+module.controller('ClientInitialAccessCreateCtrl', function($scope, realm, ClientInitialAccess, TimeUnit, Dialog, $location, $translate) {
+    $scope.expirationUnit = 'Days';
+    $scope.expiration = TimeUnit.toUnit(0, $scope.expirationUnit);
+    $scope.count = 1;
+    $scope.realm = realm;
+
+    $scope.$watch('expirationUnit', function(to, from) {
+        $scope.expiration = TimeUnit.convert($scope.expiration, from, to);
+    });
+
+    $scope.save = function() {
+        var expiration = TimeUnit.toSeconds($scope.expiration, $scope.expirationUnit);
+        ClientInitialAccess.save({
+            realm: realm.realm
+        }, { expiration: expiration, count: $scope.count}, function (data) {
+            console.debug(data);
+            $scope.id = data.id;
+            $scope.token = data.token;
+        });
+    };
+
+    $scope.cancel = function() {
+        $location.url('/realms/' + realm.realm + '/client-initial-access');
+    };
+
+    $scope.done = function() {
+        var btns = {
+            ok: {
+                label: $translate.instant('continue'),
+                cssClass: 'btn btn-primary'
+            },
+            cancel: {
+                label: $translate.instant('cancel'),
+                cssClass: 'btn btn-default'
+            }
+        }
+
+        var title = $translate.instant('initial-access-token.confirm.title');
+        var message = $translate.instant('initial-access-token.confirm.text');
+        Dialog.open(title, message, btns, function() {
+            $location.url('/realms/' + realm.realm + '/client-initial-access');
+        });
+    };
+});
+
 
 
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
index bcd98b1..61b608f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
@@ -475,6 +475,13 @@ module.factory('GroupLoader', function(Loader, Group, $route, $q) {
     });
 });
 
+module.factory('ClientInitialAccessLoader', function(Loader, ClientInitialAccess, $route) {
+    return Loader.query(ClientInitialAccess, function() {
+        return {
+            realm: $route.current.params.realm
+        }
+    });
+});
 
 
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index 34fdc9d..15e77a4 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -102,6 +102,10 @@ module.service('Dialog', function($modal) {
         openDialog(title, message, btns, '/templates/kc-modal-message.html').then(success, cancel);
     }
 
+    dialog.open = function(title, message, btns, success, cancel) {
+        openDialog(title, message, btns, '/templates/kc-modal.html').then(success, cancel);
+    }
+
     return dialog
 });
 
@@ -284,6 +288,13 @@ module.service('ServerInfo', function($resource, $q, $http) {
     }
 });
 
+module.factory('ClientInitialAccess', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/clients-initial-access/:id', {
+        realm : '@realm',
+        id : '@id'
+    });
+});
+
 
 module.factory('ClientProtocolMapper', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/clients/:client/protocol-mappers/models/:id', {
@@ -1548,11 +1559,4 @@ module.factory('UserGroupMapping', function($resource) {
             method : 'PUT'
         }
     });
-});
-
-
-
-
-
-
-
+});
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html
new file mode 100755
index 0000000..7b7c90e
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html
@@ -0,0 +1,52 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <kc-tabs-realm></kc-tabs-realm>
+
+    <table class="table table-striped table-bordered">
+        <thead>
+        <tr>
+            <th class="kc-table-actions" colspan="6">
+                <div class="form-inline">
+                    <div class="form-group">
+                        <div class="input-group">
+                            <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.id" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
+                            <div class="input-group-addon">
+                                <i class="fa fa-search" type="submit"></i>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="pull-right" data-ng-show="access.manageRealm">
+                        <a id="createClient" class="btn btn-default" href="#/realms/{{realm.realm}}/client-initial-access/create">{{:: 'create' | translate}}</a>
+                    </div>
+                </div>
+            </th>
+        </tr>
+        <tr data-ng-hide="clients.length == 0">
+            <th>{{:: 'id' | translate}}</th>
+            <th>{{:: 'created' | translate}}</th>
+            <th>{{:: 'expires' | translate}}</th>
+            <th>{{:: 'count' | translate}}</th>
+            <th>{{:: 'remainingCount' | translate}}</th>
+            <th>{{:: 'actions' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="ia in clientInitialAccess | filter:search | orderBy:'timestamp'">
+            <td>{{ia.id}}</td>
+            <td>{{(ia.timestamp * 1000)|date:'shortDate'}}&nbsp;{{(ia.timestamp * 1000)|date:'mediumTime'}}</td>
+            <td><span data-ng-show="ia.expiration > 0">{{((ia.timestamp + ia.expiration) * 1000)|date:'shortDate'}}&nbsp;{{((ia.timestamp + ia.expiration) * 1000)|date:'mediumTime'}}</span></td>
+            <td>{{ia.count}}</td>
+            <td>{{ia.remainingCount}}</td>
+            <td class="kc-action-cell">
+                <button class="btn btn-default btn-block btn-sm" data-ng-click="remove(ia.id)">{{:: 'delete' | translate}}</button>
+            </td>
+        </tr>
+        <tr data-ng-show="(clients | filter:search).length == 0">
+            <td class="text-muted" colspan="3" data-ng-show="search.clientId">{{:: 'no-results' | translate}}</td>
+            <td class="text-muted" colspan="3" data-ng-hide="search.clientId">{{:: 'no-clients-available' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access-create.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access-create.html
new file mode 100755
index 0000000..ef54939
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access-create.html
@@ -0,0 +1,63 @@
+<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}}/client-initial-access">{{:: 'initial-access-tokens' | translate}}</a></li>
+        <li>{{:: 'add-initial-access-tokens' | translate}}</li>
+    </ol>
+
+    <h1 data-ng-show="create">{{:: 'add-client' | translate}}</h1>
+
+    <form class="form-horizontal" name="createForm" novalidate kc-read-only="!access.manageRealm" data-ng-hide="token">
+
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="expiration">{{:: 'expiration' | translate}}</label>
+
+            <div class="col-md-6 time-selector">
+                <input class="form-control" type="number" required min="0" max="31536000" data-ng-model="expiration" id="expiration"
+                       name="expiration"/>
+                <select class="form-control" name="expirationUnit" data-ng-model="expirationUnit">
+                    <option data-ng-selected="!expirationUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
+                    <option value="Minutes">{{:: 'minutes' | translate}}</option>
+                    <option value="Hours">{{:: 'hours' | translate}}</option>
+                    <option value="Days">{{:: 'days' | translate}}</option>
+                </select>
+            </div>
+            <kc-tooltip>{{:: 'expiration.tooltip' | translate}}</kc-tooltip>
+        </div>
+
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="count">{{:: 'count' | translate}} </label>
+            <div class="col-sm-6">
+                <input class="form-control" type="text" id="count" name="count" required min="1" max="100" data-ng-model="count">
+            </div>
+            <kc-tooltip>{{:: 'count.tooltip' | translate}}</kc-tooltip>
+        </div>
+
+        <div class="form-group">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
+                <button kc-save>{{:: 'save' | translate}}</button>
+                <button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
+            </div>
+        </div>
+    </form>
+
+    <form name="displayForm" data-ng-show="token">
+        <div class="form-group">
+            <label for="initialAccessToken">{{:: 'initial-access-token' | translate}}</label>
+
+            <div>
+                <textarea type="text" id="initialAccessToken" name="initialAccessToken" class="form-control" rows="6" kc-select-action="click">{{token}}</textarea>
+            </div>
+
+            <kc-tooltip>{{:: 'initial-access.copyPaste.tooltip' | translate}}</kc-tooltip>
+        </div>
+
+        <div class="form-group">
+            <div>
+                <button class="btn btn-default" data-ng-click="done()">{{:: 'Back' | translate}}</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html
index 76f01ea..5d14503 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html
@@ -13,6 +13,7 @@
         <li ng-class="{active: path[2] == 'theme-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/theme-settings">{{:: 'realm-tab-themes' | translate}}</a></li>
         <li ng-class="{active: path[2] == 'cache-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/cache-settings">{{:: 'realm-tab-cache' | translate}}</a></li>
         <li ng-class="{active: path[2] == 'token-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/token-settings">{{:: 'realm-tab-tokens' | translate}}</a></li>
+        <li ng-class="{active: path[2] == 'client-initial-access'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/client-initial-access">{{:: 'realm-tab-client-initial-access' | translate}}</a></li>
         <li ng-class="{active: path[2] == 'defense'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/defense/headers">{{:: 'realm-tab-security-defenses' | translate}}</a></li>
     </ul>
 </div>
\ No newline at end of file
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java
new file mode 100644
index 0000000..8875a4c
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java
@@ -0,0 +1,31 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ClientInitialAccessResource {
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    ClientInitialAccessPresentation create(ClientInitialAccessCreatePresentation rep);
+    
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    List<ClientInitialAccessPresentation> list();
+
+    @DELETE
+    @Path("{id}")
+    void delete(final @PathParam("id") String id);
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 87513ec..82b023b 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -45,5 +45,8 @@ public interface RealmResource {
     @Path("client-session-stats")
     @GET
     List<Map<String, String>> getClientSessionStats();
-    
+
+    @Path("clients-initial-access")
+    ClientInitialAccessResource clientInitialAccess();
+
 }
diff --git a/model/api/src/main/java/org/keycloak/models/ClientInitialAccessModel.java b/model/api/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
new file mode 100755
index 0000000..7447319
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
@@ -0,0 +1,22 @@
+package org.keycloak.models;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ClientInitialAccessModel {
+
+    String getId();
+
+    RealmModel getRealm();
+
+    int getTimestamp();
+
+    int getExpiration();
+
+    int getCount();
+
+    int getRemainingCount();
+
+    void decreaseRemainingCount();
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index c421aea..051493e 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -90,8 +90,8 @@ public interface ClientModel extends RoleContainerModel {
     String getSecret();
     public void setSecret(String secret);
 
-    String getRegistrationSecret();
-    void setRegistrationSecret(String registrationSecret);
+    String getRegistrationToken();
+    void setRegistrationToken(String registrationToken);
 
     boolean isFullScopeAllowed();
     void setFullScopeAllowed(boolean value);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index f15614f..d04699f 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
@@ -17,7 +17,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
     private boolean enabled;
     private String clientAuthenticatorType;
     private String secret;
-    private String registrationSecret;
+    private String registrationToken;
     private String protocol;
     private int notBefore;
     private boolean publicClient;
@@ -91,12 +91,12 @@ public class ClientEntity extends AbstractIdentifiableEntity {
         this.secret = secret;
     }
 
-    public String getRegistrationSecret() {
-        return registrationSecret;
+    public String getRegistrationToken() {
+        return registrationToken;
     }
 
-    public void setRegistrationSecret(String registrationSecret) {
-        this.registrationSecret = registrationSecret;
+    public void setRegistrationToken(String registrationToken) {
+        this.registrationToken = registrationToken;
     }
 
     public int getNotBefore() {
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
index 1a59f4f..0c1df9c 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -63,6 +63,11 @@ public interface UserSessionProvider extends Provider {
     UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline);
     ClientSessionModel importClientSession(ClientSessionModel persistentClientSession, boolean offline);
 
+    ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
+    ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
+    void removeClientInitialAccessModel(RealmModel realm, String id);
+    List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
+
     void close();
 
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index c35c58e..02caa74 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -43,8 +43,6 @@ import java.util.UUID;
  */
 public final class KeycloakModelUtils {
 
-    private static final int RANDOM_PASSWORD_BYTES = 32;
-
     private KeycloakModelUtils() {
     }
 
@@ -182,16 +180,6 @@ public final class KeycloakModelUtils {
         return secret;
     }
 
-    public static void generateRegistrationAccessToken(ClientModel client) {
-        client.setRegistrationSecret(generatePassword());
-    }
-
-    public static String generatePassword() {
-        byte[] buf = new byte[RANDOM_PASSWORD_BYTES];
-        new SecureRandom().nextBytes(buf);
-        return Base64Url.encode(buf);
-    }
-
     public static String getDefaultClientAuthenticatorType() {
         return "client-secret";
     }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index a69bb2b..32ac72a 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -412,7 +412,6 @@ public class ModelToRepresentation {
         rep.setNotBefore(clientModel.getNotBefore());
         rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
         rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
-        rep.setRegistrationAccessToken(clientModel.getRegistrationSecret());
 
         Set<String> redirectUris = clientModel.getRedirectUris();
         if (redirectUris != null) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index a46ee57..a31d355 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -737,8 +737,6 @@ public class RepresentationToModel {
             KeycloakModelUtils.generateSecret(client);
         }
 
-        client.setRegistrationSecret(resourceRep.getRegistrationAccessToken());
-
         if (resourceRep.getAttributes() != null) {
             for (Map.Entry<String, String> entry : resourceRep.getAttributes().entrySet()) {
                 client.setAttribute(entry.getKey(), entry.getValue());
@@ -815,7 +813,6 @@ public class RepresentationToModel {
         if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
         if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
         if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType());
-        if (rep.getRegistrationAccessToken() != null) resource.setRegistrationSecret(rep.getRegistrationAccessToken());
         resource.updateClient();
 
         if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol());
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index 7a87373..e03452a 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -120,14 +120,14 @@ public class ClientAdapter implements ClientModel {
         getDelegateForUpdate();
         updated.setSecret(secret);
     }
-    public String getRegistrationSecret() {
-        if (updated != null) return updated.getRegistrationSecret();
-        return cached.getRegistrationSecret();
+    public String getRegistrationToken() {
+        if (updated != null) return updated.getRegistrationToken();
+        return cached.getRegistrationToken();
     }
 
-    public void setRegistrationSecret(String registrationsecret) {
+    public void setRegistrationToken(String registrationToken) {
         getDelegateForUpdate();
-        updated.setRegistrationSecret(registrationsecret);
+        updated.setRegistrationToken(registrationToken);
     }
 
     public boolean isPublicClient() {
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 1c04b9d..7c7b97d 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -31,7 +31,7 @@ public class CachedClient implements Serializable {
     private boolean enabled;
     private String clientAuthenticatorType;
     private String secret;
-    private String registrationSecret;
+    private String registrationToken;
     private String protocol;
     private Map<String, String> attributes = new HashMap<String, String>();
     private boolean publicClient;
@@ -58,7 +58,7 @@ public class CachedClient implements Serializable {
         id = model.getId();
         clientAuthenticatorType = model.getClientAuthenticatorType();
         secret = model.getSecret();
-        registrationSecret = model.getRegistrationSecret();
+        registrationToken = model.getRegistrationToken();
         clientId = model.getClientId();
         name = model.getName();
         description = model.getDescription();
@@ -131,8 +131,8 @@ public class CachedClient implements Serializable {
         return secret;
     }
 
-    public String getRegistrationSecret() {
-        return registrationSecret;
+    public String getRegistrationToken() {
+        return registrationToken;
     }
 
     public boolean isPublicClient() {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 5ea0b11..a7dc0f4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -178,13 +178,13 @@ public class ClientAdapter implements ClientModel {
     }
 
     @Override
-    public String getRegistrationSecret() {
-        return entity.getRegistrationSecret();
+    public String getRegistrationToken() {
+        return entity.getRegistrationToken();
     }
 
     @Override
-    public void setRegistrationSecret(String registrationSecret) {
-        entity.setRegistrationSecret(registrationSecret);
+    public void setRegistrationToken(String registrationToken) {
+        entity.setRegistrationToken(registrationToken);
     }
 
     @Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 881b129..6218e26 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -42,8 +42,8 @@ public class ClientEntity {
     private boolean enabled;
     @Column(name="SECRET")
     private String secret;
-    @Column(name="REGISTRATION_SECRET")
-    private String registrationSecret;
+    @Column(name="REGISTRATION_TOKEN")
+    private String registrationToken;
     @Column(name="CLIENT_AUTHENTICATOR_TYPE")
     private String clientAuthenticatorType;
     @Column(name="NOT_BEFORE")
@@ -203,12 +203,12 @@ public class ClientEntity {
         this.secret = secret;
     }
 
-    public String getRegistrationSecret() {
-        return registrationSecret;
+    public String getRegistrationToken() {
+        return registrationToken;
     }
 
-    public void setRegistrationSecret(String registrationSecret) {
-        this.registrationSecret = registrationSecret;
+    public void setRegistrationToken(String registrationToken) {
+        this.registrationToken = registrationToken;
     }
 
     public int getNotBefore() {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index cbacd09..8eb562f 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -178,13 +178,13 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
     }
 
     @Override
-    public String getRegistrationSecret() {
-        return getMongoEntity().getRegistrationSecret();
+    public String getRegistrationToken() {
+        return getMongoEntity().getRegistrationToken();
     }
 
     @Override
-    public void setRegistrationSecret(String registrationSecretsecret) {
-        getMongoEntity().setRegistrationSecret(registrationSecretsecret);
+    public void setRegistrationToken(String registrationToken) {
+        getMongoEntity().setRegistrationToken(registrationToken);
         updateMongoEntity();
     }
 
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java
new file mode 100644
index 0000000..7d75335
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java
@@ -0,0 +1,69 @@
+package org.keycloak.models.sessions.infinispan;
+
+import org.infinispan.Cache;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessAdapter implements ClientInitialAccessModel {
+
+    private final KeycloakSession session;
+    private final InfinispanUserSessionProvider provider;
+    private final Cache<String, SessionEntity> cache;
+    private final RealmModel realm;
+    private final ClientInitialAccessEntity entity;
+
+    public ClientInitialAccessAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, ClientInitialAccessEntity entity) {
+        this.session = session;
+        this.provider = provider;
+        this.cache = cache;
+        this.realm = realm;
+        this.entity = entity;
+    }
+
+    @Override
+    public String getId() {
+        return entity.getId();
+    }
+
+    @Override
+    public RealmModel getRealm() {
+        return realm;
+    }
+
+    @Override
+    public int getTimestamp() {
+        return entity.getTimestamp();
+    }
+
+    @Override
+    public int getExpiration() {
+        return entity.getExpiration();
+    }
+
+    @Override
+    public int getCount() {
+        return entity.getCount();
+    }
+
+    @Override
+    public int getRemainingCount() {
+        return entity.getRemainingCount();
+    }
+
+    @Override
+    public void decreaseRemainingCount() {
+        entity.setRemainingCount(entity.getRemainingCount() - 1);
+        update();
+    }
+
+    void update() {
+        provider.getTx().replace(cache, entity.getId(), entity);
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java
new file mode 100644
index 0000000..3398eff
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java
@@ -0,0 +1,55 @@
+package org.keycloak.models.sessions.infinispan.compat;
+
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientInitialAccessEntity;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessAdapter implements ClientInitialAccessModel {
+
+    private final RealmModel realm;
+    private final ClientInitialAccessEntity entity;
+
+    public ClientInitialAccessAdapter(RealmModel realm, ClientInitialAccessEntity entity) {
+        this.realm = realm;
+        this.entity = entity;
+    }
+
+    @Override
+    public String getId() {
+        return entity.getId();
+    }
+
+    @Override
+    public RealmModel getRealm() {
+        return realm;
+    }
+
+    @Override
+    public int getTimestamp() {
+        return entity.getTimestamp();
+    }
+
+    @Override
+    public int getExpiration() {
+        return entity.getExpires();
+    }
+
+    @Override
+    public int getCount() {
+        return entity.getCount();
+    }
+
+    @Override
+    public int getRemainingCount() {
+        return entity.getRemainingCount();
+    }
+
+    @Override
+    public void decreaseRemainingCount() {
+        entity.setRemainingCount(entity.getRemainingCount() - 1);
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java
new file mode 100644
index 0000000..ed0aeac
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java
@@ -0,0 +1,68 @@
+package org.keycloak.models.sessions.infinispan.compat.entities;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessEntity {
+
+    private String id;
+
+    private String realmId;
+
+    private int timestamp;
+
+    private int expires;
+
+    private int count;
+
+    private int remainingCount;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getExpires() {
+        return expires;
+    }
+
+    public void setExpiration(int expires) {
+        this.expires = expires;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public void setCount(int count) {
+        this.count = count;
+    }
+
+    public int getRemainingCount() {
+        return remainingCount;
+    }
+
+    public void setRemainingCount(int remainingCount) {
+        this.remainingCount = remainingCount;
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
index f45edf1..db20ef8 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
@@ -1,19 +1,8 @@
 package org.keycloak.models.sessions.infinispan.compat;
 
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.*;
 import org.keycloak.models.session.UserSessionPersisterProvider;
-import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
-import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
-import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
-import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureKey;
+import org.keycloak.models.sessions.infinispan.compat.entities.*;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RealmInfoUtil;
 import org.keycloak.common.util.Time;
@@ -41,11 +30,12 @@ public class MemUserSessionProvider implements UserSessionProvider {
 
     private final ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions;
     private final ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions;
+    private ConcurrentHashMap<String, ClientInitialAccessEntity> clientInitialAccess;
 
     public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap<String, UserSessionEntity> userSessions, ConcurrentHashMap<String, String> userSessionsByBrokerSessionId,
                                   ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId, ConcurrentHashMap<String, ClientSessionEntity> clientSessions,
                                   ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures,
-                                  ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions, ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions) {
+                                  ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions, ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions, ConcurrentHashMap<String, ClientInitialAccessEntity> clientInitialAccess) {
         this.session = session;
         this.userSessions = userSessions;
         this.clientSessions = clientSessions;
@@ -54,6 +44,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
         this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
         this.offlineUserSessions = offlineUserSessions;
         this.offlineClientSessions = offlineClientSessions;
+        this.clientInitialAccess = clientInitialAccess;
     }
 
     @Override
@@ -341,6 +332,15 @@ public class MemUserSessionProvider implements UserSessionProvider {
                 persister.removeClientSession(s.getId(), true);
             }
         }
+
+        // Remove expired initial access
+        Iterator<ClientInitialAccessEntity> iaitr = clientInitialAccess.values().iterator();
+        while (iaitr.hasNext()) {
+            ClientInitialAccessEntity e = iaitr.next();
+            if (e.getRealmId().equals(realm.getId()) && (e.getExpires() < Time.currentTime())) {
+                iaitr.remove();
+            }
+        }
     }
 
     @Override
@@ -575,6 +575,43 @@ public class MemUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
+    public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+        String id = KeycloakModelUtils.generateId();
+
+        ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
+        entity.setId(id);
+        entity.setRealmId(realm.getId());
+        entity.setTimestamp(Time.currentTime());
+        entity.setExpiration(expiration);
+        entity.setCount(count);
+        entity.setRemainingCount(count);
+
+        clientInitialAccess.put(id, entity);
+
+        return new ClientInitialAccessAdapter(realm, entity);
+    }
+
+    @Override
+    public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+        ClientInitialAccessEntity entity = clientInitialAccess.get(id);
+        return entity != null ? new ClientInitialAccessAdapter(realm, entity) : null;
+    }
+
+    @Override
+    public void removeClientInitialAccessModel(RealmModel realm, String id) {
+        clientInitialAccess.remove(id);
+    }
+
+    @Override
+    public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
+        List<ClientInitialAccessModel> models = new LinkedList<>();
+        for (ClientInitialAccessEntity e : clientInitialAccess.values()) {
+            models.add(new ClientInitialAccessAdapter(realm, e));
+        }
+        return models;
+    }
+
+    @Override
     public void close() {
     }
 
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
index 187a33f..451fcb0 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
@@ -1,14 +1,12 @@
 package org.keycloak.models.sessions.infinispan.compat;
 
-import org.keycloak.Config;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.UserSessionProviderFactory;
 import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
 import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
 import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureKey;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientInitialAccessEntity;
 
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -29,9 +27,11 @@ public class MemUserSessionProviderFactory {
     private ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions = new ConcurrentHashMap<String, UserSessionEntity>();
     private ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions = new ConcurrentHashMap<String, ClientSessionEntity>();
 
+    private ConcurrentHashMap<String, ClientInitialAccessEntity> clientInitialAccess = new ConcurrentHashMap<>();
+
     public UserSessionProvider create(KeycloakSession session) {
         return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures,
-                offlineUserSessions, offlineClientSessions);
+                offlineUserSessions, offlineClientSessions, clientInitialAccess);
     }
 
     public void close() {
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java
new file mode 100644
index 0000000..05daf1e
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java
@@ -0,0 +1,48 @@
+package org.keycloak.models.sessions.infinispan.entities;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessEntity extends SessionEntity {
+
+    private int timestamp;
+
+    private int expires;
+
+    private int count;
+
+    private int remainingCount;
+
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getExpiration() {
+        return expires;
+    }
+
+    public void setExpiration(int expires) {
+        this.expires = expires;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public void setCount(int count) {
+        this.count = count;
+    }
+
+    public int getRemainingCount() {
+        return remainingCount;
+    }
+
+    public void setRemainingCount(int remainingCount) {
+        this.remainingCount = remainingCount;
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 9ba1587..c819259 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -3,28 +3,10 @@ package org.keycloak.models.sessions.infinispan;
 import org.infinispan.Cache;
 import org.infinispan.distexec.mapreduce.MapReduceTask;
 import org.jboss.logging.Logger;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakTransaction;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.*;
 import org.keycloak.models.session.UserSessionPersisterProvider;
-import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
-import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
-import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
-import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
-import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
-import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
-import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
-import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.UserLoginFailureMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionNoteMapper;
+import org.keycloak.models.sessions.infinispan.entities.*;
+import org.keycloak.models.sessions.infinispan.mapreduce.*;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RealmInfoUtil;
 import org.keycloak.common.util.Time;
@@ -355,6 +337,15 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
             persister.removeClientSession(clientSessionId, true);
         }
 
+        // Remove expired client initial access
+        map = new MapReduceTask(sessionCache)
+                .mappedWith(ClientInitialAccessMapper.create(realm.getId()).time(Time.currentTime()).remainingCount(0).emitKey())
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (String id : map.keySet()) {
+            tx.remove(sessionCache, id);
+        }
     }
 
     @Override
@@ -538,11 +529,24 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return models;
     }
 
+    List<ClientInitialAccessModel> wrapClientInitialAccess(RealmModel realm, Collection<ClientInitialAccessEntity> entities) {
+        List<ClientInitialAccessModel> models = new LinkedList<>();
+        for (ClientInitialAccessEntity e : entities) {
+            models.add(wrap(realm, e));
+        }
+        return models;
+    }
+
     ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
         Cache<String, SessionEntity> cache = getCache(offline);
         return entity != null ? new ClientSessionAdapter(session, this, cache, realm, entity, offline) : null;
     }
 
+    ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
+        Cache<String, SessionEntity> cache = getCache(false);
+        return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
+    }
+
 
     UsernameLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
         return entity != null ? new UsernameLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
@@ -680,6 +684,50 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return wrap(clientSession.getRealm(), entity, offline);
     }
 
+    @Override
+    public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+        String id = KeycloakModelUtils.generateId();
+
+        ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
+        entity.setId(id);
+        entity.setRealm(realm.getId());
+        entity.setTimestamp(Time.currentTime());
+        entity.setExpiration(expiration);
+        entity.setCount(count);
+        entity.setRemainingCount(count);
+
+        tx.put(sessionCache, id, entity);
+
+        return wrap(realm, entity);
+    }
+
+    @Override
+    public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+        Cache<String, SessionEntity> cache = getCache(false);
+        ClientInitialAccessEntity entity = (ClientInitialAccessEntity) cache.get(id);
+
+        // If created in this transaction
+        if (entity == null) {
+            entity = (ClientInitialAccessEntity) tx.get(cache, id);
+        }
+
+        return wrap(realm, entity);
+    }
+
+    @Override
+    public void removeClientInitialAccessModel(RealmModel realm, String id) {
+        tx.remove(getCache(false), id);
+    }
+
+    @Override
+    public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
+        Map<String, ClientInitialAccessEntity> entities = new MapReduceTask(sessionCache)
+                .mappedWith(ClientInitialAccessMapper.create(realm.getId()))
+                .reducedWith(new FirstResultReducer())
+                .execute();
+        return wrapClientInitialAccess(realm, entities.values());
+    }
+
     class InfinispanKeycloakTransaction implements KeycloakTransaction {
 
         private boolean active;
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java
new file mode 100644
index 0000000..87c9b3c
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java
@@ -0,0 +1,80 @@
+package org.keycloak.models.sessions.infinispan.mapreduce;
+
+import org.infinispan.distexec.mapreduce.Collector;
+import org.infinispan.distexec.mapreduce.Mapper;
+import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
+
+    public ClientInitialAccessMapper(String realm) {
+        this.realm = realm;
+    }
+
+    private enum EmitValue {
+        KEY, ENTITY
+    }
+
+    private String realm;
+
+    private EmitValue emit = EmitValue.ENTITY;
+
+    private Integer time;
+    private Integer remainingCount;
+
+    public static ClientInitialAccessMapper create(String realm) {
+        return new ClientInitialAccessMapper(realm);
+    }
+
+    public ClientInitialAccessMapper emitKey() {
+        emit = EmitValue.KEY;
+        return this;
+    }
+
+    public ClientInitialAccessMapper time(int time) {
+        this.time = time;
+        return this;
+    }
+
+
+    public ClientInitialAccessMapper remainingCount(int remainingCount) {
+        this.remainingCount = remainingCount;
+        return this;
+    }
+
+    @Override
+    public void map(String key, SessionEntity e, Collector collector) {
+        if (!realm.equals(e.getRealm())) {
+            return;
+        }
+
+        if (!(e instanceof ClientInitialAccessEntity)) {
+            return;
+        }
+
+        ClientInitialAccessEntity entity = (ClientInitialAccessEntity) e;
+
+        if (time != null && entity.getExpiration() > 0 && (entity.getTimestamp() + entity.getExpiration()) < time) {
+            return;
+        }
+
+        if (remainingCount != null && entity.getRemainingCount() == remainingCount) {
+            return;
+        }
+
+        switch (emit) {
+            case KEY:
+                collector.emit(key, key);
+                break;
+            case ENTITY:
+                collector.emit(key, entity);
+                break;
+        }
+    }
+
+}

pom.xml 13(+12 -1)

diff --git a/pom.xml b/pom.xml
index d545efd..c137e59 100755
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,7 @@
         <log4j.version>1.2.17</log4j.version>
         <greenmail.version>1.3.1b</greenmail.version>
         <xmlsec.version>1.5.1</xmlsec.version>
+        <aesh.version>0.66</aesh.version>
 
         <enforcer.plugin.version>1.4</enforcer.plugin.version>
         <jboss.as.plugin.version>7.5.Final</jboss.as.plugin.version>
@@ -135,7 +136,7 @@
     <modules>
         <module>common</module>
         <module>core</module>
-        <module>client-api</module>
+        <module>client-registration</module>
         <module>connections</module>
         <module>dependencies</module>
         <module>events</module>
@@ -580,6 +581,11 @@
                 <artifactId>pax-web-runtime</artifactId>
                 <version>${pax.web.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.jboss.aesh</groupId>
+                <artifactId>aesh</artifactId>
+                <version>${aesh.version}</version>
+            </dependency>
 
             <!-- keycloak -->
             <dependency>
@@ -624,6 +630,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-client-registration-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-connections-mongo-update</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
index 298623d..81c2df4 100644
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
@@ -2,26 +2,10 @@ package org.keycloak.protocol.saml.clientregistration;
 
 import org.jboss.logging.Logger;
 import org.keycloak.events.EventBuilder;
-import org.keycloak.events.EventType;
-import org.keycloak.exportimport.ClientDescriptionConverter;
-import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.utils.ModelToRepresentation;
-import org.keycloak.models.utils.RepresentationToModel;
-import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.services.ErrorResponse;
-import org.keycloak.services.clientregistration.ClientRegAuth;
+import org.keycloak.services.clientregistration.ClientRegistrationAuth;
 import org.keycloak.services.clientregistration.ClientRegistrationProvider;
 
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.net.URI;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -31,7 +15,7 @@ public class EntityDescriptorClientRegistrationProvider implements ClientRegistr
 
     private KeycloakSession session;
     private EventBuilder event;
-    private ClientRegAuth auth;
+    private ClientRegistrationAuth auth;
 
     public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
         this.session = session;
@@ -67,7 +51,7 @@ public class EntityDescriptorClientRegistrationProvider implements ClientRegistr
     }
 
     @Override
-    public void setAuth(ClientRegAuth auth) {
+    public void setAuth(ClientRegistrationAuth auth) {
         this.auth = auth;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
index feffc5f..c667b5e 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
@@ -1,6 +1,5 @@
 package org.keycloak.services.clientregistration;
 
-import org.jboss.resteasy.spi.UnauthorizedException;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
@@ -23,7 +22,7 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
 
     private KeycloakSession session;
     private EventBuilder event;
-    private ClientRegAuth auth;
+    private ClientRegistrationAuth auth;
 
     public AdapterInstallationClientRegistrationProvider(KeycloakSession session) {
         this.session = session;
@@ -51,7 +50,7 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
     }
 
     @Override
-    public void setAuth(ClientRegAuth auth) {
+    public void setAuth(ClientRegistrationAuth auth) {
         this.auth = auth;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
index 0c6bc4e..98bd6f9 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
@@ -1,7 +1,6 @@
 package org.keycloak.services.clientregistration;
 
 import org.keycloak.events.EventBuilder;
-import org.keycloak.models.RealmModel;
 import org.keycloak.provider.Provider;
 
 /**
@@ -9,7 +8,7 @@ import org.keycloak.provider.Provider;
  */
 public interface ClientRegistrationProvider extends Provider {
 
-    void setAuth(ClientRegAuth auth);
+    void setAuth(ClientRegistrationAuth auth);
 
     void setEvent(EventBuilder event);
 
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
index 2aed3f1..0581388 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
@@ -35,7 +35,7 @@ public class ClientRegistrationService {
         }
 
         provider.setEvent(event);
-        provider.setAuth(new ClientRegAuth(session, event));
+        provider.setAuth(new ClientRegistrationAuth(session, event));
         return provider;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
new file mode 100644
index 0000000..7cd3342
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
@@ -0,0 +1,99 @@
+package org.keycloak.services.clientregistration;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.JsonWebToken;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.Urls;
+import org.keycloak.util.TokenUtil;
+
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientRegistrationTokenUtils {
+
+    public static final String TYPE_INITIAL_ACCESS_TOKEN = "InitialAccessToken";
+    public static final String TYPE_REGISTRATION_ACCESS_TOKEN = "RegistrationAccessToken";
+
+    public static String updateRegistrationAccessToken(KeycloakSession session, ClientModel client) {
+        return updateRegistrationAccessToken(session.getContext().getRealm(), session.getContext().getUri(), client);
+    }
+
+    public static String updateRegistrationAccessToken(RealmModel realm, UriInfo uri, ClientModel client) {
+        String id = KeycloakModelUtils.generateId();
+        client.setRegistrationToken(id);
+        String token = createToken(realm, uri, id, TYPE_REGISTRATION_ACCESS_TOKEN, 0);
+        return token;
+    }
+
+    public static String createInitialAccessToken(RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
+        return createToken(realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getTimestamp() + model.getExpiration());
+    }
+
+    public static JsonWebToken parseToken(RealmModel realm, UriInfo uri, String token) {
+        JWSInput input;
+        try {
+            input = new JWSInput(token);
+        } catch (Exception e) {
+            throw new ForbiddenException(e);
+        }
+
+        if (!RSAProvider.verify(input, realm.getPublicKey())) {
+            throw new ForbiddenException("Invalid signature");
+        }
+
+        JsonWebToken jwt;
+        try {
+            jwt = input.readJsonContent(JsonWebToken.class);
+        } catch (IOException e) {
+            throw new ForbiddenException(e);
+        }
+
+        if (!getIssuer(realm, uri).equals(jwt.getIssuer())) {
+            throw new ForbiddenException("Issuer doesn't match");
+        }
+
+        if (!jwt.isActive()) {
+            throw new ForbiddenException("Expired token");
+        }
+
+        if (!(TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType()) ||
+                TYPE_INITIAL_ACCESS_TOKEN.equals(jwt.getType()) ||
+                TYPE_REGISTRATION_ACCESS_TOKEN.equals(jwt.getType()))) {
+            throw new ForbiddenException("Invalid token type");
+        }
+
+        return jwt;
+    }
+
+    private static String createToken(RealmModel realm, UriInfo uri, String id, String type, int expiration) {
+        JsonWebToken jwt = new JsonWebToken();
+
+        String issuer = getIssuer(realm, uri);
+
+        jwt.type(type);
+        jwt.id(id);
+        jwt.issuedAt(Time.currentTime());
+        jwt.expiration(expiration);
+        jwt.issuer(issuer);
+        jwt.audience(issuer);
+
+        String token = new JWSBuilder().jsonContent(jwt).rsa256(realm.getPrivateKey());
+        return token;
+    }
+
+    private static String getIssuer(RealmModel realm, UriInfo uri) {
+        return Urls.realmIssuer(uri.getBaseUri(), realm.getName());
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
index 0fad9c2..38d71f2 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
@@ -2,10 +2,10 @@ package org.keycloak.services.clientregistration;
 
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
+import org.keycloak.models.ClientInitialAccessModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.ClientRepresentation;
@@ -23,7 +23,7 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
 
     private KeycloakSession session;
     private EventBuilder event;
-    private ClientRegAuth auth;
+    private ClientRegistrationAuth auth;
 
     public DefaultClientRegistrationProvider(KeycloakSession session) {
         this.session = session;
@@ -39,11 +39,19 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
 
         try {
             ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
-            KeycloakModelUtils.generateRegistrationAccessToken(clientModel);
-
             client = ModelToRepresentation.toRepresentation(clientModel);
+
+            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel);
+
+            client.setRegistrationAccessToken(registrationAccessToken);
+
             URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
 
+            if (auth.isInitialAccessToken()) {
+                ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel();
+                initialAccessModel.decreaseRemainingCount();
+            }
+
             event.client(client.getClientId()).success();
             return Response.created(uri).entity(client).build();
         } catch (ModelDuplicateException e) {
@@ -60,12 +68,15 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
         ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
         auth.requireView(client);
 
+        ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
+
         if (auth.isRegistrationAccessToken()) {
-            KeycloakModelUtils.generateRegistrationAccessToken(client);
+            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
+            rep.setRegistrationAccessToken(registrationAccessToken);
         }
 
         event.client(client.getClientId()).success();
-        return Response.ok(ModelToRepresentation.toRepresentation(client)).build();
+        return Response.ok(rep).build();
     }
 
     @PUT
@@ -78,13 +89,13 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
         auth.requireUpdate(client);
 
         RepresentationToModel.updateClient(rep, client);
+        rep = ModelToRepresentation.toRepresentation(client);
 
         if (auth.isRegistrationAccessToken()) {
-            KeycloakModelUtils.generateRegistrationAccessToken(client);
+            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
+            rep.setRegistrationAccessToken(registrationAccessToken);
         }
 
-        rep = ModelToRepresentation.toRepresentation(client);
-
         event.client(client.getClientId()).success();
         return Response.ok(rep).build();
     }
@@ -106,7 +117,7 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
     }
 
     @Override
-    public void setAuth(ClientRegAuth auth) {
+    public void setAuth(ClientRegistrationAuth auth) {
         this.auth = auth;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
index b27ddb0..961f28d 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
@@ -1,28 +1,11 @@
 package org.keycloak.services.clientregistration.oidc;
 
 import org.jboss.logging.Logger;
-import org.keycloak.common.util.Time;
 import org.keycloak.events.EventBuilder;
-import org.keycloak.events.EventType;
-import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.utils.ModelToRepresentation;
-import org.keycloak.models.utils.RepresentationToModel;
-import org.keycloak.protocol.oidc.OIDCClientDescriptionConverter;
-import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.services.ErrorResponse;
-import org.keycloak.services.clientregistration.ClientRegAuth;
+import org.keycloak.services.clientregistration.ClientRegistrationAuth;
 import org.keycloak.services.clientregistration.ClientRegistrationProvider;
 
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.net.URI;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -32,7 +15,7 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide
 
     private KeycloakSession session;
     private EventBuilder event;
-    private ClientRegAuth auth;
+    private ClientRegistrationAuth auth;
 
     public OIDCClientRegistrationProvider(KeycloakSession session) {
         this.session = session;
@@ -55,7 +38,7 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide
 //
 //            String registrationAccessToken = TokenGenerator.createRegistrationAccessToken();
 //
-//            clientModel.setRegistrationSecret(registrationAccessToken);
+//            clientModel.setRegistrationToken(registrationAccessToken);
 //
 //            URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
 //
@@ -87,7 +70,7 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide
     }
 
     @Override
-    public void setAuth(ClientRegAuth auth) {
+    public void setAuth(ClientRegistrationAuth auth) {
         this.auth = auth;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
index 871bf05..5c22200 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
@@ -40,12 +40,7 @@ import javax.ws.rs.ext.Providers;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
+import java.util.*;
 import javax.ws.rs.QueryParam;
 
 /**
@@ -318,10 +313,10 @@ public class AdminConsole {
         }
 
         try {
-            Properties msgs = AdminMessagesLoader.getMessages(getTheme(), lang);
+            Properties msgs = getTheme().getMessages("admin-messages", Locale.forLanguageTag(lang));
             if (msgs.isEmpty()) {
                 logger.warn("Message bundle not found for language code '" + lang + "'");
-                msgs = AdminMessagesLoader.getMessages(getTheme(), "en"); // fall back to en
+                msgs = getTheme().getMessages("admin-messages", Locale.ENGLISH);
             }
 
             if (msgs.isEmpty()) logger.fatal("Message bundle not found for language code 'en'");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
new file mode 100644
index 0000000..7ac2036
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
@@ -0,0 +1,102 @@
+package org.keycloak.services.resources.admin;
+
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessResource {
+
+    private final RealmAuth auth;
+    private final RealmModel realm;
+    private final AdminEventBuilder adminEvent;
+
+    @Context
+    protected KeycloakSession session;
+
+    @Context
+    protected UriInfo uriInfo;
+
+    public ClientInitialAccessResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+        this.auth = auth;
+        this.realm = realm;
+        this.adminEvent = adminEvent;
+
+        auth.init(RealmAuth.Resource.CLIENT);
+    }
+
+    /**
+     * Create a new initial access token.
+     *
+     * @param config
+     * @return
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public ClientInitialAccessPresentation create(ClientInitialAccessCreatePresentation config, @Context final HttpServletResponse response) {
+        auth.requireManage();
+
+        int expiration = config.getExpiration() != null ? config.getExpiration() : 0;
+        int count = config.getCount() != null ? config.getCount() : 1;
+
+        ClientInitialAccessModel clientInitialAccessModel = session.sessions().createClientInitialAccessModel(realm, expiration, count);
+
+        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientInitialAccessModel.getId()).representation(config).success();
+
+        if (session.getTransaction().isActive()) {
+            session.getTransaction().commit();
+        }
+
+        ClientInitialAccessPresentation rep = wrap(clientInitialAccessModel);
+
+        String token = ClientRegistrationTokenUtils.createInitialAccessToken(realm, uriInfo, clientInitialAccessModel);
+        rep.setToken(token);
+
+        response.setStatus(Response.Status.CREATED.getStatusCode());
+        response.setHeader(HttpHeaders.LOCATION, uriInfo.getAbsolutePathBuilder().path(clientInitialAccessModel.getId()).build().toString());
+
+        return rep;
+    }
+    
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<ClientInitialAccessPresentation> list() {
+        List<ClientInitialAccessModel> models = session.sessions().listClientInitialAccess(realm);
+        List<ClientInitialAccessPresentation> reps = new LinkedList<>();
+        for (ClientInitialAccessModel m : models) {
+            ClientInitialAccessPresentation r = wrap(m);
+            reps.add(r);
+        }
+        return reps;
+    }
+
+    @DELETE
+    @Path("{id}")
+    public void delete(final @PathParam("id") String id) {
+        session.sessions().removeClientInitialAccessModel(realm, id);
+    }
+
+    private ClientInitialAccessPresentation wrap(ClientInitialAccessModel model) {
+        ClientInitialAccessPresentation rep = new ClientInitialAccessPresentation();
+        rep.setId(model.getId());
+        rep.setTimestamp(model.getTimestamp());
+        rep.setExpiration(model.getExpiration());
+        rep.setCount(model.getCount());
+        rep.setRemainingCount(model.getRemainingCount());
+        return rep;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index f729c7f..03b0636 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -22,6 +22,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.ResourceAdminManager;
@@ -228,8 +229,11 @@ public class ClientResource {
     public ClientRepresentation regenerateRegistrationAccessToken() {
         auth.requireManage();
 
-        KeycloakModelUtils.generateRegistrationAccessToken(client);
+        String token = ClientRegistrationTokenUtils.updateRegistrationAccessToken(realm, uriInfo, client);
+
         ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
+        rep.setRegistrationAccessToken(token);
+
         adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(rep).success();
         return rep;
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 6ef9e1e..36af035 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -141,6 +141,18 @@ public class RealmAdminResource {
     }
 
     /**
+     * Base path for managing client initial access tokens
+     *
+     * @return
+     */
+    @Path("clients-initial-access")
+    public ClientInitialAccessResource getClientInitialAccess() {
+        ClientInitialAccessResource resource = new ClientInitialAccessResource(realm, auth, adminEvent);
+        ResteasyProviderFactory.getInstance().injectProperties(resource);
+        return resource;
+    }
+
+    /**
      * base path for managing realm-level roles of this realm
      *
      * @return
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java
new file mode 100644
index 0000000..79ef1ac
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java
@@ -0,0 +1,58 @@
+package org.keycloak.testsuite.admin;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientInitialAccessResource;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessTest extends AbstractClientTest {
+
+    @Test
+    public void create() {
+        ClientInitialAccessResource resource = keycloak.realm(REALM_NAME).clientInitialAccess();
+
+        ClientInitialAccessPresentation access = resource.create(new ClientInitialAccessCreatePresentation(1000, 2));
+        Assert.assertEquals(new Integer(2), access.getCount());
+        Assert.assertEquals(new Integer(2), access.getRemainingCount());
+        Assert.assertEquals(new Integer(1000), access.getExpiration());
+        Assert.assertNotNull(access.getTimestamp());
+        Assert.assertNotNull(access.getToken());
+
+        ClientInitialAccessPresentation access2 = resource.create(new ClientInitialAccessCreatePresentation());
+
+        List<ClientInitialAccessPresentation> list = resource.list();
+        Assert.assertEquals(2, list.size());
+
+        for (ClientInitialAccessPresentation r : list) {
+            if (r.getId().equals(access.getId())) {
+                Assert.assertEquals(new Integer(2), r.getCount());
+                Assert.assertEquals(new Integer(2), r.getRemainingCount());
+                Assert.assertEquals(new Integer(1000), r.getExpiration());
+                Assert.assertNotNull(r.getTimestamp());
+                Assert.assertNull(r.getToken());
+            } else if(r.getId().equals(access2.getId())) {
+                Assert.assertEquals(new Integer(1), r.getCount());
+                Assert.assertEquals(new Integer(1), r.getRemainingCount());
+                Assert.assertEquals(new Integer(0), r.getExpiration());
+                Assert.assertNotNull(r.getTimestamp());
+                Assert.assertNull(r.getToken());
+            } else {
+                Assert.fail("Unexpected id");
+            }
+        }
+
+        resource.delete(access.getId());
+        resource.delete(access2.getId());
+
+        Assert.assertTrue(resource.list().isEmpty());
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
index 8b8dfad..8535d46 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
@@ -2,6 +2,7 @@ package org.keycloak.testsuite.client;
 
 import org.junit.After;
 import org.junit.Before;
+import org.keycloak.client.registration.Auth;
 import org.keycloak.client.registration.ClientRegistration;
 import org.keycloak.client.registration.ClientRegistrationException;
 import org.keycloak.models.AdminRoles;
@@ -13,7 +14,6 @@ import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 
 import javax.ws.rs.NotFoundException;
-import javax.ws.rs.core.Response;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -76,13 +76,11 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
         testRealms.add(rep);
     }
 
-    public ClientRepresentation createClient(ClientRepresentation client) {
-        Response response = adminClient.realm(REALM_NAME).clients().create(client);
-        String id = response.getLocation().toString();
-        id = id.substring(id.lastIndexOf('/') + 1);
-        client.setId(id);
-        response.close();
-        return client;
+    public ClientRepresentation createClient(ClientRepresentation client) throws ClientRegistrationException {
+        authManageClients();
+        ClientRepresentation response = reg.create(client);
+        reg.auth(null);
+        return response;
     }
 
     public ClientRepresentation getClient(String clientId) {
@@ -93,4 +91,20 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
         }
     }
 
+    void authCreateClients() {
+        reg.auth(Auth.token(getToken("create-clients", "password")));
+    }
+
+    void authManageClients() {
+        reg.auth(Auth.token(getToken("manage-clients", "password")));
+    }
+
+    void authNoAccess() {
+        reg.auth(Auth.token(getToken("no-access", "password")));
+    }
+
+    private String getToken(String username, String password) {
+        return oauthClient.getToken(REALM_NAME, "security-admin-console", null, username, password).getToken();
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
index bf98364..4e0712e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
@@ -9,8 +9,6 @@ import org.keycloak.common.enums.SslRequired;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.representations.idm.ClientRepresentation;
 
-import javax.ws.rs.core.Response;
-
 import static org.junit.Assert.*;
 
 /**
@@ -37,6 +35,7 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
         client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
         client.setRootUrl("http://root");
         client = createClient(client);
+        client.setSecret("RegistrationAccessTokenTestClientSecret");
 
         client2 = new ClientRepresentation();
         client2.setEnabled(true);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
index 8b84b10..3390988 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
@@ -196,20 +196,4 @@ public class ClientRegistrationTest extends AbstractClientRegistrationTest {
         }
     }
 
-    private void authCreateClients() {
-        reg.auth(Auth.token(getToken("create-clients", "password")));
-    }
-
-    private void authManageClients() {
-        reg.auth(Auth.token(getToken("manage-clients", "password")));
-    }
-
-    private void authNoAccess() {
-        reg.auth(Auth.token(getToken("no-access", "password")));
-    }
-
-    private String getToken(String username, String password) {
-        return oauthClient.getToken(REALM_NAME, "security-admin-console", null, username, password).getToken();
-    }
-
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java
new file mode 100644
index 0000000..0ef75dd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java
@@ -0,0 +1,101 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientInitialAccessResource;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.HttpErrorException;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
+
+    private ClientInitialAccessResource resource;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        resource = adminClient.realm(REALM_NAME).clientInitialAccess();
+    }
+
+    @Test
+    public void create() throws ClientRegistrationException {
+        ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation());
+
+        reg.auth(Auth.token(response));
+
+        ClientRepresentation rep = new ClientRepresentation();
+
+        ClientRepresentation created = reg.create(rep);
+        Assert.assertNotNull(created);
+
+        try {
+            reg.create(rep);
+        } catch (ClientRegistrationException e) {
+            Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    public void createMultiple() throws ClientRegistrationException {
+        ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation(0, 2));
+
+        reg.auth(Auth.token(response));
+
+        ClientRepresentation rep = new ClientRepresentation();
+
+        ClientRepresentation created = reg.create(rep);
+        Assert.assertNotNull(created);
+
+        created = reg.create(rep);
+        Assert.assertNotNull(created);
+
+        try {
+            reg.create(rep);
+        } catch (ClientRegistrationException e) {
+            Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    public void createExpired() throws ClientRegistrationException, InterruptedException {
+        ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation(1, 1));
+
+        reg.auth(Auth.token(response));
+
+        ClientRepresentation rep = new ClientRepresentation();
+
+        Thread.sleep(2);
+
+        try {
+            reg.create(rep);
+        } catch (ClientRegistrationException e) {
+            Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    public void createDeleted() throws ClientRegistrationException, InterruptedException {
+        ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation());
+
+        reg.auth(Auth.token(response));
+
+        resource.delete(response.getId());
+
+        ClientRepresentation rep = new ClientRepresentation();
+
+        try {
+            reg.create(rep);
+        } catch (ClientRegistrationException e) {
+            Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
index be880bf..cad28ab 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
@@ -7,8 +7,6 @@ import org.keycloak.client.registration.ClientRegistrationException;
 import org.keycloak.client.registration.HttpErrorException;
 import org.keycloak.representations.idm.ClientRepresentation;
 
-import javax.ws.rs.core.Response;
-
 import static org.junit.Assert.*;
 
 /**
@@ -22,13 +20,13 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest 
     public void before() throws Exception {
         super.before();
 
-        client = new ClientRepresentation();
-        client.setEnabled(true);
-        client.setClientId("RegistrationAccessTokenTest");
-        client.setSecret("RegistrationAccessTokenTestClientSecret");
-        client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
-        client.setRootUrl("http://root");
-        client = createClient(client);
+        ClientRepresentation c = new ClientRepresentation();
+        c.setEnabled(true);
+        c.setClientId("RegistrationAccessTokenTest");
+        c.setSecret("RegistrationAccessTokenTestClientSecret");
+        c.setRootUrl("http://root");
+
+        client = createClient(c);
 
         reg.auth(Auth.token(client.getRegistrationAccessToken()));
     }
@@ -36,7 +34,7 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest 
     private ClientRepresentation assertRead(String id, String registrationAccess, boolean expectSuccess) throws ClientRegistrationException {
         if (expectSuccess) {
             reg.auth(Auth.token(registrationAccess));
-            ClientRepresentation rep = reg.get(client.getClientId());
+            ClientRepresentation rep = reg.get(id);
             assertNotNull(rep);
             return rep;
         } else {
@@ -76,6 +74,7 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest 
     @Test
     public void updateClientWithRegistrationToken() throws ClientRegistrationException {
         client.setRootUrl("http://newroot");
+
         ClientRepresentation rep = reg.update(client);
 
         assertEquals("http://newroot", getClient(client.getId()).getRootUrl());
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 074a08a..13a2059 100644
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -230,7 +230,7 @@
                 </dependency>
                 <dependency>
                     <groupId>org.keycloak</groupId>
-                    <artifactId>keycloak-client-api</artifactId>
+                    <artifactId>keycloak-client-registration-api</artifactId>
                 </dependency>
                 <dependency>
                     <groupId>org.keycloak</groupId>