keycloak-uncached

Details

diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java
index 4d6b551..004f88b 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java
@@ -43,10 +43,11 @@ public class IdpVerifyAccountLinkActionToken extends DefaultActionToken {
 
 
     public IdpVerifyAccountLinkActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId,
-      String identityProviderUsername, String identityProviderAlias) {
+      String identityProviderUsername, String identityProviderAlias, String clientId) {
         super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
         this.identityProviderUsername = identityProviderUsername;
         this.identityProviderAlias = identityProviderAlias;
+        this.issuedFor = clientId;
     }
 
     private IdpVerifyAccountLinkActionToken() {
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionToken.java b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionToken.java
index 5776617..bb7969c 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionToken.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionToken.java
@@ -27,8 +27,9 @@ public class ResetCredentialsActionToken extends DefaultActionToken {
 
     public static final String TOKEN_TYPE = "reset-credentials";
 
-    public ResetCredentialsActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId) {
+    public ResetCredentialsActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String clientId) {
         super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
+        this.issuedFor = clientId;
     }
 
     private ResetCredentialsActionToken() {
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionToken.java b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionToken.java
index 2ccd571..4d0d5a5 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionToken.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionToken.java
@@ -37,9 +37,10 @@ public class VerifyEmailActionToken extends DefaultActionToken {
     @JsonProperty(value = JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID)
     private String originalAuthenticationSessionId;
 
-    public VerifyEmailActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String email) {
+    public VerifyEmailActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String email, String clientId) {
         super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
         this.email = email;
+        this.issuedFor = clientId;
     }
 
     private VerifyEmailActionToken() {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
index 3634d3c..fa8bdea 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -131,7 +131,7 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator 
         String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authSession).getEncodedId();
         IdpVerifyAccountLinkActionToken token = new IdpVerifyAccountLinkActionToken(
           existingUser.getId(), absoluteExpirationInSecs, authSessionEncodedId,
-          brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias()
+          brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias(), authSession.getClient().getClientId()
         );
         UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo),
                 authSession.getClient().getClientId(), authSession.getTabId());
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
index e4befe2..4fb8826 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
@@ -91,7 +91,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
 
         // We send the secret in the email in a link as a query param.
         String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authenticationSession).getEncodedId();
-        ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authSessionEncodedId);
+        ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authSessionEncodedId, authenticationSession.getClient().getClientId());
         String link = UriBuilder
           .fromUri(context.getActionTokenUrl(token.serialize(context.getSession(), context.getRealm(), context.getUriInfo())))
           .build()
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
index c29c616..876dcd4 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -143,7 +143,7 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
         int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;
 
         String authSessionEncodedId = AuthenticationSessionCompoundId.fromAuthSession(authSession).getEncodedId();
-        VerifyEmailActionToken token = new VerifyEmailActionToken(user.getId(), absoluteExpirationInSecs, authSessionEncodedId, user.getEmail());
+        VerifyEmailActionToken token = new VerifyEmailActionToken(user.getId(), absoluteExpirationInSecs, authSessionEncodedId, user.getEmail(), authSession.getClient().getClientId());
         UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo),
                 authSession.getClient().getClientId(), authSession.getTabId());
         String link = builder.build(realm.getName()).toString();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index ca94437..83c294c 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -17,6 +17,7 @@
 package org.keycloak.testsuite.actions;
 
 import org.jboss.arquillian.drone.api.annotation.Drone;
+import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.authentication.actiontoken.verifyemail.VerifyEmailActionToken;
 import org.jboss.arquillian.graphene.page.Page;
 import org.junit.Assert;
@@ -29,6 +30,7 @@ import org.keycloak.events.Errors;
 import org.keycloak.events.EventType;
 import org.keycloak.models.Constants;
 import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
@@ -43,9 +45,11 @@ import org.keycloak.testsuite.pages.InfoPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.RegisterPage;
 import org.keycloak.testsuite.pages.VerifyEmailPage;
+import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
 import org.keycloak.testsuite.updaters.UserAttributeUpdater;
 import org.keycloak.testsuite.util.GreenMailRule;
 import org.keycloak.testsuite.util.MailUtils;
+import org.keycloak.testsuite.util.RealmBuilder;
 import org.keycloak.testsuite.util.SecondBrowser;
 import org.keycloak.testsuite.util.UserActionTokenBuilder;
 import org.keycloak.testsuite.util.UserBuilder;
@@ -384,9 +388,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
         events.expectRequiredAction(EventType.VERIFY_EMAIL)
           .user(testUserId)
           .detail(Details.CODE_ID, Matchers.not(Matchers.is(mailCodeId)))
-          .client(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)   // as authentication sessions are browser-specific,
-                                                            // the client and redirect_uri is unrelated to
-                                                            // the "test-app" specified in loginPage.open()
+          .client(oauth.getClientId())   // the "test-app" client specified in loginPage.open() is expected
           .detail(Details.REDIRECT_URI, Matchers.any(String.class))
           .assertEvent();
 
@@ -630,6 +632,39 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
     }
 
     @Test
+    public void verifyEmailNewBrowserSessionPreserveClient() throws IOException, MessagingException {
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        verifyEmailPage.assertCurrent();
+
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+        MimeMessage message = greenMail.getLastReceivedMessage();
+
+        String verificationUrl = getPasswordResetEmailLink(message);
+
+        // open link in the second browser without the session
+        driver2.navigate().to(verificationUrl.trim());
+
+        // follow the link
+        final WebElement proceedLink = driver2.findElement(By.linkText("» Click here to proceed"));
+        assertThat(proceedLink, Matchers.notNullValue());
+
+        // check if the initial client is preserved
+        String link = proceedLink.getAttribute("href");
+        assertThat(link, Matchers.containsString("client_id=test-app"));
+        proceedLink.click();
+
+        // confirmation in the second browser
+        assertThat(driver2.getPageSource(), Matchers.containsString("kc-info-message"));
+        assertThat(driver2.getPageSource(), Matchers.containsString("Your email address has been verified."));
+
+        final WebElement backToApplicationLink = driver2.findElement(By.linkText("« Back to Application"));
+        assertThat(backToApplicationLink, Matchers.notNullValue());
+    }
+
+    @Test
     public void verifyEmailDuringAuthFlow() throws IOException, MessagingException {
         try (Closeable u = new UserAttributeUpdater(testRealm().users().get(testUserId))
                 .setEmailVerified(false)
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java
index 09f289f..f80a436 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java
@@ -19,9 +19,11 @@ package org.keycloak.testsuite.broker;
 
 import java.util.List;
 
+import org.hamcrest.Matchers;
 import org.jboss.arquillian.graphene.page.Page;
 import org.junit.After;
 import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
@@ -36,6 +38,7 @@ import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.UpdateAccountInformationPage;
 import org.openqa.selenium.TimeoutException;
 
+import static org.junit.Assert.assertThat;
 import static org.keycloak.testsuite.broker.BrokerTestTools.encodeUrl;
 import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
 
@@ -142,6 +145,22 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest {
         return BrokerTestTools.getAuthRoot(suiteContext) + "/auth/realms/" + realmName + "/account/password";
     }
 
+    /**
+     * Get the login page for an existing client in provided realm
+     * @param realmName Name of the realm
+     * @param clientId ClientId of a client. Client has to exists in the realm.
+     * @return Login URL
+     */
+    protected String getLoginUrl(String realmName, String clientId) {
+        List<ClientRepresentation> clients = adminClient.realm(realmName).clients().findByClientId(clientId);
+
+        assertThat(clients, Matchers.is(Matchers.not(Matchers.empty())));
+
+        String redirectURI = clients.get(0).getBaseUrl();
+
+        return BrokerTestTools.getAuthRoot(suiteContext) + "/auth/realms/" + realmName + "/protocol/openid-connect/auth?client_id=" +
+                clientId + "&redirect_uri=" + redirectURI + "&response_type=code&scope=openid";
+    }
 
     protected void logoutFromRealm(String realm) {
         driver.navigate().to(BrokerTestTools.getAuthRoot(suiteContext)
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
index f9f141d..8aaaf0b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
@@ -1,5 +1,8 @@
 package org.keycloak.testsuite.broker;
 
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.jboss.arquillian.drone.api.annotation.Drone;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -16,6 +19,7 @@ import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.pages.ConsentPage;
 import org.keycloak.testsuite.util.*;
 
+import org.openqa.selenium.By;
 import org.openqa.selenium.TimeoutException;
 
 import java.util.Collections;
@@ -27,11 +31,14 @@ import java.util.stream.Collectors;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
+import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername;
 import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
 import static org.keycloak.testsuite.broker.BrokerTestConstants.USER_EMAIL;
 import static org.keycloak.testsuite.util.MailAssert.assertEmailAndGetUrl;
 
 import org.jboss.arquillian.graphene.page.Page;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
 
 import javax.ws.rs.core.Response;
 
@@ -49,6 +56,10 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
 
     protected IdentityProviderResource identityProviderResource;
 
+    @Drone
+    @SecondBrowser
+    protected WebDriver driver2;
+
     @Before
     public void beforeBrokerTest() {
         log.debug("creating user for realm " + bc.providerRealmName());
@@ -181,15 +192,10 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
         MailServer.createEmailAccount(USER_EMAIL, "password");
         
         try {
-            //configure smpt server in the realm
-            RealmRepresentation master = adminClient.realm(bc.consumerRealmName()).toRepresentation();
-            master.setSmtpServer(suiteContext.getSmtpServer());
-            adminClient.realm(bc.consumerRealmName()).update(master);
-        
+            configureSMPTServer();
+
             //create user on consumer's site who should be linked later
-            UserRepresentation newUser = UserBuilder.create().username("consumer").email(USER_EMAIL).enabled(true).build();
-            String userId = createUserWithAdminClient(adminClient.realm(bc.consumerRealmName()), newUser);
-            resetUserPassword(adminClient.realm(bc.consumerRealmName()).users().get(userId), "password", false);
+            String linkedUserId = createUser("consumer");
         
             //test
             driver.navigate().to(getAccountUrl(bc.consumerRealmName()));
@@ -228,8 +234,74 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
             assertEquals(accountPage.buildUri().toASCIIString().replace("master", "consumer") + "/", driver.getCurrentUrl());
             
             //test if the user has verified email
-            assertTrue(adminClient.realm(bc.consumerRealmName()).users().get(userId).toRepresentation().isEmailVerified());
+            assertTrue(adminClient.realm(bc.consumerRealmName()).users().get(linkedUserId).toRepresentation().isEmailVerified());
         } finally {
+            removeUserByUsername(adminClient.realm(bc.consumerRealmName()), "consumer");
+            // stop mail server
+            MailServer.stop();
+        }
+    }
+
+    @Test
+    public void testVerifyEmailInNewBrowserWithPreserveClient() {
+        //start mail server
+        MailServer.start();
+        MailServer.createEmailAccount(USER_EMAIL, "password");
+
+        try {
+            configureSMPTServer();
+
+            //create user on consumer's site who should be linked later
+            String linkedUserId = createUser("consumer");
+
+            driver.navigate().to(getLoginUrl(bc.consumerRealmName(), "broker-app"));
+
+            log.debug("Clicking social " + bc.getIDPAlias());
+            accountLoginPage.clickSocial(bc.getIDPAlias());
+
+            waitForPage(driver, "log in to", true);
+
+            Assert.assertTrue("Driver should be on the provider realm page right now",
+                    driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
+
+            log.debug("Logging in");
+            accountLoginPage.login(bc.getUserLogin(), bc.getUserPassword());
+
+            waitForPage(driver, "update account information", false);
+
+            Assert.assertTrue(updateAccountInformationPage.isCurrent());
+            Assert.assertTrue("We must be on correct realm right now",
+                    driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
+
+            log.debug("Updating info on updateAccount page");
+            updateAccountInformationPage.updateAccountInformation("Firstname", "Lastname");
+
+            //link account by email
+            waitForPage(driver, "account already exists", false);
+            idpConfirmLinkPage.clickLinkAccount();
+
+            String url = assertEmailAndGetUrl(MailServerConfiguration.FROM, USER_EMAIL,
+                    "Someone wants to link your ", false);
+
+            log.info("navigating to url from email in second browser: " + url);
+
+            // navigate to url in the second browser
+            driver2.navigate().to(url);
+
+            final WebElement proceedLink = driver2.findElement(By.linkText("» Click here to proceed"));
+            MatcherAssert.assertThat(proceedLink, Matchers.notNullValue());
+
+            // check if the initial client is preserved
+            String link = proceedLink.getAttribute("href");
+            MatcherAssert.assertThat(link, Matchers.containsString("client_id=broker-app"));
+            proceedLink.click();
+
+            assertThat(driver2.getPageSource(), Matchers.containsString("You successfully verified your email. Please go back to your original browser and continue there with the login."));
+
+            //test if the user has verified email
+            assertTrue(adminClient.realm(bc.consumerRealmName()).users().get(linkedUserId).toRepresentation().isEmailVerified());
+        } finally {
+            removeUserByUsername(adminClient.realm(bc.consumerRealmName()), "consumer");
             // stop mail server
             MailServer.stop();
         }
@@ -431,4 +503,17 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
         String link = errorPage.getBackToApplicationLink();
         Assert.assertTrue(link.endsWith("/auth/realms/consumer/account"));
     }
+
+    private void configureSMPTServer() {
+        RealmRepresentation master = adminClient.realm(bc.consumerRealmName()).toRepresentation();
+        master.setSmtpServer(suiteContext.getSmtpServer());
+        adminClient.realm(bc.consumerRealmName()).update(master);
+    }
+
+    private String createUser(String username) {
+        UserRepresentation newUser = UserBuilder.create().username(username).email(USER_EMAIL).enabled(true).build();
+        String userId = createUserWithAdminClient(adminClient.realm(bc.consumerRealmName()), newUser);
+        resetUserPassword(adminClient.realm(bc.consumerRealmName()).users().get(userId), "password", false);
+        return userId;
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java
index c6da797..5748179 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java
@@ -121,7 +121,20 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
 
     @Override
     public List<ClientRepresentation> createConsumerClients(SuiteContext suiteContext) {
-        return null;
+        ClientRepresentation client = new ClientRepresentation();
+        client.setId("broker-app");
+        client.setClientId("broker-app");
+        client.setName("broker-app");
+        client.setSecret("broker-app-secret");
+        client.setEnabled(true);
+
+        client.setRedirectUris(Collections.singletonList(getAuthRoot(suiteContext) +
+                "/auth/*"));
+
+        client.setBaseUrl(getAuthRoot(suiteContext) +
+                "/auth/realms/" + REALM_CONS_NAME + "/app");
+
+        return Collections.singletonList(client);
     }
 
     @Override
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java
index 904acd6..25877d4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java
@@ -166,6 +166,15 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
             .addRedirectUri("http://localhost:8080/sales-post/*")
             .attribute(SamlConfigAttributes.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE)
             .attribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, SamlProtocol.ATTRIBUTE_FALSE_VALUE)
+            .build(),
+          ClientBuilder.create()
+            .id("broker-app")
+            .clientId("broker-app")
+            .name("broker-app")
+            .secret("broker-app-secret")
+            .enabled(true)
+            .addRedirectUri(getAuthRoot(suiteContext) + "/auth/*")
+            .baseUrl(getAuthRoot(suiteContext) + "/auth/realms/" + REALM_CONS_NAME + "/app")
             .build()
         );
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 633494b..5f36170 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -16,6 +16,8 @@
  */
 package org.keycloak.testsuite.forms;
 
+import org.hamcrest.Matchers;
+import org.jboss.arquillian.drone.api.annotation.Drone;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionToken;
 import org.jboss.arquillian.graphene.page.Page;
@@ -40,9 +42,11 @@ import org.keycloak.testsuite.pages.LoginPasswordResetPage;
 import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
 import org.keycloak.testsuite.pages.VerifyEmailPage;
 import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
+import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
 import org.keycloak.testsuite.util.GreenMailRule;
 import org.keycloak.testsuite.util.MailUtils;
 import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.SecondBrowser;
 import org.keycloak.testsuite.util.UserActionTokenBuilder;
 import org.keycloak.testsuite.util.UserBuilder;
 
@@ -57,7 +61,11 @@ import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.junit.*;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
 
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.*;
 
 /**
@@ -68,6 +76,10 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
 
     private String userId;
 
+    @Drone
+    @SecondBrowser
+    protected WebDriver driver2;
+
     @Override
     public void configureTestRealm(RealmRepresentation testRealm) {
     }
@@ -182,7 +194,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
         String changePasswordUrl = resetPassword("login-test");
         events.clear();
 
-        assertSecondPasswordResetFails(changePasswordUrl, null); // KC_RESTART doesn't exists, it was deleted after first successful reset-password flow was finished
+        assertSecondPasswordResetFails(changePasswordUrl, oauth.getClientId()); // KC_RESTART doesn't exists, it was deleted after first successful reset-password flow was finished
     }
 
     @Test
@@ -194,7 +206,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
         driver.navigate().to(resetUri); // This is necessary to delete KC_RESTART cookie that is restricted to /auth/realms/test path
         driver.manage().deleteAllCookies();
 
-        assertSecondPasswordResetFails(changePasswordUrl, null);
+        assertSecondPasswordResetFails(changePasswordUrl, oauth.getClientId());
     }
 
     public void assertSecondPasswordResetFails(String changePasswordUrl, String clientId) {
@@ -204,7 +216,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
         assertEquals("Action expired. Please continue with login now.", errorPage.getError());
 
         events.expect(EventType.RESET_PASSWORD)
-          .client("account")
+          .client(clientId)
           .session((String) null)
           .user(userId)
           .error(Errors.EXPIRED_CODE)
@@ -1012,4 +1024,36 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
         }
     }
 
+    @Test
+    public void resetPasswordLinkNewBrowserSessionPreserveClient() throws IOException, MessagingException {
+        loginPage.open();
+        loginPage.resetPassword();
+
+        resetPasswordPage.assertCurrent();
+
+        resetPasswordPage.changePassword("login-test");
+
+        loginPage.assertCurrent();
+        assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
+
+        assertEquals(1, greenMail.getReceivedMessages().length);
+
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+
+        String changePasswordUrl = MailUtils.getPasswordResetEmailLink(message);
+
+        driver2.navigate().to(changePasswordUrl.trim());
+
+        final WebElement newPassword = driver2.findElement(By.id("password-new"));
+        newPassword.sendKeys("resetPassword");
+        final WebElement confirmPassword = driver2.findElement(By.id("password-confirm"));
+        confirmPassword.sendKeys("resetPassword");
+        final WebElement submit = driver2.findElement(By.cssSelector("input[type=\"submit\"]"));
+        submit.click();
+
+        assertThat(driver2.getCurrentUrl(), Matchers.containsString("client_id=test-app"));
+
+        assertThat(driver2.getPageSource(), Matchers.containsString("Your account has been updated."));
+    }
+
 }