keycloak-uncached

Changes

Details

diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-claims.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-claims.html
index f33e8cd..4223312 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-claims.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-claims.html
@@ -2,13 +2,13 @@
 <div id="content-area" class="col-md-9" role="main">
     <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
+        <li data-ng-show="!application.bearerOnly && !application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/revocation">Revocation</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
+        <li data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
     </ul>
     <div id="content">
         <ol class="breadcrumb" data-ng-hide="create">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html
index b3b9839..6e71c3f 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-credentials.html
@@ -8,7 +8,7 @@
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/revocation">Revocation</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
+        <li data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
     </ul>
     <div id="content">
         <ol class="breadcrumb" data-ng-hide="create">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
index dc834c3..6cde4cd 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
@@ -2,13 +2,13 @@
 <div id="content-area" class="col-md-9" role="main">
     <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
         <li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
+        <li data-ng-show="!application.bearerOnly && !application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/revocation">Revocation</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
+        <li data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
     </ul>
     <div id="content">
         <ol class="breadcrumb" data-ng-show="create">
@@ -39,6 +39,44 @@
                         <input ng-model="application.enabled" name="enabled" id="enabled" onoffswitch />
                     </div>
                 </div>
+                <div class="form-group clearfix block" data-ng-show="!application.publicClient">
+                    <label class="col-sm-2 control-label" for="bearerOnly">Bearer Only</label>
+                    <div class="col-sm-4">
+                        <input ng-model="application.bearerOnly" name="bearerOnly" id="bearerOnly" onoffswitch />
+                    </div>
+                </div>
+                <div class="form-group clearfix block">
+                    <label class="col-sm-2 control-label" for="publicClient">Public Client</label>
+                    <div class="col-sm-4">
+                        <input ng-model="application.publicClient" name="publicClient" id="publicClient" onoffswitch />
+                    </div>
+                </div>
+                <div class="form-group" data-ng-show="!application.bearerOnly">
+                    <label class="col-sm-2 control-label" for="newRedirectUri">Redirect URI <span class="required" data-ng-show="create">*</span></label>
+                    <div class="col-sm-6">
+                        <div ng-repeat="redirectUri in application.redirectUris" class="row kc-item-deletable">
+                            <div class="col-sm-8">
+                                <input class="form-control" type="text" data-ng-class="{'input-below':!$first}"
+                                       name="redirectUri" id="redirectUri" data-ng-model="redirectUri" readonly />
+                            </div>
+                            <div class="col-sm-2">
+                                <button class="btn btn-danger" type="button" data-ng-click="deleteRedirectUri($index)">
+                                    Delete</button>
+                            </div>
+                        </div>
+
+                        <div class="row">
+                            <div class="col-sm-8">
+                                <input class="form-control" type="text" name="newRedirectUri" id="newRedirectUri"
+                                       placeholder="New Redirect URI..." data-ng-model="newRedirectUri"
+                                       data-ng-class="{'input-below':application.redirectUris.length}" />
+                            </div>
+                            <div class="col-sm-2">
+                                <button class="btn btn-primary" data-ng-click="addRedirectUri()" ng-show="newRedirectUri.length > 0">Add</button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
                 <div class="form-group">
                     <label class="col-sm-2 control-label" for="baseUrl">Base URL</label>
                     <div class="col-sm-4">
@@ -55,9 +93,9 @@
                 </div>
                 <div class="form-group">
                     <label class="col-sm-2 control-label" for="newWebOrigin">Web Origin</label>
-                    <div class="col-sm-4">
+                    <div class="col-sm-6">
                         <div ng-repeat="webOrigin in application.webOrigins" class="row kc-item-deletable">
-                                <div class="col-sm-9">
+                                <div class="col-sm-8">
                                     <input class="form-control kc-button-control"
                                            type="text" data-ng-class="{'input-below':!$first}"
                                            name="webOrigin" id="webOrigin"
@@ -69,7 +107,7 @@
                                 </div>
                         </div>
                         <div class="row">
-                            <div class="col-sm-9">
+                            <div class="col-sm-8">
                                 <input class="form-control kc-button-control"
                                        type="text" name="newWebOrigin" id="newWebOrigin"
                                        placeholder="New Web Origin..." data-ng-model="newWebOrigin"
@@ -82,32 +120,6 @@
                         </div>
                     </div>
                 </div>
-                <div class="form-group">
-                    <label class="col-sm-2 control-label" for="newRedirectUri">Redirect URI</label>
-                    <div class="col-sm-4">
-                        <div ng-repeat="redirectUri in application.redirectUris" class="row kc-item-deletable">
-                            <div class="col-sm-9">
-                                <input class="form-control" type="text" data-ng-class="{'input-below':!$first}"
-                                       name="redirectUri" id="redirectUri" data-ng-model="redirectUri" readonly />
-                            </div>
-                            <div class="col-sm-2">
-                                <button class="btn btn-danger" type="button" data-ng-click="deleteRedirectUri($index)">
-                                    Delete</button>
-                            </div>
-                        </div>
-
-                        <div class="row">
-                            <div class="col-sm-9">
-                                <input class="form-control" type="text" name="newRedirectUri" id="newRedirectUri"
-                                       placeholder="New Redirect URI..." data-ng-model="newRedirectUri"
-                                       data-ng-class="{'input-below':application.redirectUris.length}" />
-                            </div>
-                            <div class="col-sm-2">
-                                <button class="btn btn-primary" data-ng-click="addRedirectUri()" ng-show="newRedirectUri.length > 0">Add</button>
-                            </div>
-                        </div>
-                    </div>
-                </div>
             </fieldset>
 
             <div class="pull-right form-actions" data-ng-show="create && access.manageApplications">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-installation.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-installation.html
index 9c527d7..59a6c2e 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-installation.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-installation.html
@@ -3,13 +3,13 @@
 
     <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
+        <li data-ng-show="!application.bearerOnly && !application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/revocation">Revocation</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
+        <li data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
     </ul>
 
     <div class="top-nav" data-ng-show="create">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-revocation.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-revocation.html
index 6b7e951..c9eb079 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-revocation.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-revocation.html
@@ -2,13 +2,13 @@
 <div id="content-area" class="col-md-9" role="main">
     <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
+        <li data-ng-show="!application.bearerOnly && !application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/revocation">Revocation</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
+        <li data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
     </ul>
     <div id="content">
         <ol class="breadcrumb">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
index 8e3d603..e8c2a12 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
@@ -2,13 +2,13 @@
 <div id="content-area" class="col-md-9" role="main">
     <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
+        <li data-ng-show="!application.bearerOnly && !application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/revocation">Revocation</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
+        <li data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
     </ul>
 
     <div id="content">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-list.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-list.html
index 77b4065..6ce67e3 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-list.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-list.html
@@ -3,13 +3,13 @@
 
     <ul class="nav nav-tabs nav-tabs-pf">
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
+        <li data-ng-show="!application.bearerOnly && !application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/revocation">Revocation</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
+        <li data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
     </ul>
 
     <div id="content">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-scope-mappings.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-scope-mappings.html
index ac6dd6f..d8f3f5f 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-scope-mappings.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-scope-mappings.html
@@ -3,13 +3,13 @@
 
     <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
+        <li data-ng-show="!application.bearerOnly && !application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
         <li><a href="#">Installation</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/claims">Claims</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/revocation">Revocation</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
+        <li data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
     </ul>
 
     <div id="content">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-sessions.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-sessions.html
index 129647c..4a3ecf7 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-sessions.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-sessions.html
@@ -2,7 +2,7 @@
 <div id="content-area" class="col-md-9" role="main">
     <ul class="nav nav-tabs nav-tabs-pf">
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
+        <li data-ng-show="!application.bearerOnly && !application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
         <li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/claims">Claims</a></li>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-claims.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-claims.html
index 57d1d7d..5fa2668 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-claims.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-claims.html
@@ -2,7 +2,7 @@
 <div id="content-area" class="col-md-9" role="main">
     <ul class="nav nav-tabs nav-tabs-pf">
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
+        <li data-ng-show="!oauth.publicClient"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-detail.html
index 5be853e..a279e7c 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-detail.html
@@ -2,7 +2,7 @@
 <div id="content-area" class="col-md-9" role="main">
     <ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
         <li class="active"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
+        <li data-ng-show="!oauth.publicClient"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
@@ -40,52 +40,58 @@
                         <input ng-model="oauth.enabled" name="enabled" id="enabled" onoffswitch />
                     </div>
                 </div>
+                <div class="form-group clearfix block">
+                    <label class="col-sm-2 control-label" for="publicClient">Public Client</label>
+                    <div class="col-sm-4">
+                        <input ng-model="oauth.publicClient" name="publicClient" id="publicClient" onoffswitch />
+                    </div>
+                </div>
                 <div class="form-group">
-                    <label class="col-sm-2 control-label" for="newWebOrigin">Web Origin</label>
+                    <label class="col-sm-2 control-label" for="newRedirectUri">Redirect URI <span class="required" data-ng-show="create">*</span></label>
                     <div class="col-sm-4">
-                        <div ng-repeat="webOrigin in oauth.webOrigins" class="kc-item-deletable row">
+                        <div ng-repeat="redirectUri in oauth.redirectUris" class="kc-item-deletable row">
                             <div class="col-sm-9">
                                 <input  class="form-control" type="text" data-ng-class="{'input-below':!$first}"
-                                        name="webOrigin" id="webOrigin" data-ng-model="webOrigin" readonly />
+                                        name="redirectUri" id="redirectUri" data-ng-model="redirectUri" readonly />
                             </div>
                             <div class="col-sm-2">
-                                <button class="btn btn-danger" type="button" data-ng-click="deleteWebOrigin($index)">
+                                <button class="btn btn-danger" type="button" data-ng-click="deleteRedirectUri($index)">
                                     Delete</button>
                             </div>
                         </div>
                         <div class="row">
                             <div class="col-sm-9">
-                                <input  class="form-control" type="text" name="newWebOrigin" id="newWebOrigin"
-                                        placeholder="New Web Origin..." data-ng-model="newWebOrigin"
-                                        data-ng-class="{'input-below':oauth.webOrigins.length}" />
+                                <input  class="form-control" type="text" name="newRedirectUri" id="newRedirectUri"
+                                        placeholder="New Redirect URI..." data-ng-model="newRedirectUri"
+                                        data-ng-class="{'input-below':oauth.redirectUris.length}" />
                             </div>
                             <div class="col-sm-2">
-                                <button  class="btn btn-primary" data-ng-click="addWebOrigin()" ng-show="newWebOrigin.length > 0">Add</button>
+                                <button class="btn btn-primary" data-ng-click="addRedirectUri()" ng-show="newRedirectUri.length > 0">Add</button>
                             </div>
                         </div>
                     </div>
                 </div>
                 <div class="form-group">
-                    <label class="col-sm-2 control-label" for="newRedirectUri">Redirect URI</label>
+                    <label class="col-sm-2 control-label" for="newWebOrigin">Web Origin</label>
                     <div class="col-sm-4">
-                        <div ng-repeat="redirectUri in oauth.redirectUris" class="kc-item-deletable row">
+                        <div ng-repeat="webOrigin in oauth.webOrigins" class="kc-item-deletable row">
                             <div class="col-sm-9">
                                 <input  class="form-control" type="text" data-ng-class="{'input-below':!$first}"
-                                        name="redirectUri" id="redirectUri" data-ng-model="redirectUri" readonly />
+                                        name="webOrigin" id="webOrigin" data-ng-model="webOrigin" readonly />
                             </div>
                             <div class="col-sm-2">
-                                <button class="btn btn-danger" type="button" data-ng-click="deleteRedirectUri($index)">
+                                <button class="btn btn-danger" type="button" data-ng-click="deleteWebOrigin($index)">
                                     Delete</button>
                             </div>
                         </div>
                         <div class="row">
                             <div class="col-sm-9">
-                                <input  class="form-control" type="text" name="newRedirectUri" id="newRedirectUri"
-                                        placeholder="New Redirect URI..." data-ng-model="newRedirectUri"
-                                        data-ng-class="{'input-below':oauth.redirectUris.length}" />
+                                <input  class="form-control" type="text" name="newWebOrigin" id="newWebOrigin"
+                                        placeholder="New Web Origin..." data-ng-model="newWebOrigin"
+                                        data-ng-class="{'input-below':oauth.webOrigins.length}" />
                             </div>
                             <div class="col-sm-2">
-                                <button class="btn btn-primary" data-ng-click="addRedirectUri()" ng-show="newRedirectUri.length > 0">Add</button>
+                                <button  class="btn btn-primary" data-ng-click="addWebOrigin()" ng-show="newWebOrigin.length > 0">Add</button>
                             </div>
                         </div>
                     </div>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-installation.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-installation.html
index 28a4fa1..795a8a7 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-installation.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-installation.html
@@ -2,7 +2,7 @@
 <div id="content-area" class="col-md-9" role="main">
     <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
+        <li data-ng-show="!oauth.publicClient"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-revocation.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-revocation.html
index a610a30..c505cd0 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-revocation.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-revocation.html
@@ -2,7 +2,7 @@
 <div id="content-area" class="col-md-9" role="main">
     <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
+        <li data-ng-show="!oauth.publicClient"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/claims">Claims</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-scope-mappings.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-scope-mappings.html
index 6c247b2..bd30e24 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-scope-mappings.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/oauth-client-scope-mappings.html
@@ -3,7 +3,7 @@
 
     <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">Settings</a></li>
-        <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
+        <li data-ng-show="!oauth.publicClient"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/claims">Claims</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
         <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
index e3989e1..10b7656 100755
--- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java
+++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
@@ -23,6 +23,7 @@ public class AbstractOAuthClient {
     protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
     protected String stateCookiePath;
     protected boolean isSecure;
+    protected boolean publicClient;
     protected final AtomicLong counter = new AtomicLong();
 
     protected String getStateCode() {
@@ -103,6 +104,14 @@ public class AbstractOAuthClient {
         this.stateCookiePath = stateCookiePath;
     }
 
+    public boolean isPublicClient() {
+        return publicClient;
+    }
+
+    public void setPublicClient(boolean publicClient) {
+        this.publicClient = publicClient;
+    }
+
     protected String stripOauthParametersFromRedirect(String uri) {
         KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(uri)
                 .replaceQueryParam("code", null)
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
index 808e46a..7e01e45 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
@@ -14,7 +14,7 @@ import java.util.Map;
  * @version $Revision: 1 $
  */
 @JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-not-required",
-        "resource", "credentials",
+        "resource", "public-client", "credentials",
         "use-resource-role-mappings",
         "enable-cors", "cors-max-age", "cors-allowed-methods",
         "expose-token", "bearer-only", "scope"})
@@ -35,6 +35,8 @@ public class BaseAdapterConfig extends BaseRealmConfig {
     protected boolean exposeToken;
     @JsonProperty("bearer-only")
     protected boolean bearerOnly;
+    @JsonProperty("public-client")
+    protected boolean publicClient;
     @JsonProperty("credentials")
     protected Map<String, String> credentials = new HashMap<String, String>();
     @JsonProperty("scope")
@@ -120,4 +122,12 @@ public class BaseAdapterConfig extends BaseRealmConfig {
     public void setScope(AccessScope scope) {
         this.scope = scope;
     }
+
+    public boolean isPublicClient() {
+        return publicClient;
+    }
+
+    public void setPublicClient(boolean publicClient) {
+        this.publicClient = publicClient;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index d065aa4..a22f305 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -20,6 +20,8 @@ public class ApplicationRepresentation {
     protected List<String> webOrigins;
     protected ClaimRepresentation claims;
     protected Integer notBefore;
+    protected Boolean bearerOnly;
+    protected Boolean publicClient;
 
 
     public String getId() {
@@ -117,4 +119,20 @@ public class ApplicationRepresentation {
     public void setNotBefore(Integer notBefore) {
         this.notBefore = notBefore;
     }
+
+    public Boolean isBearerOnly() {
+        return bearerOnly;
+    }
+
+    public void setBearerOnly(Boolean bearerOnly) {
+        this.bearerOnly = bearerOnly;
+    }
+
+    public Boolean isPublicClient() {
+        return publicClient;
+    }
+
+    public void setPublicClient(Boolean publicClient) {
+        this.publicClient = publicClient;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
index 5c9f035..516ae8e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
@@ -16,6 +16,7 @@ public class OAuthClientRepresentation {
     protected String secret;
     protected ClaimRepresentation claims;
     protected Integer notBefore;
+    protected Boolean publicClient;
 
 
     public String getId() {
@@ -89,4 +90,12 @@ public class OAuthClientRepresentation {
     public void setNotBefore(Integer notBefore) {
         this.notBefore = notBefore;
     }
+
+    public Boolean isPublicClient() {
+        return publicClient;
+    }
+
+    public void setPublicClient(Boolean publicClient) {
+        this.publicClient = publicClient;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/util/JsonSerialization.java b/core/src/main/java/org/keycloak/util/JsonSerialization.java
index 9e1633b..d0bf6a0 100755
--- a/core/src/main/java/org/keycloak/util/JsonSerialization.java
+++ b/core/src/main/java/org/keycloak/util/JsonSerialization.java
@@ -19,10 +19,8 @@ public class JsonSerialization {
     public static final ObjectMapper prettyMapper = new ObjectMapper();
 
     static {
-        mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
         mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
         prettyMapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
-        prettyMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
         prettyMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
     }
 
diff --git a/examples/demo-template/testrealm.json b/examples/demo-template/testrealm.json
index 05addb5..5de7597 100755
--- a/examples/demo-template/testrealm.json
+++ b/examples/demo-template/testrealm.json
@@ -62,12 +62,18 @@
             "name": "customer-portal",
             "enabled": true,
             "adminUrl": "http://localhost:8080/customer-portal",
+            "redirectUris": [
+                "http://localhost:8080/customer-portal/*"
+            ],
             "secret": "password"
         },
         {
             "name": "product-portal",
             "enabled": true,
             "adminUrl": "http://localhost:8080/product-portal",
+            "redirectUris": [
+                "http://localhost:8080/product-portal/*"
+            ],
             "secret": "password"
         }
     ],
@@ -75,6 +81,10 @@
         {
             "name": "third-party",
             "enabled": true,
+            "redirectUris": [
+                "http://localhost:8080/oauth-client/*",
+                "http://localhost:8080/oauth-client-cdi/*"
+            ],
             "secret": "password"
         }
     ],
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/OAuthClientConfigLoader.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/OAuthClientConfigLoader.java
index 23fb072..10ee4b6 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/OAuthClientConfigLoader.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/OAuthClientConfigLoader.java
@@ -28,6 +28,7 @@ public abstract class OAuthClientConfigLoader extends RealmConfigurationLoader {
 
     public void configureOAuthClient(AbstractOAuthClient oauthClient) {
         oauthClient.setClientId(adapterConfig.getResource());
+        oauthClient.setPublicClient(adapterConfig.isPublicClient());
         oauthClient.setCredentials(adapterConfig.getCredentials());
         if (adapterConfig.getAuthServerUrl() == null) {
             throw new RuntimeException("You must specify auth-url");
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfiguration.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfiguration.java
index 14b551a..23afde1 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfiguration.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfiguration.java
@@ -17,6 +17,7 @@ public class RealmConfiguration {
     protected KeycloakUriBuilder authUrl;
     protected String codeUrl;
     protected String refreshUrl;
+    protected boolean publicClient;
     protected Map<String, String> resourceCredentials = new HashMap<String, String>();
     protected boolean sslRequired = true;
     protected String stateCookieName = "OAuth_Token_Request_State";
@@ -97,4 +98,12 @@ public class RealmConfiguration {
     public void setNotBefore(int notBefore) {
         this.notBefore = notBefore;
     }
+
+    public boolean isPublicClient() {
+        return publicClient;
+    }
+
+    public void setPublicClient(boolean publicClient) {
+        this.publicClient = publicClient;
+    }
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfigurationLoader.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfigurationLoader.java
index dc2325d..abedb2b 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfigurationLoader.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/RealmConfigurationLoader.java
@@ -32,6 +32,7 @@ public class RealmConfigurationLoader extends AdapterConfigLoader {
         realmConfiguration.setMetadata(resourceMetadata);
         realmConfiguration.setSslRequired(!adapterConfig.isSslNotRequired());
         realmConfiguration.setResourceCredentials(adapterConfig.getCredentials());
+        realmConfiguration.setPublicClient(adapterConfig.isPublicClient());
         if (!setupClient || adapterConfig.isBearerOnly()) return;
         initClient();
         if (adapterConfig.getAuthServerUrl() == null) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
index 9dd8ba0..9d10988 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
@@ -9,6 +9,7 @@ import org.apache.http.client.methods.HttpPost;
 import org.apache.http.message.BasicNameValuePair;
 import org.keycloak.adapters.config.RealmConfiguration;
 import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.util.BasicAuthHelper;
 import org.keycloak.util.JsonSerialization;
@@ -49,27 +50,31 @@ public class ServerRequest {
     public static AccessTokenResponse invokeAccessCodeToToken(RealmConfiguration config, String code, String redirectUri) throws HttpFailure, IOException {
         String codeUrl = config.getCodeUrl();
         String client_id = config.getMetadata().getResourceName();
-        Map<String,String> credentials = config.getResourceCredentials();
+        Map<String, String> credentials = config.getResourceCredentials();
         HttpClient client = config.getClient();
 
-        return invokeAccessCodeToToken(client, code, codeUrl, redirectUri, client_id, credentials);
+        return invokeAccessCodeToToken(client, config.isPublicClient(), code, codeUrl, redirectUri, client_id, credentials);
     }
 
-    public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, String code, String codeUrl, String redirectUri, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
+    public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String codeUrl, String redirectUri, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
         List<NameValuePair> formparams = new ArrayList<NameValuePair>();
         redirectUri = stripOauthParametersFromRedirect(redirectUri);
         formparams.add(new BasicNameValuePair("grant_type", "authorization_code"));
         formparams.add(new BasicNameValuePair("code", code));
         formparams.add(new BasicNameValuePair("redirect_uri", redirectUri));
         HttpResponse response = null;
-        UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
         HttpPost post = new HttpPost(codeUrl);
-        String clientSecret = credentials.get(CredentialRepresentation.SECRET);
-        if (clientSecret != null) {
-            String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
-            post.setHeader("Authorization", authorization);
+        if (!publicClient) {
+            String clientSecret = credentials.get(CredentialRepresentation.SECRET);
+            if (clientSecret != null) {
+                String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
+                post.setHeader("Authorization", authorization);
+            }
+        } else {
+            formparams.add(new BasicNameValuePair("client_id", client_id));
         }
 
+        UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
         post.setEntity(form);
         response = client.execute(post);
         int status = response.getStatusLine().getStatusCode();
@@ -106,13 +111,13 @@ public class ServerRequest {
     public static AccessTokenResponse invokeRefresh(RealmConfiguration config, String refreshToken) throws IOException, HttpFailure {
         String refreshUrl = config.getRefreshUrl();
         String client_id = config.getMetadata().getResourceName();
-        Map<String,String> credentials = config.getResourceCredentials();
+        Map<String, String> credentials = config.getResourceCredentials();
         HttpClient client = config.getClient();
-        return invokeRefresh(client, refreshToken, refreshUrl, client_id, credentials);
+        return invokeRefresh(client, config.isPublicClient(), refreshToken, refreshUrl, client_id, credentials);
     }
 
 
-    public static AccessTokenResponse invokeRefresh(HttpClient client, String refreshToken, String refreshUrl, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
+    public static AccessTokenResponse invokeRefresh(HttpClient client, boolean publicClient, String refreshToken, String refreshUrl, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
         List<NameValuePair> formparams = new ArrayList<NameValuePair>();
         for (Map.Entry<String, String> entry : credentials.entrySet()) {
             formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
@@ -120,14 +125,18 @@ public class ServerRequest {
         formparams.add(new BasicNameValuePair("grant_type", "refresh_token"));
         formparams.add(new BasicNameValuePair("refresh_token", refreshToken));
         HttpResponse response = null;
-        UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
         HttpPost post = new HttpPost(refreshUrl);
-        String clientSecret = credentials.get(CredentialRepresentation.SECRET);
-        if (clientSecret != null) {
-            String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
-            post.setHeader("Authorization", authorization);
+        if (!publicClient) {
+            String clientSecret = credentials.get(CredentialRepresentation.SECRET);
+            if (clientSecret != null) {
+                String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
+                post.setHeader("Authorization", authorization);
+            }
+        } else {
+            formparams.add(new BasicNameValuePair("client_id", client_id));
         }
 
+        UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
         post.setEntity(form);
         response = client.execute(post);
         int status = response.getStatusLine().getStatusCode();
@@ -163,7 +172,7 @@ public class ServerRequest {
 
 
     protected static void error(int status, HttpEntity entity) throws HttpFailure, IOException {
-       String body = null;
+        String body = null;
         if (entity != null) {
             InputStream is = entity.getContent();
             try {
@@ -189,5 +198,4 @@ public class ServerRequest {
     }
 
 
-
 }
diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java
index 9bcff38..942ddf3 100755
--- a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java
+++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java
@@ -63,10 +63,16 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
             .build();
     protected static final SimpleAttributeDefinition BEARER_ONLY =
             new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true)
-            .setXmlName("bearer-only")
-            .setAllowExpression(true)
-            .setDefaultValue(new ModelNode(false))
-            .build();
+                    .setXmlName("bearer-only")
+                    .setAllowExpression(true)
+                    .setDefaultValue(new ModelNode(false))
+                    .build();
+    protected static final SimpleAttributeDefinition PUBLIC_CLIENT =
+            new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true)
+                    .setXmlName("bearer-only")
+                    .setAllowExpression(true)
+                    .setDefaultValue(new ModelNode(false))
+                    .build();
 
     protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
     static {
@@ -74,6 +80,7 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
         DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
+        DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
     }
 
     protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
diff --git a/integration/as7-eap-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties b/integration/as7-eap-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties
index 3df3b20..a79c672 100755
--- a/integration/as7-eap-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties
+++ b/integration/as7-eap-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties
@@ -7,51 +7,52 @@ keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
 keycloak.realm=A Keycloak realm.
 keycloak.realm.add=Add a realm definition to the subsystem.
 keycloak.realm.remove=Remove a realm from the subsystem.
-keycloak.realm.realm-public-key=TODO: fill in help text
-keycloak.realm.auth-server-url=TODO: fill in help text
-keycloak.realm.disable-trust-manager=TODO: fill in help text
-keycloak.realm.ssl-not-required=TODO: fill in help text
-keycloak.realm.allow-any-hostname=TODO: fill in help text
-keycloak.realm.truststore=TODO: fill in help text
-keycloak.realm.truststore-password=TODO: fill in help text
-keycloak.realm.connection-pool-size=TODO: fill in help text
-keycloak.realm.enable-cors=TODO: fill in help text
-keycloak.realm.client-keystore=TODO: fill in help text
-keycloak.realm.client-keystore-password=TODO: fill in help text
-keycloak.realm.client-key-password=TODO: fill in help text
-keycloak.realm.cors-max-age=TODO: fill in help text
-keycloak.realm.cors-allowed-headers=TODO: fill in help text
-keycloak.realm.cors-allowed-methods=TODO: fill in help text
-keycloak.realm.expose-token=TODO: fill in help text
+keycloak.realm.realm-public-key=Public key of the realm
+keycloak.realm.auth-server-url=Base URL of the Realm Auth Server
+keycloak.realm.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.realm.ssl-not-required=SSL is not required for secure interactions
+keycloak.realm.allow-any-hostname=SSL Setting
+keycloak.realm.truststore=Truststore used for adapter client HTTPS requests
+keycloak.realm.truststore-password=Password of the Truststore
+keycloak.realm.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.realm.enable-cors=Enable Keycloak CORS support 
+keycloak.realm.client-keystore=n/a
+keycloak.realm.client-keystore-password=n/a
+keycloak.realm.client-key-password=n/a
+keycloak.realm.cors-max-age=CORS max-age header
+keycloak.realm.cors-allowed-headers=CORS allowed headers
+keycloak.realm.cors-allowed-methods=CORS allowed methods
+keycloak.realm.expose-token=Enable secure URL that exposes access token
 
 keycloak.secure-deployment=A deployment secured by Keycloak
 keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
 keycloak.secure-deployment.realm=Keycloak realm
 keycloak.secure-deployment.remove=Remove a deployment to be secured by Keycloak
-keycloak.secure-deployment.realm-public-key=TODO: fill in help text
-keycloak.secure-deployment.auth-server-url=TODO: fill in help text
-keycloak.secure-deployment.disable-trust-manager=TODO: fill in help text
-keycloak.secure-deployment.ssl-not-required=TODO: fill in help text
-keycloak.secure-deployment.allow-any-hostname=TODO: fill in help text
-keycloak.secure-deployment.truststore=TODO: fill in help text
-keycloak.secure-deployment.truststore-password=TODO: fill in help text
-keycloak.secure-deployment.connection-pool-size=TODO: fill in help text
-keycloak.secure-deployment.resource=TODO: fill in help text
-keycloak.secure-deployment.use-resource-role-mappings=TODO: fill in help text
-keycloak.secure-deployment.credentials=TODO: fill in help text
-keycloak.secure-deployment.bearer-only=TODO: fill in help text
-keycloak.secure-deployment.enable-cors=TODO: fill in help text
-keycloak.secure-deployment.client-keystore=TODO: fill in help text
-keycloak.secure-deployment.client-keystore-password=TODO: fill in help text
-keycloak.secure-deployment.client-key-password=TODO: fill in help text
-keycloak.secure-deployment.cors-max-age=TODO: fill in help text
-keycloak.secure-deployment.cors-allowed-headers=TODO: fill in help text
-keycloak.secure-deployment.cors-allowed-methods=TODO: fill in help text
-keycloak.secure-deployment.expose-token=TODO: fill in help text
+keycloak.secure-deployment.realm-public-key=Public key of the realm
+keycloak.secure-deployment.auth-server-url=Base URL of the Realm Auth Server
+keycloak.secure-deployment.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.secure-deployment.ssl-not-required=SSL is not required for secure interactions
+keycloak.secure-deployment.allow-any-hostname=SSL Setting
+keycloak.secure-deployment.truststore=Truststore used for adapter client HTTPS requests
+keycloak.secure-deployment.truststore-password=Password of the Truststore
+keycloak.secure-deployment.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.secure-deployment.resource=Application name
+keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
+keycloak.secure-deployment.credentials=Adapter credentials
+keycloak.secure-deployment.bearer-only=Bearer Token Auth only
+keycloak.secure-deployment.public-client=Public client
+keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
+keycloak.secure-deployment.client-keystore=n/a
+keycloak.secure-deployment.client-keystore-password=n/a
+keycloak.secure-deployment.client-key-password=n/a
+keycloak.secure-deployment.cors-max-age=CORS max-age header
+keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
+keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
+keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
 
-keycloak.secure-deployment.credential=TODO: fill in help text
+keycloak.secure-deployment.credential=Credential value
 
-keycloak.credential=TODO: fill in help text
-keycloak.credential.value=TODO: fill in help text
-keycloak.credential.add=TODO: fill in help text
-keycloak.credential.remove=TODO: fill in help text
\ No newline at end of file
+keycloak.credential=Credential
+keycloak.credential.value=Credential value
+keycloak.credential.add=Credential add
+keycloak.credential.remove=Credential remove
\ No newline at end of file
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 2efdf81..df0d809 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -49,7 +49,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
     }
 
     public AccessTokenResponse resolveBearerToken(String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
-        return ServerRequest.invokeAccessCodeToToken(client, code, codeUrl, redirectUri, clientId, credentials);
+        return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, codeUrl, redirectUri, clientId, credentials);
     }
 
     /**
@@ -155,7 +155,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
     }
 
     public AccessTokenResponse refreshToken(String refreshToken) throws IOException, ServerRequest.HttpFailure {
-        return ServerRequest.invokeRefresh(client, refreshToken, refreshUrl, clientId, credentials);
+        return ServerRequest.invokeRefresh(client, publicClient, refreshToken, refreshUrl, clientId, credentials);
     }
 
     public static IDToken extractIdToken(String idToken) {
diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java
index cae8897..84adf7b 100755
--- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java
+++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java
@@ -60,10 +60,16 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
             .build();
     protected static final SimpleAttributeDefinition BEARER_ONLY =
             new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true)
-            .setXmlName("bearer-only")
-            .setAllowExpression(true)
-            .setDefaultValue(new ModelNode(false))
-            .build();
+                    .setXmlName("bearer-only")
+                    .setAllowExpression(true)
+                    .setDefaultValue(new ModelNode(false))
+                    .build();
+    protected static final SimpleAttributeDefinition PUBLIC_CLIENT =
+            new SimpleAttributeDefinitionBuilder("public-client", ModelType.BOOLEAN, true)
+                    .setXmlName("public-client")
+                    .setAllowExpression(true)
+                    .setDefaultValue(new ModelNode(false))
+                    .build();
 
     protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
     static {
@@ -71,6 +77,7 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
         DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
+        DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
     }
 
     protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
diff --git a/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties b/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties
index 3df3b20..adc1c1c 100755
--- a/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties
+++ b/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties
@@ -7,51 +7,52 @@ keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
 keycloak.realm=A Keycloak realm.
 keycloak.realm.add=Add a realm definition to the subsystem.
 keycloak.realm.remove=Remove a realm from the subsystem.
-keycloak.realm.realm-public-key=TODO: fill in help text
-keycloak.realm.auth-server-url=TODO: fill in help text
-keycloak.realm.disable-trust-manager=TODO: fill in help text
-keycloak.realm.ssl-not-required=TODO: fill in help text
-keycloak.realm.allow-any-hostname=TODO: fill in help text
-keycloak.realm.truststore=TODO: fill in help text
-keycloak.realm.truststore-password=TODO: fill in help text
-keycloak.realm.connection-pool-size=TODO: fill in help text
-keycloak.realm.enable-cors=TODO: fill in help text
-keycloak.realm.client-keystore=TODO: fill in help text
-keycloak.realm.client-keystore-password=TODO: fill in help text
-keycloak.realm.client-key-password=TODO: fill in help text
-keycloak.realm.cors-max-age=TODO: fill in help text
-keycloak.realm.cors-allowed-headers=TODO: fill in help text
-keycloak.realm.cors-allowed-methods=TODO: fill in help text
-keycloak.realm.expose-token=TODO: fill in help text
+keycloak.realm.realm-public-key=Public key of the realm
+keycloak.realm.auth-server-url=Base URL of the Realm Auth Server
+keycloak.realm.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.realm.ssl-not-required=SSL is not required for secure interactions
+keycloak.realm.allow-any-hostname=SSL Setting
+keycloak.realm.truststore=Truststore used for adapter client HTTPS requests
+keycloak.realm.truststore-password=Password of the Truststore
+keycloak.realm.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.realm.enable-cors=Enable Keycloak CORS support
+keycloak.realm.client-keystore=n/a
+keycloak.realm.client-keystore-password=n/a
+keycloak.realm.client-key-password=n/a
+keycloak.realm.cors-max-age=CORS max-age header
+keycloak.realm.cors-allowed-headers=CORS allowed headers
+keycloak.realm.cors-allowed-methods=CORS allowed methods
+keycloak.realm.expose-token=Enable secure URL that exposes access token
 
 keycloak.secure-deployment=A deployment secured by Keycloak
 keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
 keycloak.secure-deployment.realm=Keycloak realm
 keycloak.secure-deployment.remove=Remove a deployment to be secured by Keycloak
-keycloak.secure-deployment.realm-public-key=TODO: fill in help text
-keycloak.secure-deployment.auth-server-url=TODO: fill in help text
-keycloak.secure-deployment.disable-trust-manager=TODO: fill in help text
-keycloak.secure-deployment.ssl-not-required=TODO: fill in help text
-keycloak.secure-deployment.allow-any-hostname=TODO: fill in help text
-keycloak.secure-deployment.truststore=TODO: fill in help text
-keycloak.secure-deployment.truststore-password=TODO: fill in help text
-keycloak.secure-deployment.connection-pool-size=TODO: fill in help text
-keycloak.secure-deployment.resource=TODO: fill in help text
-keycloak.secure-deployment.use-resource-role-mappings=TODO: fill in help text
-keycloak.secure-deployment.credentials=TODO: fill in help text
-keycloak.secure-deployment.bearer-only=TODO: fill in help text
-keycloak.secure-deployment.enable-cors=TODO: fill in help text
-keycloak.secure-deployment.client-keystore=TODO: fill in help text
-keycloak.secure-deployment.client-keystore-password=TODO: fill in help text
-keycloak.secure-deployment.client-key-password=TODO: fill in help text
-keycloak.secure-deployment.cors-max-age=TODO: fill in help text
-keycloak.secure-deployment.cors-allowed-headers=TODO: fill in help text
-keycloak.secure-deployment.cors-allowed-methods=TODO: fill in help text
-keycloak.secure-deployment.expose-token=TODO: fill in help text
+keycloak.secure-deployment.realm-public-key=Public key of the realm
+keycloak.secure-deployment.auth-server-url=Base URL of the Realm Auth Server
+keycloak.secure-deployment.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.secure-deployment.ssl-not-required=SSL is not required for secure interactions
+keycloak.secure-deployment.allow-any-hostname=SSL Setting
+keycloak.secure-deployment.truststore=Truststore used for adapter client HTTPS requests
+keycloak.secure-deployment.truststore-password=Password of the Truststore
+keycloak.secure-deployment.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.secure-deployment.resource=Application name
+keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
+keycloak.secure-deployment.credentials=Adapter credentials
+keycloak.secure-deployment.bearer-only=Bearer Token Auth only
+keycloak.secure-deployment.public-client=Public client
+keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
+keycloak.secure-deployment.client-keystore=n/a
+keycloak.secure-deployment.client-keystore-password=n/a
+keycloak.secure-deployment.client-key-password=n/a
+keycloak.secure-deployment.cors-max-age=CORS max-age header
+keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
+keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
+keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
 
-keycloak.secure-deployment.credential=TODO: fill in help text
+keycloak.secure-deployment.credential=Credential value
 
-keycloak.credential=TODO: fill in help text
-keycloak.credential.value=TODO: fill in help text
-keycloak.credential.add=TODO: fill in help text
-keycloak.credential.remove=TODO: fill in help text
\ No newline at end of file
+keycloak.credential=Credential
+keycloak.credential.value=Credential value
+keycloak.credential.add=Credential add
+keycloak.credential.remove=Credential remove
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
index d8688e4..7088f54 100755
--- a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
@@ -36,6 +36,9 @@ public interface ApplicationModel extends RoleContainerModel, ClientModel {
 
     Set<RoleModel> getApplicationScopeMappings(ClientModel client);
 
+    boolean isBearerOnly();
+    void setBearerOnly(boolean only);
+
     void addScope(RoleModel role);
 
 }
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 d9159d3..b824db6 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -50,6 +50,9 @@ public interface ClientModel {
     String getSecret();
     public void setSecret(String secret);
 
+    boolean isPublicClient();
+    void setPublicClient(boolean flag);
+
     RealmModel getRealm();
 
     /**
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index 371a43a..9881e3c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -78,6 +78,16 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
     }
 
     @Override
+    public boolean isBearerOnly() {
+        return applicationEntity.isBearerOnly();
+    }
+
+    @Override
+    public void setBearerOnly(boolean only) {
+        applicationEntity.setBearerOnly(only);
+    }
+
+    @Override
     public RoleModel getRole(String name) {
         TypedQuery<ApplicationRoleEntity> query = em.createNamedQuery("getAppRoleByName", ApplicationRoleEntity.class);
         query.setParameter("name", name);
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 ed8a6b5..3267344 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
@@ -61,12 +61,24 @@ public class ClientAdapter implements ClientModel {
     }
 
     @Override
+    public boolean isPublicClient() {
+        return entity.isPublicClient();
+    }
+
+    @Override
+    public void setPublicClient(boolean flag) {
+        entity.setPublicClient(flag);
+    }
+
+    @Override
     public Set<String> getWebOrigins() {
         Set<String> result = new HashSet<String>();
         result.addAll(entity.getWebOrigins());
         return result;
     }
 
+
+
     @Override
     public void setWebOrigins(Set<String> webOrigins) {
         entity.setWebOrigins(webOrigins);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java
index 1481857..ffafaa5 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java
@@ -28,6 +28,7 @@ public class ApplicationEntity extends ClientEntity {
     private boolean surrogateAuthRequired;
     private String baseUrl;
     private String managementUrl;
+    private boolean bearerOnly;
 
     @ManyToOne()
     private RealmEntity realm;
@@ -87,6 +88,11 @@ public class ApplicationEntity extends ClientEntity {
         this.realm = realm;
     }
 
+    public boolean isBearerOnly() {
+        return bearerOnly;
+    }
 
-
+    public void setBearerOnly(boolean bearerOnly) {
+        this.bearerOnly = bearerOnly;
+    }
 }
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 c9f27b6..b365ee0 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
@@ -32,6 +32,7 @@ public class ClientEntity {
     private String secret;
     private long allowedClaimsMask;
     private int notBefore;
+    private boolean publicClient;
 
 
     @ElementCollection
@@ -101,4 +102,12 @@ public class ClientEntity {
     public void setNotBefore(int notBefore) {
         this.notBefore = notBefore;
     }
+
+    public boolean isPublicClient() {
+        return publicClient;
+    }
+
+    public void setPublicClient(boolean publicClient) {
+        this.publicClient = publicClient;
+    }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
index d023b2a..1c73a19 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
@@ -124,6 +124,26 @@ public class ApplicationAdapter extends AbstractAdapter implements ApplicationMo
     }
 
     @Override
+    public boolean isBearerOnly() {
+        return application.isBearerOnly();
+    }
+
+    @Override
+    public void setBearerOnly(boolean only) {
+        application.setBearerOnly(only);
+    }
+
+    @Override
+    public boolean isPublicClient() {
+        return application.isPublicClient();
+    }
+
+    @Override
+    public void setPublicClient(boolean flag) {
+        application.setPublicClient(flag);
+    }
+
+    @Override
     public RoleAdapter getRole(String name) {
         DBObject query = new QueryBuilder()
                 .and("name").is(name)
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java
index b1bc63e..d7e1142 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java
@@ -83,6 +83,16 @@ public class OAuthClientAdapter extends AbstractAdapter implements OAuthClientMo
     }
 
     @Override
+    public boolean isPublicClient() {
+        return delegate.isPublicClient();
+    }
+
+    @Override
+    public void setPublicClient(boolean flag) {
+        delegate.setPublicClient(flag);
+    }
+
+    @Override
     public Set<String> getWebOrigins() {
         Set<String> result = new HashSet<String>();
         if (delegate.getWebOrigins() != null) {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java
index 75c921a..ee9f170 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java
@@ -24,6 +24,8 @@ public class ApplicationEntity extends AbstractMongoIdentifiableEntity implement
     private String baseUrl;
     private String secret;
     private int notBefore;
+    private boolean bearerOnly;
+    private boolean publicClient;
 
     private String realmId;
     private long allowedClaimsMask;
@@ -156,6 +158,24 @@ public class ApplicationEntity extends AbstractMongoIdentifiableEntity implement
         this.notBefore = notBefore;
     }
 
+    @MongoField
+    public boolean isBearerOnly() {
+        return bearerOnly;
+    }
+
+    public void setBearerOnly(boolean bearerOnly) {
+        this.bearerOnly = bearerOnly;
+    }
+
+    @MongoField
+    public boolean isPublicClient() {
+        return publicClient;
+    }
+
+    public void setPublicClient(boolean publicClient) {
+        this.publicClient = publicClient;
+    }
+
     @Override
     public void afterRemove(MongoStoreInvocationContext context) {
         // Remove all roles, which belongs to this application
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java
index 41295ef..7188cd2 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java
@@ -20,6 +20,7 @@ public class OAuthClientEntity extends AbstractMongoIdentifiableEntity implement
     private String secret;
     private long allowedClaimsMask;
     private int notBefore;
+    private boolean publicClient;
     private List<String> scopeIds;
     private List<String> webOrigins;
     private List<String> redirectUris;
@@ -106,6 +107,16 @@ public class OAuthClientEntity extends AbstractMongoIdentifiableEntity implement
         this.notBefore = notBefore;
     }
 
+    @MongoField
+    public boolean isPublicClient() {
+        return publicClient;
+    }
+
+    public void setPublicClient(boolean publicClient) {
+        this.publicClient = publicClient;
+    }
+
+
     @Override
     public void afterRemove(MongoStoreInvocationContext context) {
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
index e957492..6b7c901 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
@@ -6,7 +6,6 @@ import org.jboss.resteasy.logging.Logger;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.Constants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
@@ -18,12 +17,12 @@ import org.keycloak.representations.idm.ScopeMappingRepresentation;
 import org.keycloak.representations.idm.UserRoleMappingRepresentation;
 
 import java.net.URI;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 import java.util.Map;
-import java.util.HashMap;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -54,8 +53,11 @@ public class ApplicationManager {
         ApplicationModel applicationModel = realm.addApplication(resourceRep.getName());
         if (resourceRep.isEnabled() != null) applicationModel.setEnabled(resourceRep.isEnabled());
         applicationModel.setManagementUrl(resourceRep.getAdminUrl());
-        if (resourceRep.isSurrogateAuthRequired() != null) applicationModel.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
+        if (resourceRep.isSurrogateAuthRequired() != null)
+            applicationModel.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
         applicationModel.setBaseUrl(resourceRep.getBaseUrl());
+        if (resourceRep.isBearerOnly() != null) applicationModel.setBearerOnly(resourceRep.isBearerOnly());
+        if (resourceRep.isPublicClient() != null) applicationModel.setPublicClient(resourceRep.isPublicClient());
         applicationModel.updateApplication();
 
         if (resourceRep.getNotBefore() != null) {
@@ -138,6 +140,8 @@ public class ApplicationManager {
     public void updateApplication(ApplicationRepresentation rep, ApplicationModel resource) {
         if (rep.getName() != null) resource.setName(rep.getName());
         if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled());
+        if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
+        if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
         if (rep.getAdminUrl() != null) resource.setManagementUrl(rep.getAdminUrl());
         if (rep.getBaseUrl() != null) resource.setBaseUrl(rep.getBaseUrl());
         if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
@@ -171,6 +175,8 @@ public class ApplicationManager {
         rep.setName(applicationModel.getName());
         rep.setEnabled(applicationModel.isEnabled());
         rep.setAdminUrl(applicationModel.getManagementUrl());
+        rep.setPublicClient(applicationModel.isPublicClient());
+        rep.setBearerOnly(applicationModel.isBearerOnly());
         rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired());
         rep.setBaseUrl(applicationModel.getBaseUrl());
         rep.setNotBefore(applicationModel.getNotBefore());
@@ -193,22 +199,26 @@ public class ApplicationManager {
 
     }
 
-    @JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-not-required",
-            "resource", "credentials",
+    @JsonPropertyOrder({"realm", "realm-public-key", "bearer-only", "auth-server-url", "ssl-not-required",
+            "resource", "public-client", "credentials",
             "use-resource-role-mappings"})
     public static class InstallationAdapterConfig extends BaseRealmConfig {
         @JsonProperty("resource")
         protected String resource;
         @JsonProperty("use-resource-role-mappings")
-        protected boolean useResourceRoleMappings;
+        protected Boolean useResourceRoleMappings;
+        @JsonProperty("bearer-only")
+        protected Boolean bearerOnly;
+        @JsonProperty("public-client")
+        protected Boolean publicClient;
         @JsonProperty("credentials")
-        protected Map<String, String> credentials = new HashMap<String, String>();
+        protected Map<String, String> credentials;
 
-        public boolean isUseResourceRoleMappings() {
+        public Boolean isUseResourceRoleMappings() {
             return useResourceRoleMappings;
         }
 
-        public void setUseResourceRoleMappings(boolean useResourceRoleMappings) {
+        public void setUseResourceRoleMappings(Boolean useResourceRoleMappings) {
             this.useResourceRoleMappings = useResourceRoleMappings;
         }
 
@@ -219,6 +229,7 @@ public class ApplicationManager {
         public void setResource(String resource) {
             this.resource = resource;
         }
+
         public Map<String, String> getCredentials() {
             return credentials;
         }
@@ -227,6 +238,21 @@ public class ApplicationManager {
             this.credentials = credentials;
         }
 
+        public Boolean getPublicClient() {
+            return publicClient;
+        }
+
+        public void setPublicClient(Boolean publicClient) {
+            this.publicClient = publicClient;
+        }
+
+        public Boolean getBearerOnly() {
+            return bearerOnly;
+        }
+
+        public void setBearerOnly(Boolean bearerOnly) {
+            this.bearerOnly = bearerOnly;
+        }
     }
 
 
@@ -236,15 +262,19 @@ public class ApplicationManager {
         rep.setRealmKey(realmModel.getPublicKeyPem());
         rep.setSslNotRequired(realmModel.isSslNotRequired());
 
-        rep.setAuthServerUrl(baseUri.toString());
-        rep.setUseResourceRoleMappings(applicationModel.getRoles().size() > 0);
+        if (applicationModel.isPublicClient() && !applicationModel.isBearerOnly()) rep.setPublicClient(true);
+        if (applicationModel.isBearerOnly()) rep.setBearerOnly(true);
+        if (!applicationModel.isBearerOnly()) rep.setAuthServerUrl(baseUri.toString());
+        if (applicationModel.getRoles().size() > 0) rep.setUseResourceRoleMappings(true);
 
         rep.setResource(applicationModel.getName());
 
-        Map<String, String> creds = new HashMap<String, String>();
-        String cred = applicationModel.getSecret();
-        creds.put(CredentialRepresentation.SECRET, cred);
-        rep.setCredentials(creds);
+        if (!applicationModel.isBearerOnly() && !applicationModel.isPublicClient()) {
+            Map<String, String> creds = new HashMap<String, String>();
+            String cred = applicationModel.getSecret();
+            creds.put(CredentialRepresentation.SECRET, cred);
+            rep.setCredentials(creds);
+        }
 
         return rep;
     }
@@ -254,11 +284,21 @@ public class ApplicationManager {
         buffer.append("<secure-deployment name=\"WAR MODULE NAME.war\">\n");
         buffer.append("    <realm>").append(realmModel.getName()).append("</realm>\n");
         buffer.append("    <realm-public-key>").append(realmModel.getPublicKeyPem()).append("</realm-public-key>\n");
-        buffer.append("    <auth-server-url>").append(baseUri.toString()).append("</auth-server-url>\n");
+        if (applicationModel.isBearerOnly()){
+            buffer.append("    <bearer-only>true</bearer-only>\n");
+
+        } else {
+            buffer.append("    <auth-server-url>").append(baseUri.toString()).append("</auth-server-url>\n");
+            if (applicationModel.isPublicClient() && !applicationModel.isBearerOnly()) {
+                buffer.append("    <public-client>true</public-client>\n");
+            }
+        }
         buffer.append("    <ssl-not-required>").append(realmModel.isSslNotRequired()).append("</ssl-not-required>\n");
         buffer.append("    <resource>").append(applicationModel.getName()).append("</resource>\n");
         String cred = applicationModel.getSecret();
-        buffer.append("    <credential name=\"secret\">").append(cred).append("</credential>\n");
+        if (!applicationModel.isBearerOnly() && !applicationModel.isPublicClient()) {
+            buffer.append("    <credential name=\"secret\">").append(cred).append("</credential>\n");
+        }
         buffer.append("</secure-deployment>\n");
         return buffer.toString();
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java b/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java
index 92b03fc..b808089 100755
--- a/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java
@@ -2,13 +2,9 @@ package org.keycloak.services.managers;
 
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.annotate.JsonPropertyOrder;
-import org.keycloak.models.ClaimMask;
-import org.keycloak.models.Constants;
 import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
 import org.keycloak.representations.adapters.config.BaseRealmConfig;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.OAuthClientRepresentation;
@@ -58,10 +54,10 @@ public class OAuthClientManager {
         return model;
     }
 
-    public void update(OAuthClientRepresentation rep, OAuthClientModel model)
-    {
+    public void update(OAuthClientRepresentation rep, OAuthClientModel model) {
         if (rep.getName() != null) model.setClientId(rep.getName());
         if (rep.isEnabled() != null) model.setEnabled(rep.isEnabled());
+        if (rep.isPublicClient() != null) model.setPublicClient(rep.isPublicClient());
         List<String> redirectUris = rep.getRedirectUris();
         if (redirectUris != null) {
             model.setRedirectUris(new HashSet<String>(redirectUris));
@@ -87,6 +83,7 @@ public class OAuthClientManager {
         rep.setId(model.getId());
         rep.setName(model.getClientId());
         rep.setEnabled(model.isEnabled());
+        rep.setPublicClient(model.isPublicClient());
         Set<String> redirectUris = model.getRedirectUris();
         if (redirectUris != null) {
             rep.setRedirectUris(new LinkedList<String>(redirectUris));
@@ -101,12 +98,14 @@ public class OAuthClientManager {
     }
 
     @JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-not-required",
-            "resource", "credentials"})
+            "resource", "public-client", "credentials"})
     public static class InstallationAdapterConfig extends BaseRealmConfig {
+        @JsonProperty("public-client")
+        protected Boolean publicClient;
         @JsonProperty("resource")
         protected String resource;
         @JsonProperty("credentials")
-        protected Map<String, String> credentials = new HashMap<String, String>();
+        protected Map<String, String> credentials;
 
         public String getResource() {
             return resource;
@@ -115,6 +114,7 @@ public class OAuthClientManager {
         public void setResource(String resource) {
             this.resource = resource;
         }
+
         public Map<String, String> getCredentials() {
             return credentials;
         }
@@ -123,6 +123,13 @@ public class OAuthClientManager {
             this.credentials = credentials;
         }
 
+        public Boolean getPublicClient() {
+            return publicClient;
+        }
+
+        public void setPublicClient(Boolean publicClient) {
+            this.publicClient = publicClient;
+        }
     }
 
 
@@ -132,12 +139,15 @@ public class OAuthClientManager {
         rep.setRealmKey(realmModel.getPublicKeyPem());
         rep.setSslNotRequired(realmModel.isSslNotRequired());
         rep.setAuthServerUrl(baseUri.toString());
+        if (model.isPublicClient()) rep.setPublicClient(true);
 
         rep.setResource(model.getClientId());
 
-        Map<String, String> creds = new HashMap<String, String>();
-        creds.put(CredentialRepresentation.SECRET, model.getSecret());
-        rep.setCredentials(creds);
+        if (!model.isPublicClient()) {
+            Map<String, String> creds = new HashMap<String, String>();
+            creds.put(CredentialRepresentation.SECRET, model.getSecret());
+            rep.setCredentials(creds);
+        }
 
         return rep;
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 1cbdf5f..1f1d882 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -31,9 +31,11 @@ import org.keycloak.util.BasicAuthHelper;
 
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.Consumes;
+import javax.ws.rs.ForbiddenException;
 import javax.ws.rs.GET;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.NotAcceptableException;
+import javax.ws.rs.NotAllowedException;
 import javax.ws.rs.NotAuthorizedException;
 import javax.ws.rs.OPTIONS;
 import javax.ws.rs.POST;
@@ -55,6 +57,7 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -136,7 +139,12 @@ public class TokenService {
             throw new NotAcceptableException("HTTPS required");
         }
 
-        ClientModel client = authorizeClient(authorizationHeader);
+        ClientModel client = authorizeClient(authorizationHeader, form);
+
+        if (client.isPublicClient()) {
+            // we don't allow public clients to invoke grants/access to prevent phishing attacks
+            throw new ForbiddenException("Public clients are not allowed to invoke grants/access");
+        }
 
 
         String username = form.getFirst(AuthenticationManager.FORM_USERNAME);
@@ -169,13 +177,13 @@ public class TokenService {
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     @Produces(MediaType.APPLICATION_JSON)
     public Response refreshAccessToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader,
-                                     final MultivaluedMap<String, String> form) {
+                                       final MultivaluedMap<String, String> form) {
         logger.info("--> refreshAccessToken");
         if (!checkSsl()) {
             throw new NotAcceptableException("HTTPS required");
         }
 
-        ClientModel client = authorizeClient(authorizationHeader);
+        ClientModel client = authorizeClient(authorizationHeader, form);
         String refreshToken = form.getFirst("refresh_token");
         AccessToken accessToken = null;
         try {
@@ -188,9 +196,9 @@ public class TokenService {
         }
 
         AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
-                                              .accessToken(accessToken)
-                                              .generateIDToken()
-                                              .generateRefreshToken().build();
+                .accessToken(accessToken)
+                .generateIDToken()
+                .generateRefreshToken().build();
         return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
     }
 
@@ -198,8 +206,8 @@ public class TokenService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processLogin(@QueryParam("client_id") final String clientId, @QueryParam("scope") final String scopeParam,
-            @QueryParam("state") final String state, @QueryParam("redirect_uri") String redirect,
-            final MultivaluedMap<String, String> formData) {
+                                 @QueryParam("state") final String state, @QueryParam("redirect_uri") String redirect,
+                                 final MultivaluedMap<String, String> formData) {
         logger.debug("TokenService.processLogin");
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
@@ -233,7 +241,7 @@ public class TokenService {
             user = realm.getUserByEmail(username);
         }
 
-        if (user == null){
+        if (user == null) {
             return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
         }
 
@@ -273,8 +281,8 @@ public class TokenService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processRegister(@QueryParam("client_id") final String clientId,
-            @QueryParam("scope") final String scopeParam, @QueryParam("state") final String state,
-            @QueryParam("redirect_uri") String redirect, final MultivaluedMap<String, String> formData) {
+                                    @QueryParam("scope") final String scopeParam, @QueryParam("state") final String state,
+                                    @QueryParam("redirect_uri") String redirect, final MultivaluedMap<String, String> formData) {
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
         if (!realm.isEnabled()) {
@@ -361,7 +369,7 @@ public class TokenService {
             throw new NotAuthorizedException("Realm not enabled");
         }
 
-        ClientModel client = authorizeClient(authorizationHeader);
+        ClientModel client = authorizeClient(authorizationHeader, formData);
 
         String code = formData.getFirst("code");
         if (code == null) {
@@ -418,25 +426,36 @@ public class TokenService {
         }
         logger.debug("accessRequest SUCCESS");
         AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
-                                              .accessToken(accessCode.getToken())
-                                              .generateIDToken()
-                                              .generateRefreshToken().build();
+                .accessToken(accessCode.getToken())
+                .generateIDToken()
+                .generateRefreshToken().build();
 
         return Cors.add(request, Response.ok(res)).auth().allowedOrigins(client).allowedMethods("POST").build();
     }
 
-    protected ClientModel authorizeClient(String authorizationHeader) {
-        if (authorizationHeader == null) {
-            throw new NotAuthorizedException("No Authorization header to authenticate client", "Basic realm=\"" + realm.getName() + "\"");
+    protected ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData) {
+        String client_id = null;
+        String clientSecret = null;
+        if (authorizationHeader != null) {
+            String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
+            if (usernameSecret == null) {
+                throw new NotAuthorizedException("Bad Authorization header", "Basic realm=\"" + realm.getName() + "\"");
+            }
+            client_id = usernameSecret[0];
+            clientSecret = usernameSecret[1];
+        } else {
+            logger.info("no authorization header");
+            client_id = formData.getFirst("client_id");
+            clientSecret = formData.getFirst("client_secret");
         }
 
-        String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
-        if (usernameSecret == null) {
-            throw new NotAuthorizedException("No Authorization header to authenticate client", "Basic realm=\"" + realm.getName() + "\"");
+        if (client_id == null) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put("error", "invalid_client");
+            error.put("error_description", "Could not find client");
+            throw new BadRequestException("Could not find client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
         }
 
-        String client_id = usernameSecret[0];
-        String clientSecret = usernameSecret[1];
         ClientModel client = realm.findClient(client_id);
         if (client == null) {
             Map<String, String> error = new HashMap<String, String>();
@@ -452,10 +471,12 @@ public class TokenService {
             throw new BadRequestException("Client is not enabled", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
         }
 
-        if (!client.validateSecret(clientSecret)) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put("error", "unauthorized_client");
-            throw new BadRequestException("Unauthorized Client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+        if (!client.isPublicClient()) {
+            if (!client.validateSecret(clientSecret)) {
+                Map<String, String> error = new HashMap<String, String>();
+                error.put("error", "unauthorized_client");
+                throw new BadRequestException("Unauthorized Client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+            }
         }
         return client;
     }
@@ -463,8 +484,8 @@ public class TokenService {
     @Path("login")
     @GET
     public Response loginPage(final @QueryParam("response_type") String responseType,
-            @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
-            final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state, final @QueryParam("prompt") String prompt) {
+                              @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
+                              final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state, final @QueryParam("prompt") String prompt) {
         logger.info("TokenService.loginPage");
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
@@ -508,8 +529,8 @@ public class TokenService {
     @Path("registrations")
     @GET
     public Response registerPage(final @QueryParam("response_type") String responseType,
-            @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
-            final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state) {
+                                 @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
+                                 final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state) {
         logger.info("**********registerPage()");
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
@@ -613,30 +634,50 @@ public class TokenService {
         return location.build();
     }
 
+    public static boolean matchesRedirects(Set<String> validRedirects, String redirect) {
+        for (String validRedirect : validRedirects) {
+            if (validRedirect.endsWith("*")) {
+                // strip off *
+                int length = validRedirect.length() - 1;
+                validRedirect = validRedirect.substring(0, length);
+                if (redirect.startsWith(validRedirect)) return true;
+                // strip off trailing '/'
+                if (length - 1 > 0 && validRedirect.charAt(length - 1) == '/') length--;
+                validRedirect = validRedirect.substring(0, length);
+                if (validRedirect.equals(redirect)) return true;
+            } else if (validRedirect.equals(redirect)) return true;
+        }
+        return false;
+    }
+
     public static String verifyRedirectUri(String redirectUri, ClientModel client) {
         if (redirectUri == null) {
             return client.getRedirectUris().size() == 1 ? client.getRedirectUris().iterator().next() : null;
         } else if (client.getRedirectUris().isEmpty()) {
+            if (client.isPublicClient()) {
+                logger.error("Client redirect uri must be registered for public client");
+                return null;
+            }
             return redirectUri;
         } else {
             String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
 
-            boolean valid = client.getRedirectUris().contains(r);
+            boolean valid = matchesRedirects(client.getRedirectUris(), r);
 
             if (!valid && r.startsWith(Constants.INSTALLED_APP_URL) && r.indexOf(':', Constants.INSTALLED_APP_URL.length()) >= 0) {
                 int i = r.indexOf(':', Constants.INSTALLED_APP_URL.length());
 
-                    StringBuilder sb = new StringBuilder();
-                    sb.append(r.substring(0, i));
+                StringBuilder sb = new StringBuilder();
+                sb.append(r.substring(0, i));
 
-                    i = r.indexOf('/', i);
-                    if (i >= 0) {
-                        sb.append(r.substring(i));
-                    }
+                i = r.indexOf('/', i);
+                if (i >= 0) {
+                    sb.append(r.substring(i));
+                }
 
-                    r = sb.toString();
+                r = sb.toString();
 
-                valid = client.getRedirectUris().contains(r);
+                valid = matchesRedirects(client.getRedirectUris(), r);
             }
 
             return valid ? redirectUri : null;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
index 0152868..73516b8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
@@ -59,6 +59,9 @@ public class OAuthRedirectUriTest {
             ApplicationModel installedApp2 = appRealm.addApplication("test-installed2");
             installedApp2.setEnabled(true);
             installedApp2.addRedirectUri(Constants.INSTALLED_APP_URL + "/myapp");
+            ApplicationModel installedApp3 = appRealm.addApplication("test-wildcard");
+            installedApp3.setEnabled(true);
+            installedApp3.addRedirectUri("http://example.com/foo/*");
         }
     });
 
@@ -187,6 +190,13 @@ public class OAuthRedirectUriTest {
         Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?key=value&code="));
     }
 
+    @Test
+    public void testWildcard() throws IOException {
+        oauth.clientId("test-wildcard");
+        checkRedirectUri("http://example.com", false);
+        checkRedirectUri("http://example.com/foo", true);
+        checkRedirectUri("http://example.com/foobar", false);
+    }
 
     @Test
     public void testLocalhost() throws IOException {