keycloak-uncached

Details

diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/templates/kc-navigation.html b/admin-ui/src/main/resources/META-INF/resources/admin/templates/kc-navigation.html
index 307aab6..6c53b86 100644
--- a/admin-ui/src/main/resources/META-INF/resources/admin/templates/kc-navigation.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/templates/kc-navigation.html
@@ -1,6 +1,6 @@
 <ul class="nav nav-tabs nav-tabs-pf">
     <li ng-class="{active: !path[2]}"><a href="#/realms/{{realm.realm}}">General</a></li>
-    <li ng-class="{active: path[2] == 'social'}" data-ng-show="kcSocial && access.viewRealm"><a href="#/realms/{{realm.realm}}/social-settings">Social</a></li>
+    <li ng-class="{active: path[2] == 'social'}" data-ng-show="realm.social && access.viewRealm"><a href="#/realms/{{realm.realm}}/social-settings">Social</a></li>
     <li ng-class="{active: path[2] == 'roles'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/roles">Roles</a></li>
     <li ng-class="{active: path[2] == 'default-roles'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/default-roles">Default Roles</a></li>
     <li ng-class="{active: path[2] == 'required-credentials'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/required-credentials">Credentials</a></li>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/code.ftl b/forms/common-themes/src/main/resources/theme/login/base/code.ftl
new file mode 100755
index 0000000..43fdbe5
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/base/code.ftl
@@ -0,0 +1,19 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+    <#if section = "title">
+        <#if code.success>
+            Success code=${code.code}
+        <#else>
+            Error error=${code.error}
+        </#if>
+    <#elseif section = "form">
+        <div id="kc-code">
+            <#if code.success>
+                <p>Please copy this code and paste it into your application:</p>
+                <textarea id="code">${code.code}</textarea>
+            <#else>
+                <p>${code.error}</p>
+            </#if>
+        </div>
+    </#if>
+</@layout.registrationLayout>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login.ftl b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
index b609784..eed0257 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
@@ -36,9 +36,6 @@
                         </div>
                     </#if>
                     <div class="${properties.kcFormOptionsWrapperClass!}">
-                        <#if realm.registrationAllowed>
-                            <span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.register}</a></span>
-                        </#if>
                         <#if realm.resetPasswordAllowed>
                             <span>${rb.loginForgot} <a href="${url.loginPasswordResetUrl}">${rb.password}</a>?</span>
                         </#if>
@@ -54,6 +51,12 @@
             </div>
         </form>
     <#elseif section = "info" >
+        <#if realm.registrationAllowed>
+            <div id="kc-registration">
+                <span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.register}</a></span>
+            </div>
+        </#if>
+
         <div id="kc-social-providers">
             <ul>
                 <#list social.providers as p>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/template.ftl b/forms/common-themes/src/main/resources/theme/login/base/template.ftl
index a53b4d4..49c0246 100644
--- a/forms/common-themes/src/main/resources/theme/login/base/template.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/template.ftl
@@ -4,6 +4,11 @@
 
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <#if properties.meta?has_content>
+        <#list properties.meta?split(' ') as meta>
+            <meta name="${meta?split('==')[0]}" content="${meta?split('==')[1]}"/>
+        </#list>
+    </#if>
     <title><#nested "title"></title>
     <link rel="icon" href="${url.resourcesPath}/img/favicon.ico" />
     <#if properties.styles?has_content>
@@ -19,7 +24,7 @@
 </head>
 
 <body class="${properties.kcBodyClass!}">
-    <dv id="kc-logo"></dv>
+    <div id="kc-logo"><div id="kc-logo-wrapper"></div></div>
 
     <div id="kc-container" class="${properties.kcContainerClass!}">
         <div id="kc-container-wrapper" class="${properties.kcContainerWrapperClass!}">
@@ -31,24 +36,28 @@
             <#if displayMessage && message?has_content>
                 <div id="kc-feedback" class="feedback-${message.type} ${properties.kcFeedBackClass!}">
                     <div id="kc-feedback-wrapper">
-                        <span>${message.summary}</span>
+                        <span class="kc-feedback-text">${message.summary}</span>
                     </div>
                 </div>
             </#if>
 
-            <div id="kc-form" class="${properties.kcFormAreaClass!}">
-                <div id="kc-form-wrapper" class="${properties.kcFormAreaWrapperClass!}">
-                    <#nested "form">
-                </div>
-            </div>
-
-            <#if displayInfo>
-                <div id="kc-info" class="${properties.kcInfoAreaClass!}">
-                    <div id="kc-info-wrapper" class="${properties.kcInfoAreaWrapperClass!}">
-                        <#nested "info">
+            <div id="kc-content" class="${properties.kcContentClass!}">
+                <div id="kc-content-wrapper" class="${properties.kcContentWrapperClass!}">
+                    <div id="kc-form" class="${properties.kcFormAreaClass!}">
+                        <div id="kc-form-wrapper" class="${properties.kcFormAreaWrapperClass!}">
+                            <#nested "form">
+                        </div>
                     </div>
+
+                    <#if displayInfo>
+                        <div id="kc-info" class="${properties.kcInfoAreaClass!}">
+                            <div id="kc-info-wrapper" class="${properties.kcInfoAreaWrapperClass!}">
+                                <#nested "info">
+                            </div>
+                        </div>
+                    </#if>
                 </div>
-            </#if>
+            </div>
         </div>
     </div>
 
diff --git a/forms/common-themes/src/main/resources/theme/login/patternfly/resources/css/login.css b/forms/common-themes/src/main/resources/theme/login/patternfly/resources/css/login.css
index 5de3439..f84516d 100644
--- a/forms/common-themes/src/main/resources/theme/login/patternfly/resources/css/login.css
+++ b/forms/common-themes/src/main/resources/theme/login/patternfly/resources/css/login.css
@@ -1,20 +1,43 @@
+.login-pf .container {
+    padding-top: 40px;
+}
+
 #kc-logo {
+    width: 100%;
+}
+
+#kc-logo-wrapper {
     background-image: url("../img/keycloak-logo.png");
     background-repeat: no-repeat;
-    position: absolute;
-    top: 50px;
-    right: 50px;
+    background-position: top right;
+
     height: 37px;
-    width: 150px;
+
+    margin: 50px;
+}
+
+#kc-header {
+    overflow: visible;
+    padding-left: 80px;
+    white-space: nowrap;
 }
 
 #kc-header-wrapper {
     font-size: 26px;
-    height: 18px;
     text-transform: uppercase;
-    display: block;
+/*    display: block;
+    position: relative;
+    top: -80px;*/
+}
+
+#kc-container-wrapper {
+    bottom: 13%;
+    position: absolute;
+    width: 100%;
+}
+
+#kc-content {
     position: relative;
-    top: -80px;
 }
 
 #kc-form-options span {
@@ -27,18 +50,18 @@
     margin-bottom: 10px;
 }
 
+#kc-feedback-wrapper {
+    display: inline-block;
+    width: auto;
 
-#kc-feedback {
     background-position: left bottom;
     background-repeat: no-repeat;
-    padding-bottom: 21px;
+    padding-bottom: 10px;
 
-    position: absolute;
-    top: -40px;
-    white-space: nowrap;
 }
 
 #kc-feedback span {
+    display: block;
     padding: 0.90909090909091em 3.63636363636364em;
     border-style: solid;
     border-width: 1px 1px 0px 1px;
@@ -52,7 +75,7 @@
     margin-bottom: 0;
 }
 
-.feedback-error {
+.feedback-error #kc-feedback-wrapper {
     background-image: url(../img/feedback-error-arrow-down.png);
 }
 .feedback-error span {
@@ -61,7 +84,7 @@
     background-color: #f8e7e7;
 }
 
-.feedback-success {
+.feedback-success #kc-feedback-wrapper {
     background-image: url(../img/feedback-success-arrow-down.png);
 }
 .feedback-success span {
@@ -70,7 +93,7 @@
     background-color: #e4f1e1;
 }
 
-.feedback-warning {
+.feedback-warning #kc-feedback-wrapper {
     background-image: url(../img/feedback-warning-arrow-down.png);
 }
 .feedback-warning span {
@@ -79,6 +102,10 @@
     background-color: #fef1e9;
 }
 
+#kc-registration {
+    margin-bottom: 15px;
+}
+
 /* TOTP */
 
 ol#kc-totp-settings {
@@ -121,6 +148,12 @@ ol#kc-totp-settings li:first-of-type {
     width: 50%;
 }
 
+/* Code */
+#kc-code textarea {
+    width: 100%;
+    height: 8em;
+}
+
 /* Social */
 
 #kc-social-providers ul {
@@ -130,14 +163,17 @@ ol#kc-totp-settings li:first-of-type {
 
 #kc-social-providers li {
     display: block;
-    margin-top: 1em;
-    width: 130px;
+    margin-top: 5px;
 }
 
 #kc-social-providers li:first-of-type {
     margin-top: 0;
 }
 
+.zocial {
+    width: 125px;
+}
+
 .zocial.facebook,
 .zocial.github,
 .zocial.google,
@@ -167,26 +203,50 @@ ol#kc-totp-settings li:first-of-type {
 }
 
 @media (max-width: 767px) {
-    #kc-logo {
-        position: inherit;
-        display: inline-block;
+    #kc-logo-wrapper {
+        background-image: url("../img/keycloak-logo.png");
+        background-repeat: no-repeat;
+        background-position: top center;
+
+        height: 37px;
+
         margin: 20px;
-        float: right;
+    }
+
+    #kc-header {
+        padding-left: 40px;
+        padding-right: 40px;
+        white-space: normal;
+        float: none;
     }
 
     #kc-feedback {
+        padding-left: 40px;
+        padding-right: 40px;
+        float: none;
+    }
+
+    #kc-container-wrapper {
         position: inherit;
-        display: inline-block;
-        margin-left: 20px;
+        float: none;
     }
 
-    #kc-social-providers {
-        margin-top: 30px;
+    #kc-form {
+        padding-left: 40px;
+        padding-right: 40px;
+        float: none;
+    }
+
+    #kc-info-wrapper {
+        border-top: 1px solid rgba(255, 255, 255, 0.1);
+        margin-top: 20px;
+        padding-top: 20px;
+        padding-left: 20px;
+        padding-right: 40px;
     }
 
     #kc-social-providers li {
-        float: left;
-        margin-right: 10px;
-        margin-top: 0;
+        display: inline-block;
+        margin-right: 5px;
     }
 }
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/patternfly/theme.properties b/forms/common-themes/src/main/resources/theme/login/patternfly/theme.properties
index 4b46ae1..aba1e9b 100644
--- a/forms/common-themes/src/main/resources/theme/login/patternfly/theme.properties
+++ b/forms/common-themes/src/main/resources/theme/login/patternfly/theme.properties
@@ -1,24 +1,25 @@
 parent=base
 styles=lib/patternfly/css/patternfly.css lib/zocial/zocial.css css/login.css
+meta=viewport==width=device-width,initial-scale=1
 
 kcHtmlClass=login-pf
 
-kcContainerClass=container
-kcContainerWrapperClass=row
+kcContentClass=col-sm-12 col-md-12 col-lg-12 container
+kcContentWrapperClass=row
 
-kcHeaderClass=col-sm-12
+kcHeaderClass=col-xs-12 col-sm-7 col-md-6 col-lg-5
 
-kcFeedBackClass=col-sm-offset-2 col-md-offset-4
+kcFeedBackClass=col-xs-12 col-sm-5 col-md-6 col-lg-7
 
-kcFormAreaClass=col-sm-7 col-md-6 col-lg-5 login
+kcFormAreaClass=col-xs-12 col-sm-8 col-md-8 col-lg-6 login
 
 kcFormClass=form-horizontal
 kcFormGroupClass=form-group
 kcLabelClass=control-label
-kcLabelWrapperClass=col-sm-4 col-md-4 col-lg-3
+kcLabelWrapperClass=col-xs-12 col-sm-12 col-md-4 col-lg-3
 kcInputClass=form-control
-kcInputWrapperClass=col-sm-8 col-md-8 col-lg-9
-kcFormOptionsClass=col-sm-offset-4 col-sm-4 col-md-offset-4 col-md-4 col-lg-offset-3 col-lg-5
-kcFormButtonsClass=col-sm-4 col-md-4 col-lg-4 submit
+kcInputWrapperClass=col-xs-12 col-sm-12 col-md-8 col-lg-9
+kcFormOptionsClass=col-xs-5 col-sm-5 col-md-offset-4 col-md-4 col-lg-offset-3 col-lg-5
+kcFormButtonsClass=col-xs-7 col-sm-7 col-md-4 col-lg-4 submit
 
-kcInfoAreaClass=col-sm-5 col-md-6 col-lg-7 details
\ No newline at end of file
+kcInfoAreaClass=col-xs-12 col-sm-4 col-md-4 col-lg-6 details
\ No newline at end of file
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java b/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
index e112755..2eb3704 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
@@ -27,6 +27,8 @@ public interface LoginForms {
 
     public Response createOAuthGrant();
 
+    public Response createCode();
+
     public LoginForms setAccessCode(String accessCodeId, String accessCode);
 
     public LoginForms setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
index f0d1300..2b0cd23 100644
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
@@ -5,6 +5,6 @@ package org.keycloak.login;
  */
 public enum LoginFormsPages {
 
-    LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, LOGIN_USERNAME_REMINDER, REGISTER, ERROR, LOGIN_UPDATE_PROFILE;
+    LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, ERROR, LOGIN_UPDATE_PROFILE, CODE;
 
 }
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java
index b139aa5..69e17fc 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java
@@ -8,6 +8,7 @@ import org.keycloak.freemarker.Theme;
 import org.keycloak.freemarker.ThemeLoader;
 import org.keycloak.login.LoginForms;
 import org.keycloak.login.LoginFormsPages;
+import org.keycloak.login.freemarker.model.CodeBean;
 import org.keycloak.login.freemarker.model.LoginBean;
 import org.keycloak.login.freemarker.model.MessageBean;
 import org.keycloak.login.freemarker.model.OAuthGrantBean;
@@ -178,6 +179,9 @@ public class FreeMarkerLoginForms implements LoginForms {
             case OAUTH_GRANT:
                 attributes.put("oauth", new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested));
                 break;
+            case CODE:
+                attributes.put("code", new CodeBean(accessCode, messageType == MessageType.ERROR ? message : null));
+                break;
         }
 
         try {
@@ -197,10 +201,6 @@ public class FreeMarkerLoginForms implements LoginForms {
         return createResponse(LoginFormsPages.LOGIN_RESET_PASSWORD);
     }
 
-    public Response createUsernameReminder() {
-        return createResponse(LoginFormsPages.LOGIN_USERNAME_REMINDER);
-    }
-
     public Response createLoginTotp() {
         return createResponse(LoginFormsPages.LOGIN_TOTP);
     }
@@ -218,6 +218,11 @@ public class FreeMarkerLoginForms implements LoginForms {
         return createResponse(LoginFormsPages.OAUTH_GRANT);
     }
 
+    @Override
+    public Response createCode() {
+        return createResponse(LoginFormsPages.CODE);
+    }
+
     public FreeMarkerLoginForms setError(String message) {
         this.message = message;
         this.messageType = MessageType.ERROR;
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/CodeBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/CodeBean.java
new file mode 100644
index 0000000..8851f9e
--- /dev/null
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/CodeBean.java
@@ -0,0 +1,27 @@
+package org.keycloak.login.freemarker.model;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class CodeBean {
+
+    private final String code;
+    private final String error;
+
+    public CodeBean(String code, String error) {
+        this.code = code;
+        this.error = error;
+    }
+
+    public boolean isSuccess() {
+        return code != null && error == null;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getError() {
+        return error;
+    }
+}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
index a57e8a3..02e20ec 100644
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
@@ -29,6 +29,8 @@ public class Templates {
                 return "error.ftl";
             case LOGIN_UPDATE_PROFILE:
                 return "login-update-profile.ftl";
+            case CODE:
+                return "code.ftl";
             default:
                 throw new IllegalArgumentException();
         }
diff --git a/integration/js/src/main/resources/META-INF/resources/js/keycloak.js b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js
index 5d4bf8c..dd49966 100755
--- a/integration/js/src/main/resources/META-INF/resources/js/keycloak.js
+++ b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js
@@ -5,7 +5,7 @@ var Keycloak = function (options) {
         return new Keycloak(options);
     }
 
-    var instance = this;
+    var kc = this;
 
     if (!options.url) {
         var scripts = document.getElementsByTagName('script');
@@ -33,7 +33,7 @@ var Keycloak = function (options) {
         throw 'clientSecret missing';
     }
 
-    this.init = function (successCallback, errorCallback) {
+    kc.init = function (successCallback, errorCallback) {
         if (window.oauth.callback) {
             delete sessionStorage.oauthToken;
             processCallback(successCallback, errorCallback);
@@ -44,50 +44,50 @@ var Keycloak = function (options) {
         } else if (options.onload) {
             switch (options.onload) {
                 case 'login-required' :
-                    window.location = createLoginUrl(true);
+                    window.location = kc.createLoginUrl(true);
                     break;
                 case 'check-sso' :
-                    window.location = createLoginUrl(false);
+                    window.location = kc.createLoginUrl(false);
                     break;
             }
         }
     }
 
-    this.login = function () {
-        window.location.href = createLoginUrl(true);
+    kc.login = function () {
+        window.location.href = kc.createLoginUrl(true);
     }
 
-    this.logout = function () {
+    kc.logout = function () {
         setToken(undefined);
-        window.location.href = createLogoutUrl();
+        window.location.href = kc.createLogoutUrl();
     }
 
-    this.hasRealmRole = function (role) {
-        var access = this.realmAccess;
+    kc.hasRealmRole = function (role) {
+        var access = kc.realmAccess;
         return access && access.roles.indexOf(role) >= 0 || false;
     }
 
-    this.hasResourceRole = function (role, resource) {
-        if (!this.resourceAccess) {
+    kc.hasResourceRole = function (role, resource) {
+        if (!kc.resourceAccess) {
             return false;
         }
 
-        var access = this.resourceAccess[resource || options.clientId];
+        var access = kc.resourceAccess[resource || options.clientId];
         return access && access.roles.indexOf(role) >= 0 || false;
     }
 
-    this.loadUserProfile = function (success, error) {
-        var url = getRealmUrl() + '/account';
+    kc.loadUserProfile = function (success, error) {
+        var url = kc.getRealmUrl() + '/account';
         var req = new XMLHttpRequest();
         req.open('GET', url, true);
         req.setRequestHeader('Accept', 'application/json');
-        req.setRequestHeader('Authorization', 'bearer ' + this.token);
+        req.setRequestHeader('Authorization', 'bearer ' + kc.token);
 
         req.onreadystatechange = function () {
             if (req.readyState == 4) {
                 if (req.status == 200) {
-                    instance.profile = JSON.parse(req.responseText);
-                    success && success(instance.profile)
+                    kc.profile = JSON.parse(req.responseText);
+                    success && success(kc.profile)
                 } else {
                     var response = { status: req.status, statusText: req.status };
                     if (req.responseText) {
@@ -108,22 +108,22 @@ var Keycloak = function (options) {
      * @param successCallback
      * @param errorCallback
      */
-    this.onValidAccessToken = function(successCallback, errorCallback) {
-        if (!this.tokenParsed) {
+    kc.onValidAccessToken = function(successCallback, errorCallback) {
+        if (!kc.tokenParsed) {
             console.log('no token');
             errorCallback();
             return;
         }
         var currTime = new Date().getTime() / 1000;
-        if (currTime > this.tokenParsed['exp']) {
-            if (!this.refreshToken) {
+        if (currTime > kc.tokenParsed['exp']) {
+            if (!kc.refreshToken) {
                 console.log('no refresh token');
                 errorCallback();
                 return;
             }
             console.log('calling refresh');
-            var params = 'grant_type=refresh_token&' + 'refresh_token=' + this.refreshToken;
-            var url = getRealmUrl() + '/tokens/refresh';
+            var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
+            var url = kc.getRealmUrl() + '/tokens/refresh';
 
             var req = new XMLHttpRequest();
             req.open('POST', url, true, options.clientId, options.clientSecret);
@@ -134,8 +134,8 @@ var Keycloak = function (options) {
                     if (req.status == 200) {
                         console.log('Refresh Success');
                         var tokenResponse = JSON.parse(req.responseText);
-                        this.refreshToken = tokenResponse['refresh_token'];
-                        setToken(tokenResponse['access_token'], successCallback);
+                        kc.refreshToken = tokenResponse['refresh_token'];
+                        kc.setToken(tokenResponse['access_token'], successCallback);
                     } else {
                         console.log('error on refresh HTTP invoke: ' + req.status);
                         errorCallback && errorCallback({ authenticated: false, status: req.status, statusText: req.statusText });
@@ -150,7 +150,7 @@ var Keycloak = function (options) {
 
     }
 
-    function getRealmUrl() {
+    kc.getRealmUrl = function() {
         return options.url + '/auth/rest/realms/' + encodeURIComponent(options.realm);
     }
 
@@ -161,7 +161,7 @@ var Keycloak = function (options) {
 
         if (code) {
             var params = 'code=' + code;
-            var url = getRealmUrl() + '/tokens/access/codes';
+            var url = kc.getRealmUrl() + '/tokens/access/codes';
 
             var req = new XMLHttpRequest();
             req.open('POST', url, true, options.clientId, options.clientSecret);
@@ -171,8 +171,8 @@ var Keycloak = function (options) {
                 if (req.readyState == 4) {
                     if (req.status == 200) {
                         var tokenResponse = JSON.parse(req.responseText);
-                        instance.refreshToken = tokenResponse['refresh_token'];
-                        setToken(tokenResponse['access_token'], successCallback);
+                        kc.refreshToken = tokenResponse['refresh_token'];
+                        kc.setToken(tokenResponse['access_token'], successCallback);
                     } else {
                         errorCallback && errorCallback({ authenticated: false, status: req.status, statusText: req.statusText });
                     }
@@ -189,33 +189,33 @@ var Keycloak = function (options) {
         }
     }
 
-    function setToken(token, successCallback) {
+    kc.setToken = function(token, successCallback) {
         if (token) {
             sessionStorage.oauthToken = token;
             window.oauth.token = token;
-            instance.token = token;
+            kc.token = token;
 
-            instance.tokenParsed = JSON.parse(atob(token.split('.')[1]));
-            instance.authenticated = true;
-            instance.username = instance.tokenParsed.sub;
-            instance.realmAccess = instance.tokenParsed.realm_access;
-            instance.resourceAccess = instance.tokenParsed.resource_access;
+            kc.tokenParsed = JSON.parse(atob(token.split('.')[1]));
+            kc.authenticated = true;
+            kc.username = kc.tokenParsed.sub;
+            kc.realmAccess = kc.tokenParsed.realm_access;
+            kc.resourceAccess = kc.tokenParsed.resource_access;
 
             setTimeout(function() {
-                successCallback && successCallback({ authenticated: instance.authenticated, username: instance.username });
+                successCallback && successCallback({ authenticated: kc.authenticated, username: kc.username });
             }, 0);
         } else {
             delete sessionStorage.oauthToken;
             delete window.oauth.token;
-            delete instance.token;
+            delete kc.token;
         }
     }
 
-    function createLoginUrl(prompt) {
+    kc.createLoginUrl = function(prompt) {
         var state = createUUID();
 
         sessionStorage.oauthState = state;
-        var url = getRealmUrl()
+        var url = kc.getRealmUrl()
             + '/tokens/login'
             + '?client_id=' + encodeURIComponent(options.clientId)
             + '&redirect_uri=' + getEncodedRedirectUri()
@@ -229,17 +229,22 @@ var Keycloak = function (options) {
         return url;
     }
 
-    function createLogoutUrl() {
-        var url = getRealmUrl()
+    kc.createLogoutUrl = function() {
+        var url = kc.getRealmUrl()
             + '/tokens/logout'
             + '?redirect_uri=' + getEncodedRedirectUri();
         return url;
     }
 
     function getEncodedRedirectUri() {
-        var url = (location.protocol + '//' + location.hostname + (location.port && (':' + location.port)) + location.pathname);
-        if (location.hash) {
-            url += '?redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
+        var url;
+        if (options.redirectUri) {
+            url = options.redirectUri;
+        } else {
+            url = (location.protocol + '//' + location.hostname + (location.port && (':' + location.port)) + location.pathname);
+            if (location.hash) {
+                url += '?redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
+            }
         }
         return encodeURI(url);
     }
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index d243bd1..0630397 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -11,4 +11,6 @@ public interface Constants {
     String INTERNAL_ROLE = "KEYCLOAK_";
 
     String ACCOUNT_MANAGEMENT_APP = "account";
+
+    String INSTALLED_APP_URN = "urn:ietf:wg:oauth:2.0:oob";
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index 38d00e2..eeb6674 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -38,6 +38,7 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.resources.TokenService;
 
+import javax.ws.rs.Path;
 import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
@@ -79,24 +80,32 @@ public class OAuthFlows {
 
     public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect, boolean rememberMe) {
         String code = accessCode.getCode();
-        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
-        log.debug("redirectAccessCode: state: {0}", state);
-        if (state != null)
-            redirectUri.queryParam("state", state);
-        Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
-        Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
-        rememberMe = rememberMe || remember != null;
-        location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo, rememberMe));
-        return location.build();
+
+        if (Constants.INSTALLED_APP_URN.equals(redirect)) {
+            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), code).createCode();
+        } else {
+            UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
+            log.debug("redirectAccessCode: state: {0}", state);
+            if (state != null)
+                redirectUri.queryParam("state", state);
+            Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
+            Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
+            rememberMe = rememberMe || remember != null;
+            location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo, rememberMe));
+            return location.build();
+        }
     }
 
     public Response redirectError(ClientModel client, String error, String state, String redirect) {
-        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("error", error);
-        if (state != null) {
-            redirectUri.queryParam("state", state);
+        if (Constants.INSTALLED_APP_URN.equals(redirect)) {
+            return Flows.forms(realm, request, uriInfo).setError(error).createCode();
+        } else {
+            UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("error", error);
+            if (state != null) {
+                redirectUri.queryParam("state", state);
+            }
+            return Response.status(302).location(redirectUri.build()).build();
         }
-
-        return Response.status(302).location(redirectUri.build()).build();
     }
 
     public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index 620ecad..4e1f043 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -26,8 +26,8 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AuthorizationCodeResponse;
@@ -36,6 +36,7 @@ import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
 
 import java.io.IOException;
@@ -73,6 +74,21 @@ public class AuthorizationCodeTest {
         Assert.assertNotNull(response.getCode());
         Assert.assertEquals("mystate", response.getState());
         Assert.assertNull(response.getError());
+
+        oauth.verifyCode(response.getCode());
+    }
+
+    @Test
+    public void authorizationRequestInstalledApp() throws IOException {
+        oauth.redirectUri(Constants.INSTALLED_APP_URN);
+
+        oauth.doLogin("test-user@localhost", "password");
+
+        String title = driver.getTitle();
+        Assert.assertTrue(title.startsWith("Success code="));
+
+        String code = driver.findElement(By.id("code")).getText();
+        oauth.verifyCode(code);
     }
 
     @Test
@@ -94,6 +110,8 @@ public class AuthorizationCodeTest {
 
         Assert.assertTrue(response.isRedirected());
         Assert.assertNotNull(response.getCode());
+
+        oauth.verifyCode(response.getCode());
     }
 
     @Test
@@ -104,6 +122,8 @@ public class AuthorizationCodeTest {
         Assert.assertNotNull(response.getCode());
         Assert.assertNull(response.getState());
         Assert.assertNull(response.getError());
+
+        oauth.verifyCode(response.getCode());
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 8d9b742..18fb97d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -38,6 +38,8 @@ import org.json.JSONObject;
 import org.junit.Assert;
 import org.keycloak.RSATokenVerifier;
 import org.keycloak.VerificationException;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.representations.AccessScope;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.UserRepresentation;
@@ -156,6 +158,12 @@ public class OAuthClient {
         }
     }
 
+    public void verifyCode(String code) {
+        if (!RSAProvider.verify(new JWSInput(code), realmPublicKey)) {
+            throw new RuntimeException("Failed to verify code");
+        }
+    }
+
     public String getClientId() {
         return clientId;
     }