keycloak-aplcache

Changes

Details

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 2a21202..6d1ffa1 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
@@ -47,6 +47,14 @@
                             <input ng-model="application.enabled" name="enabled" id="enabled" onoffswitch />
                         </div>
                         <div class="form-group">
+                            <label for="baseUrl" class="control-label">Base URL</label>
+
+                            <div class="controls">
+                                <input class="input-small" type="text" name="baseUrl" id="baseUrl"
+                                       data-ng-model="application.baseUrl">
+                            </div>
+                        </div>
+                        <div class="form-group">
                             <label for="adminUrl" class="control-label">Admin URL</label>
 
                             <div class="controls">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-list.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-list.html
index d472c61..16a1697 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-list.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-list.html
@@ -36,11 +36,12 @@
                     <tr data-ng-show="applications.length > 0">
                         <th>Application Name</th>
                         <th>Enabled</th>
+                        <th>Base URL</th>
                     </tr>
                     </thead>
                     <tfoot data-ng-show="applications && applications.length > 5"> <!-- todo -->
                     <tr>
-                        <td colspan="2">
+                        <td colspan="3">
                             <div class="table-nav">
                                 <a href="#" class="first disabled">First page</a><a href="#" class="prev disabled">Previous
                                 page</a><span><strong>1-8</strong> of <strong>10</strong></span><a href="#"
@@ -54,6 +55,7 @@
                     <tr ng-repeat="app in applications">
                         <td><a href="#/realms/{{realm.id}}/applications/{{app.id}}">{{app.name}}</a></td>
                         <td>{{app.enabled}}</td>
+                        <td ng-class="{'text-muted': !app.baseUrl}">{{app.baseUrl || "Not defined"}}</td>
                     </tr>
                     </tbody>
                 </table>
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 8f7d2ab..559ebc2 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -12,6 +12,7 @@ public class ApplicationRepresentation {
     protected String id;
     protected String name;
     protected String adminUrl;
+    protected String baseUrl;
     protected boolean surrogateAuthRequired;
     protected boolean enabled;
     protected List<CredentialRepresentation> credentials;
@@ -89,6 +90,14 @@ public class ApplicationRepresentation {
         this.adminUrl = adminUrl;
     }
 
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
     public List<CredentialRepresentation> getCredentials() {
         return credentials;
     }
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 cf46b1e..7a4cc69 100755
--- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
@@ -10,6 +10,7 @@ import java.util.List;
 public class OAuthClientRepresentation {
     protected String id;
     protected String name;
+    protected String baseUrl;
     protected List<String> redirectUris;
     protected List<String> webOrigins;
     protected boolean enabled;
@@ -39,6 +40,14 @@ public class OAuthClientRepresentation {
         this.enabled = enabled;
     }
 
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
     public List<String> getRedirectUris() {
         return redirectUris;
     }
diff --git a/forms/src/main/java/org/keycloak/forms/UrlBean.java b/forms/src/main/java/org/keycloak/forms/UrlBean.java
index 49635a0..579659d 100755
--- a/forms/src/main/java/org/keycloak/forms/UrlBean.java
+++ b/forms/src/main/java/org/keycloak/forms/UrlBean.java
@@ -36,6 +36,8 @@ public class UrlBean {
 
     private boolean socialRegistration;
 
+    private String referrerURI;
+
     public boolean isSocialRegistration() {
         return socialRegistration;
     }
@@ -44,9 +46,10 @@ public class UrlBean {
         this.socialRegistration = socialRegistration;
     }
 
-    public UrlBean(RealmBean realm, URI baseURI){
+    public UrlBean(RealmBean realm, URI baseURI, String referrerURI){
         this.realm = realm;
         this.baseURI = baseURI;
+        this.referrerURI = referrerURI;
     }
 
     public RealmBean getRealm() {
@@ -136,4 +139,8 @@ public class UrlBean {
         return Urls.loginActionEmailVerification(baseURI, realm.getId()).toString();
     }
 
+    public String getReferrerURI() {
+        return referrerURI;
+    }
+
 }
diff --git a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
index 28ed554..329dbf2 100755
--- a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
+++ b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
@@ -35,6 +35,7 @@ import org.keycloak.forms.TemplateBean;
 import org.keycloak.forms.TotpBean;
 import org.keycloak.forms.UrlBean;
 import org.keycloak.forms.UserBean;
+import org.keycloak.models.ApplicationModel;
 import org.keycloak.services.FormService;
 import org.keycloak.services.resources.flows.Pages;
 
@@ -54,22 +55,22 @@ public class FormServiceImpl implements FormService {
 
     private static final String ID = "FormServiceId";
     private static final String BUNDLE = "org.keycloak.forms.messages";
-    private final Map<String, Command> commandMap = new HashMap<String,Command>();
+    private final Map<String, CommandCommon> commandMap = new HashMap<String, CommandCommon>();
 
     public FormServiceImpl(){
         commandMap.put(Pages.LOGIN, new CommandLogin());
         commandMap.put(Pages.REGISTER, new CommandRegister());
-        commandMap.put(Pages.ACCOUNT, new CommandAccount());
-        commandMap.put(Pages.LOGIN_UPDATE_PROFILE, new CommandPassword());
-        commandMap.put(Pages.PASSWORD, new CommandPassword());
-        commandMap.put(Pages.LOGIN_RESET_PASSWORD, new CommandPassword());
-        commandMap.put(Pages.LOGIN_UPDATE_PASSWORD, new CommandPassword());
-        commandMap.put(Pages.ACCESS, new CommandAccess());
-        commandMap.put(Pages.SOCIAL, new CommandSocial());
+        commandMap.put(Pages.ACCOUNT, new CommandCommon());
+        commandMap.put(Pages.LOGIN_UPDATE_PROFILE, new CommandCommon());
+        commandMap.put(Pages.PASSWORD, new CommandCommon());
+        commandMap.put(Pages.LOGIN_RESET_PASSWORD, new CommandCommon());
+        commandMap.put(Pages.LOGIN_UPDATE_PASSWORD, new CommandCommon());
+        commandMap.put(Pages.ACCESS, new CommandCommon());
+        commandMap.put(Pages.SOCIAL, new CommandCommon());
         commandMap.put(Pages.TOTP, new CommandTotp());
         commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp());
         commandMap.put(Pages.LOGIN_TOTP, new CommandLoginTotp());
-        commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandVerifyEmail());
+        commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandCommon());
         commandMap.put(Pages.OAUTH_GRANT, new CommandOAuthGrant());
     }
 
@@ -117,121 +118,69 @@ public class FormServiceImpl implements FormService {
         return out.toString();
     }
 
-    private class CommandTotp implements Command {
+    private class CommandCommon {
+        protected RealmBean realm;
+        protected UrlBean url;
+        protected UserBean user;
+        protected LoginBean login;
+
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            RealmBean realm = new RealmBean(dataBean.getRealm());
+            realm = new RealmBean(dataBean.getRealm());
+
+            String referrer = dataBean.getQueryParam("referrer");
+            String referrerUri = null;
+            if (referrer != null) {
+                for (ApplicationModel a : dataBean.getRealm().getApplications()) {
+                    if (a.getName().equals(referrer)) {
+                        referrerUri = a.getBaseUrl();
+                        break;
+                    }
+                }
+            }
+
+            url = new UrlBean(realm, dataBean.getBaseURI(), referrerUri);
+            url.setSocialRegistration(dataBean.getSocialRegistration());
+            user = new UserBean(dataBean.getUserModel());
+            login = new LoginBean(realm, dataBean.getFormData());
 
             attributes.put("realm", realm);
-            attributes.put("url", new UrlBean(realm, dataBean.getBaseURI()));
-
-            UserBean user = new UserBean(dataBean.getUserModel());
+            attributes.put("url", url);
             attributes.put("user", user);
-
-            TotpBean totp = new TotpBean(user, dataBean.getContextPath());
-            attributes.put("totp", totp);
-
-            attributes.put("login", new LoginBean(realm, dataBean.getFormData()));
-        }
-    }
-
-    private class CommandSocial implements Command {
-        public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            RealmBean realm = new RealmBean(dataBean.getRealm());
-            attributes.put("user", new UserBean(dataBean.getUserModel()));
-            attributes.put("url", new UrlBean(realm, dataBean.getBaseURI()));
-        }
-    }
-
-    private class CommandEmail implements Command {
-        public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
+            attributes.put("login", login);
         }
     }
 
-    private class CommandPassword implements Command {
+    private class CommandTotp extends CommandCommon {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            RealmBean realm = new RealmBean(dataBean.getRealm());
+            super.exec(attributes, dataBean);
 
-            attributes.put("realm", realm);
-            attributes.put("url", new UrlBean(realm, dataBean.getBaseURI()));
-            attributes.put("user", new UserBean(dataBean.getUserModel()));
-            attributes.put("login", new LoginBean(realm, dataBean.getFormData()));
+            attributes.put("totp", new TotpBean(user, dataBean.getContextPath()));
         }
     }
 
-    private class CommandLoginTotp implements Command {
+    private class CommandLoginTotp extends CommandCommon {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-
-            RealmBean realm = new RealmBean(dataBean.getRealm());
-
-            attributes.put("realm", realm);
-
-            UrlBean url = new UrlBean(realm, dataBean.getBaseURI());
-            url.setSocialRegistration(dataBean.getSocialRegistration());
-
-            attributes.put("url", url);
-            attributes.put("user", new UserBean(dataBean.getUserModel()));
-            attributes.put("login", new LoginBean(realm, dataBean.getFormData()));
+            super.exec(attributes, dataBean);
 
             RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration());
-
             SocialBean social = new SocialBean(realm, dataBean.getSocialProviders(), register, url);
             attributes.put("social", social);
         }
     }
 
-    private class CommandAccess implements Command {
-        public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            RealmBean realm = new RealmBean(dataBean.getRealm());
-
-            attributes.put("realm", realm);
-            attributes.put("user", new UserBean(dataBean.getUserModel()));
-            attributes.put("url", new UrlBean(realm, dataBean.getBaseURI()));
-        }
-    }
-
-    private class CommandAccount implements Command {
-        public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            RealmBean realm = new RealmBean(dataBean.getRealm());
-
-            attributes.put("realm", realm);
-            attributes.put("url", new UrlBean(realm, dataBean.getBaseURI()));
-            attributes.put("user", new UserBean(dataBean.getUserModel()));
-            attributes.put("login", new LoginBean(realm, dataBean.getFormData()));
-        }
-    }
-
-    private class CommandLogin implements Command {
+    private class CommandLogin extends CommandCommon {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-            RealmBean realm = new RealmBean(dataBean.getRealm());
-
-            attributes.put("realm", realm);
-
-            UrlBean url = new UrlBean(realm, dataBean.getBaseURI());
-            url.setSocialRegistration(dataBean.getSocialRegistration());
-
-            attributes.put("url", url);
-            attributes.put("user", new UserBean(dataBean.getUserModel()));
-            attributes.put("login", new LoginBean(realm, dataBean.getFormData()));
+            super.exec(attributes, dataBean);
 
             RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration());
-
             SocialBean social = new SocialBean(realm, dataBean.getSocialProviders(), register, url);
             attributes.put("social", social);
         }
     }
 
-    private class CommandRegister implements Command {
+    private class CommandRegister extends CommandCommon {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-
-            RealmBean realm = new RealmBean(dataBean.getRealm());
-
-            attributes.put("realm", realm);
-
-            UrlBean url = new UrlBean(realm, dataBean.getBaseURI());
-            url.setSocialRegistration(dataBean.getSocialRegistration());
-
-            attributes.put("url", url);
-            attributes.put("user", new UserBean(dataBean.getUserModel()));
+            super.exec(attributes, dataBean);
 
             RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration());
             attributes.put("register", register);
@@ -241,8 +190,9 @@ public class FormServiceImpl implements FormService {
         }
     }
 
-    private class CommandOAuthGrant implements Command {
+    private class CommandOAuthGrant extends CommandCommon {
         public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
+            super.exec(attributes, dataBean);
 
             OAuthGrantBean oauth = new OAuthGrantBean();
             oauth.setAction(dataBean.getOAuthAction());
@@ -255,22 +205,4 @@ public class FormServiceImpl implements FormService {
         }
     }
 
-    private class CommandVerifyEmail implements Command {
-        public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
-
-            RealmBean realm = new RealmBean(dataBean.getRealm());
-
-            attributes.put("realm", realm);
-
-            UrlBean url = new UrlBean(realm, dataBean.getBaseURI());
-            url.setSocialRegistration(dataBean.getSocialRegistration());
-
-            attributes.put("url", url);
-        }
-    }
-
-    private interface Command {
-        public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean);
-    }
-
 }
\ No newline at end of file
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl
index 4e7caac..cdd5fef 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl
@@ -27,7 +27,7 @@
             </div>
         </fieldset>
         <div class="form-actions">
-            <#--a href="#">« Back to my application</a-->
+            <#if url.referrerURI??><a href="${url.referrerURI}">Back to application</a></#if>
             <button type="submit" class="primary">Save</button>
             <button type="submit">Cancel</button>
         </div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl
index 5c48264..b92f3e6 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl
@@ -23,7 +23,7 @@
             </div>
         </fieldset>
         <div class="form-actions">
-            <#--a href="#">« Back to my application</a-->
+            <#if url.referrerURI??><a href="${url.referrerURI}">Back to application</a></#if>
             <button type="submit" class="primary">Save</button>
             <button type="submit">Cancel</button>
         </div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.ftl
index 5f4d6dc..cad065e 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.ftl
@@ -54,6 +54,7 @@
                         <input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
                     </div>
                     <div class="form-actions">
+                        <#if url.referrerURI??><a href="${url.referrerURI}">Back to application</a></#if>
                         <button type="submit" class="primary">Submit</button>
                     </div>
                 </form>
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 9039f4f..e5374b8 100755
--- a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
@@ -29,6 +29,10 @@ public interface ApplicationModel extends RoleContainerModel, RoleMapperModel, S
 
     void setManagementUrl(String url);
 
+    String getBaseUrl();
+
+    void setBaseUrl(String url);
+
     List<String> getDefaultRoles();
 
     void addDefaultRole(String name);
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 ad5f9e1..c196edd 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
@@ -83,6 +83,16 @@ public class ApplicationAdapter implements ApplicationModel {
     }
 
     @Override
+    public String getBaseUrl() {
+        return application.getBaseUrl();
+    }
+
+    @Override
+    public void setBaseUrl(String url) {
+        application.setBaseUrl(url);
+    }
+
+    @Override
     public RoleModel getRole(String name) {
         Collection<RoleEntity> roles = application.getRoles();
         if (roles == null) return null;
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 c8a7400..c0e2144 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
@@ -25,6 +25,7 @@ public class ApplicationEntity {
     private String name;
     private boolean enabled;
     private boolean surrogateAuthRequired;
+    private String baseUrl;
     private String managementUrl;
 
     @OneToOne(fetch = FetchType.EAGER)
@@ -58,6 +59,14 @@ public class ApplicationEntity {
         this.surrogateAuthRequired = surrogateAuthRequired;
     }
 
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
     public String getManagementUrl() {
         return managementUrl;
     }
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java
index e6fe85b..98a510a 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java
@@ -106,6 +106,17 @@ public class ApplicationAdapter implements ApplicationModel {
     }
 
     @Override
+    public String getBaseUrl() {
+        return applicationData.getBaseUrl();
+    }
+
+    @Override
+    public void setBaseUrl(String url) {
+        applicationData.setBaseUrl(url);
+        updateApplication();
+    }
+
+    @Override
     public RoleAdapter getRole(String name) {
         Role role = SampleModel.getRole(getIdm(), name);
         if (role == null) return null;
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java
index 3adb8f3..ec4b4e7 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java
@@ -13,6 +13,7 @@ public class ApplicationData extends AbstractPartition {
     private boolean enabled;
     private boolean surrogateAuthRequired;
     private String managementUrl;
+    private String baseUrl;
     private User resourceUser;
     private String[] defaultRoles;
 
@@ -59,6 +60,15 @@ public class ApplicationData extends AbstractPartition {
     }
 
     @AttributeProperty
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    @AttributeProperty
     public String getManagementUrl() {
         return managementUrl;
     }
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java
index 88af1a6..8e450a7 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java
@@ -31,6 +31,8 @@ public class ApplicationEntity implements Serializable {
     private boolean surrogateAuthRequired;
     @AttributeValue
     private String managementUrl;
+    @AttributeValue
+    private String baseUrl;
 
     @AttributeValue
     private String[] defaultRoles;
diff --git a/services/src/main/java/org/keycloak/services/FormService.java b/services/src/main/java/org/keycloak/services/FormService.java
index 9a357a3..716e76b 100755
--- a/services/src/main/java/org/keycloak/services/FormService.java
+++ b/services/src/main/java/org/keycloak/services/FormService.java
@@ -53,6 +53,7 @@ public interface FormService {
         private FormFlows.MessageType messageType;
 
         private MultivaluedMap<String, String> formData;
+        private Map<String, String> queryParams;
         private URI baseURI;
 
         private List<SocialProvider> socialProviders;
@@ -87,10 +88,11 @@ public interface FormService {
 
         private String contextPath;
 
-        public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap<String, String> formData, String message) {
+        public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap<String, String> formData, Map<String, String> queryParams, String message) {
             this.realm = realm;
             this.userModel = userModel;
             this.formData = formData;
+            this.queryParams = queryParams;
             this.message = message;
 
             socialProviders = new LinkedList<SocialProvider>();
@@ -125,6 +127,16 @@ public interface FormService {
             return formData;
         }
 
+        public Map<String, String> getQueryParams() {
+            return queryParams;
+        }
+
+
+        public String getQueryParam(String key) {
+            return queryParams != null ? queryParams.get(key) : null;
+        }
+
+
         public void setFormData(MultivaluedMap<String, String> formData) {
             this.formData = formData;
         }
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 e3c3322..1e5a24f 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
@@ -50,6 +50,7 @@ public class ApplicationManager {
         applicationModel.setEnabled(resourceRep.isEnabled());
         applicationModel.setManagementUrl(resourceRep.getAdminUrl());
         applicationModel.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
+        applicationModel.setBaseUrl(resourceRep.getBaseUrl());
         applicationModel.updateApplication();
 
         UserModel resourceUser = applicationModel.getApplicationUser();
@@ -128,6 +129,7 @@ public class ApplicationManager {
         resource.setName(rep.getName());
         resource.setEnabled(rep.isEnabled());
         resource.setManagementUrl(rep.getAdminUrl());
+        resource.setBaseUrl(rep.getBaseUrl());
         resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
         resource.updateApplication();
 
@@ -153,6 +155,7 @@ public class ApplicationManager {
         rep.setEnabled(applicationModel.isEnabled());
         rep.setAdminUrl(applicationModel.getManagementUrl());
         rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired());
+        rep.setBaseUrl(applicationModel.getBaseUrl());
 
         Set<String> redirectUris = applicationModel.getApplicationUser().getRedirectUris();
         if (redirectUris != null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 584ef1c..22e7269 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -46,6 +46,7 @@ import javax.ws.rs.*;
 import javax.ws.rs.core.*;
 import javax.ws.rs.ext.Providers;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.List;
 
 /**
@@ -89,7 +90,15 @@ public class AccountService {
             if (!hasAccess(auth)) {
                 return noAccess();
             }
-            return Flows.forms(realm, request, uriInfo).setUser(auth.getUser()).forwardToForm(template);
+
+            FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(auth.getUser());
+
+            String referrer = getReferrer();
+            if (referrer != null) {
+                forms.setQueryParam("referrer", referrer);
+            }
+
+            return forms.forwardToForm(template);
         } else {
             return login(path);
         }
@@ -321,11 +330,8 @@ public class AccountService {
                 throw new BadRequestException();
             }
 
-            UriBuilder redirectBuilder = Urls.accountBase(uriInfo.getBaseUri());
-            if (path != null) {
-                redirectBuilder.path(path);
-            }
-            URI redirectUri = redirectBuilder.build(realm.getId());
+            URI accountUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getId());
+            URI redirectUri = path != null ? accountUri.resolve(path) : accountUri;
 
             NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
             return Response.status(302).cookie(cookie).location(redirectUri).build();
@@ -353,6 +359,11 @@ public class AccountService {
 
         URI accountUri = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect").build(realm.getId());
 
+        String referrer = getReferrer();
+        if (referrer != null) {
+            path = (path != null ? path : "") + "?referrer=" + referrer;
+        }
+
         oauth.setStateCookiePath(accountUri.getPath());
         return oauth.redirect(uriInfo, accountUri.toString(), path);
     }
@@ -393,4 +404,23 @@ public class AccountService {
         return application.hasRole(user, role);
     }
 
+    private String getReferrer() {
+        String referrer = uriInfo.getQueryParameters().getFirst("referrer");
+        if (referrer != null) {
+            return referrer;
+        }
+
+        String referrerUrl = headers.getHeaderString("Referer");
+        if (referrerUrl != null) {
+            for (ApplicationModel a : realm.getApplications()) {
+                if (a.getBaseUrl() != null && referrerUrl.startsWith(a.getBaseUrl())) {
+                    return a.getName();
+                }
+            }
+            return null;
+        }
+
+        return null;
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
index 3dcd6b5..e95c3aa 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
@@ -39,18 +39,16 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.net.URI;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class FormFlows {
 
-    public static final String DATA = "KEYCLOAK_FORMS_DATA";
-    public static final String ERROR_MESSAGE = "KEYCLOAK_FORMS_ERROR_MESSAGE";
-    public static final String USER = UserModel.class.getName();
-    public static final String SOCIAL_REGISTRATION = "socialRegistration";
     public static final String CODE = "code";
 
     // TODO refactor/rename "error" to "message" everywhere where it makes sense
@@ -61,6 +59,8 @@ public class FormFlows {
 
     private MultivaluedMap<String, String> formData;
 
+    private Map<String, String> queryParams;
+
     private RealmModel realm;
 
     private HttpRequest request;
@@ -118,6 +118,12 @@ public class FormFlows {
             uriBuilder.queryParam(CODE, accessCode.getCode());
         }
 
+        if (queryParams != null) {
+            for (Map.Entry<String, String> q : queryParams.entrySet()) {
+                uriBuilder.replaceQueryParam(q.getKey(), q.getValue());
+            }
+        }
+
         URI baseURI = uriBuilder.build();
         formDataBean.setBaseURI(baseURI);
 
@@ -140,7 +146,7 @@ public class FormFlows {
 
     public Response forwardToForm(String template) {
 
-        FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, error);
+        FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, queryParams, error);
         formDataBean.setMessageType(messageType);
 
         return forwardToForm(template, formDataBean);
@@ -192,7 +198,7 @@ public class FormFlows {
 
     public Response forwardToOAuthGrant(){
 
-        FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, error);
+        FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, queryParams, error);
 
         formDataBean.setOAuthRealmRolesRequested((List<RoleModel>) request.getAttribute("realmRolesRequested"));
         formDataBean.setOAuthResourceRolesRequested((MultivaluedMap<String, RoleModel>) request.getAttribute("resourceRolesRequested"));
@@ -208,6 +214,14 @@ public class FormFlows {
         return this;
     }
 
+    public FormFlows setQueryParam(String key, String value) {
+        if (queryParams == null) {
+            queryParams = new HashMap<String, String>();
+        }
+        queryParams.put(key, value);
+        return this;
+    }
+
     public FormFlows setError(String error) {
         this.error = error;
         return this;
diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
index f96e089..e322481 100755
--- a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
+++ b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
@@ -40,6 +40,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
 
         realm = manager.createRealm("original");
         application = realm.addApplication("application");
+        application.setBaseUrl("http://base");
         application.setManagementUrl("http://management");
         application.setName("app-name");
         application.addRole("role-1");
@@ -82,6 +83,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
 
     public static void assertEquals(ApplicationModel expected, ApplicationModel actual) {
         Assert.assertEquals(expected.getName(), actual.getName());
+        Assert.assertEquals(expected.getBaseUrl(), actual.getBaseUrl());
         Assert.assertEquals(expected.getManagementUrl(), actual.getManagementUrl());
         Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java
index 9aa9655..996085e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java
@@ -39,23 +39,11 @@ import java.util.List;
  */
 public class ApplicationServlet extends HttpServlet {
 
+    private static final String LINK = "<a href=\"%s\" id=\"%s\">%s</a>";
+
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         String title = "";
-        String body = "";
-
-        StringBuffer sb = req.getRequestURL();
-        sb.append("?");
-        sb.append(req.getQueryString());
-
-        List<NameValuePair> query = null;
-
-        try {
-            query = URLEncodedUtils.parse(new URI(sb.toString()), "UTF-8");
-        } catch (URISyntaxException e) {
-            throw new ServletException(e);
-        }
-
         if (req.getRequestURI().endsWith("auth")) {
             title = "AUTH_RESPONSE";
         } else if (req.getRequestURI().endsWith("logout")) {
@@ -65,7 +53,11 @@ public class ApplicationServlet extends HttpServlet {
         }
 
         PrintWriter pw = resp.getWriter();
-        pw.printf("<html><head><title>%s</title></head><body>%s</body>", title, body);
+        pw.printf("<html><head><title>%s</title></head><body>", title);
+
+        pw.printf(LINK, "http://localhost:8081/auth-server/rest/realms/test/account", "account", "account");
+
+        pw.print("</body></html>");
         pw.flush();
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
index 4d0d3ca..17d6348 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
@@ -112,6 +112,28 @@ public class AccountTest {
     }
 
     @Test
+    public void returnToAppFromHeader() {
+        appPage.open();
+        appPage.openAccount();
+        loginPage.login("test-user@localhost", "password");
+
+        Assert.assertTrue(profilePage.isCurrent());
+        profilePage.backToApplication();
+
+        Assert.assertTrue(appPage.isCurrent());
+    }
+
+    @Test
+    public void returnToAppFromQueryParam() {
+        driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");
+        loginPage.login("test-user@localhost", "password");
+        Assert.assertTrue(profilePage.isCurrent());
+        profilePage.backToApplication();
+
+        Assert.assertTrue(appPage.isCurrent());
+    }
+
+    @Test
     public void changePassword() {
         changePasswordPage.open();
         loginPage.login("test-user@localhost", "password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index a9f6495..d19ca29 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -30,7 +30,7 @@ import org.openqa.selenium.support.FindBy;
  */
 public class AccountUpdateProfilePage extends AbstractAccountPage {
 
-    private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account";
+    public static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account";
 
     @FindBy(id = "firstName")
     private WebElement firstNameInput;
@@ -41,6 +41,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
     @FindBy(id = "email")
     private WebElement emailInput;
 
+
+    @FindBy(linkText = "Back to application")
+    private WebElement backToApplicationLink;
+
     @FindBy(css = "button[type=\"submit\"]")
     private WebElement submitButton;
 
@@ -78,6 +82,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
         driver.navigate().to(PATH);
     }
 
+    public void backToApplication() {
+        backToApplicationLink.click();
+    }
+
     public boolean isSuccess(){
         return feedbackMessage != null && "Success!".equals(feedbackMessage.getText());
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
index 3faa197..c0ce139 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
@@ -22,6 +22,9 @@
 package org.keycloak.testsuite.pages;
 
 
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -29,6 +32,9 @@ public class AppPage extends AbstractPage {
 
     private String baseUrl = "http://localhost:8081/app";
 
+    @FindBy(id = "account")
+    private WebElement accountLink;
+
     @Override
     public void open() {
         driver.navigate().to(baseUrl);
@@ -43,6 +49,10 @@ public class AppPage extends AbstractPage {
         return RequestType.valueOf(driver.getTitle());
     }
 
+    public void openAccount() {
+        accountLink.click();
+    }
+
     public enum RequestType {
         AUTH_RESPONSE, LOGOUT_REQUEST, APP_REQUEST
     }
diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json
index 4122174..cf8d8ae 100755
--- a/testsuite/integration/src/test/resources/testrealm.json
+++ b/testsuite/integration/src/test/resources/testrealm.json
@@ -72,6 +72,7 @@
         {
             "name": "test-app",
             "enabled": true,
+            "baseUrl": "http://localhost:8081/app",
             "adminUrl": "http://localhost:8081/app/logout",
             "credentials": [
                 {