keycloak-uncached

Merge pull request #3187 from mposolda/master KEYCLOAK-3349

9/2/2016 4:58:25 PM

Changes

Details

diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java b/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java
index e8be6d6..953797b 100755
--- a/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java
+++ b/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java
@@ -50,7 +50,7 @@ public abstract class AbstractIdentityProviderFactory<T extends IdentityProvider
     }
 
     @Override
-    public Map<String, String> parseConfig(InputStream inputStream) {
+    public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
         return new HashMap<String, String>();
     }
 }
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java b/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java
index fba8e64..93ef720 100755
--- a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java
+++ b/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java
@@ -17,6 +17,7 @@
 package org.keycloak.broker.provider;
 
 import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.provider.ProviderFactory;
 
 import java.io.InputStream;
@@ -47,8 +48,9 @@ public interface IdentityProviderFactory<T extends IdentityProvider> extends Pro
      * <p>Creates an {@link IdentityProvider} based on the configuration from
      * <code>inputStream</code>.</p>
      *
+     * @param session
      * @param inputStream The input stream from where configuration will be loaded from..
      * @return
      */
-    Map<String, String> parseConfig(InputStream inputStream);
+    Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream);
 }
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
index 789d381..86dd4e8 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
@@ -40,6 +40,7 @@ import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -166,30 +167,13 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
     }
 
     protected PublicKey getSignatureValidationKey(ClientModel client, ClientAuthenticationFlowContext context) {
-        CertificateRepresentation certInfo = CertificateInfoHelper.getCertificateFromClient(client, ATTR_PREFIX);
-
-        String encodedCertificate = certInfo.getCertificate();
-        String encodedPublicKey = certInfo.getPublicKey();
-
-        if (encodedCertificate == null && encodedPublicKey == null) {
-            Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", "Client '" + client.getClientId() + "' doesn't have certificate or publicKey configured");
-            context.failure(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, challengeResponse);
-            return null;
-        }
-
-        if (encodedCertificate != null && encodedPublicKey != null) {
-            Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", "Client '" + client.getClientId() + "' has both publicKey and certificate configured");
+        try {
+            return CertificateInfoHelper.getSignatureValidationKey(client, ATTR_PREFIX);
+        } catch (ModelException me) {
+            Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", me.getMessage());
             context.failure(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, challengeResponse);
             return null;
         }
-
-        // TODO: Caching of publicKeys / certificates, so it doesn't need to be always computed from pem. For performance reasons...
-        if (encodedCertificate != null) {
-            X509Certificate clientCert = KeycloakModelUtils.getCertificate(encodedCertificate);
-            return clientCert.getPublicKey();
-        } else {
-            return KeycloakModelUtils.getPublicKey(encodedPublicKey);
-        }
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java
index d3e8eba..56f24a2 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java
@@ -18,6 +18,7 @@ package org.keycloak.broker.oidc;
 
 import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
 import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
 
 import java.io.InputStream;
 import java.util.Map;
@@ -45,8 +46,8 @@ public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProvide
     }
 
     @Override
-    public Map<String, String> parseConfig(InputStream inputStream) {
-        return OIDCIdentityProviderFactory.parseOIDCConfig(inputStream);
+    public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
+        return OIDCIdentityProviderFactory.parseOIDCConfig(session, inputStream);
 
     }
 
diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
index 82c2cdd..a0e5017 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
@@ -19,6 +19,7 @@ package org.keycloak.broker.oidc;
 import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
 import org.keycloak.jose.jwk.JWK;
 import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.jose.jwk.JSONWebKeySet;
 import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
@@ -56,11 +57,11 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory
     }
 
     @Override
-    public Map<String, String> parseConfig(InputStream inputStream) {
-        return parseOIDCConfig(inputStream);
+    public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
+        return parseOIDCConfig(session, inputStream);
     }
 
-    protected static Map<String, String> parseOIDCConfig(InputStream inputStream) {
+    protected static Map<String, String> parseOIDCConfig(KeycloakSession session, InputStream inputStream) {
         OIDCConfigurationRepresentation rep;
         try {
             rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class);
@@ -74,14 +75,14 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory
         config.setTokenUrl(rep.getTokenEndpoint());
         config.setUserInfoUrl(rep.getUserinfoEndpoint());
         if (rep.getJwksUri() != null) {
-            sendJwksRequest(rep, config);
+            sendJwksRequest(session, rep, config);
         }
         return config.getConfig();
     }
 
-    protected static void sendJwksRequest(OIDCConfigurationRepresentation rep, OIDCIdentityProviderConfig config) {
+    protected static void sendJwksRequest(KeycloakSession session, OIDCConfigurationRepresentation rep, OIDCIdentityProviderConfig config) {
         try {
-            JSONWebKeySet keySet = JWKSUtils.sendJwksRequest(rep.getJwksUri());
+            JSONWebKeySet keySet = JWKSUtils.sendJwksRequest(session, rep.getJwksUri());
             PublicKey key = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
             if (key == null) {
                 logger.supportedJwkNotFound(JWK.Use.SIG.asString());
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
index dac6750..2617995 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
@@ -24,6 +24,7 @@ import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType;
 import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
 import org.keycloak.dom.saml.v2.metadata.KeyTypes;
 import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ParsingException;
 import org.keycloak.saml.common.util.DocumentUtil;
@@ -54,7 +55,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
     }
 
     @Override
-    public Map<String, String> parseConfig(InputStream inputStream) {
+    public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
         try {
             Object parsedObject = new SAMLParser().parse(inputStream);
             EntityDescriptorType entityType;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 931b04c..1b4db29 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -17,11 +17,6 @@
 
 package org.keycloak.protocol.oidc.endpoints;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
 import javax.ws.rs.GET;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -42,6 +37,8 @@ import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.protocol.AuthorizationEndpointBase;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
+import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequestParserProcessor;
 import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder;
 import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
 import org.keycloak.protocol.oidc.utils.OIDCResponseType;
@@ -67,43 +64,10 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
     /**
      * Prefix used to store additional HTTP GET params from original client request into {@link ClientSessionModel} note to be available later in Authenticators, RequiredActions etc. Prefix is used to
      * prevent collisions with internally used notes.
-     * 
+     *
      * @see ClientSessionModel#getNote(String)
-     * @see #KNOWN_REQ_PARAMS
-     * @see #additionalReqParams
      */
     public static final String CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX = "client_request_param_";
-    /**
-     * Max number of additional req params copied into client session note to prevent DoS attacks
-     * 
-     * @see #additionalReqParams
-     */
-    public static final int ADDITIONAL_REQ_PARAMS_MAX_MUMBER = 5;
-    /**
-     * Max size of additional req param value copied into client session note to prevent DoS attacks - params with longer value are ignored
-     * 
-     * @see #additionalReqParams
-     */
-    public static final int ADDITIONAL_REQ_PARAMS_MAX_SIZE = 200;
-
-    /** Set of known protocol GET params not to be stored into {@link #additionalReqParams} */
-    private static final Set<String> KNOWN_REQ_PARAMS = new HashSet<>();
-    static {
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLIENT_ID_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REDIRECT_URI_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.STATE_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.SCOPE_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.LOGIN_HINT_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.PROMPT_PARAM);
-        KNOWN_REQ_PARAMS.add(AdapterConstants.KC_IDP_HINT);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.NONCE_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.MAX_AGE_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
-        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
-    }
 
     private enum Action {
         REGISTER, CODE, FORGOT_CREDENTIALS
@@ -116,19 +80,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
     private OIDCResponseType parsedResponseType;
     private OIDCResponseMode parsedResponseMode;
 
-    private String clientId;
+    private AuthorizationEndpointRequest request;
     private String redirectUri;
-    private String redirectUriParam;
-    private String responseType;
-    private String responseMode;
-    private String state;
-    private String scope;
-    private String loginHint;
-    private String prompt;
-    private String nonce;
-    private String maxAge;
-    private String idpHint;
-    protected Map<String, String> additionalReqParams = new HashMap<>();
 
     public AuthorizationEndpoint(RealmModel realm, EventBuilder event) {
         super(realm, event);
@@ -139,34 +92,25 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
     public Response build() {
         MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
 
-        clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
-        responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
-        responseMode = params.getFirst(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
-        redirectUriParam = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
-        state = params.getFirst(OIDCLoginProtocol.STATE_PARAM);
-        scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM);
-        loginHint = params.getFirst(OIDCLoginProtocol.LOGIN_HINT_PARAM);
-        prompt = params.getFirst(OIDCLoginProtocol.PROMPT_PARAM);
-        idpHint = params.getFirst(AdapterConstants.KC_IDP_HINT);
-        nonce = params.getFirst(OIDCLoginProtocol.NONCE_PARAM);
-        maxAge = params.getFirst(OIDCLoginProtocol.MAX_AGE_PARAM);
-
-        extractAdditionalReqParams(params);
+        String clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
 
         checkSsl();
         checkRealm();
-        checkClient();
+        checkClient(clientId);
+
+        request = AuthorizationEndpointRequestParserProcessor.parseRequest(event, session, client, params);
+
         checkRedirectUri();
         Response errorResponse = checkResponseType();
         if (errorResponse != null) {
             return errorResponse;
         }
 
-        if (!TokenUtil.isOIDCRequest(scope)) {
+        if (!TokenUtil.isOIDCRequest(request.getScope())) {
             logger.oidcScopeMissing();
         }
 
-        errorResponse = checkOIDCParams(params);
+        errorResponse = checkOIDCParams();
         if (errorResponse != null) {
             return errorResponse;
         }
@@ -186,27 +130,6 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         throw new RuntimeException("Unknown action " + action);
     }
 
-    protected void extractAdditionalReqParams(MultivaluedMap<String, String> params) {
-        for (String paramName : params.keySet()) {
-            if (!KNOWN_REQ_PARAMS.contains(paramName)) {
-                String value = params.getFirst(paramName);
-                if (value != null && value.trim().isEmpty()) {
-                    value = null;
-                }
-                if (value != null && value.length() <= ADDITIONAL_REQ_PARAMS_MAX_SIZE) {
-                    if (additionalReqParams.size() >= ADDITIONAL_REQ_PARAMS_MAX_MUMBER) {
-                        logger.debug("Maximal number of additional OIDC params (" + ADDITIONAL_REQ_PARAMS_MAX_MUMBER + ") exceeded, ignoring rest of them!");
-                        break;
-                    }
-                    additionalReqParams.put(paramName, value);
-                } else {
-                    logger.debug("OIDC Additional param " + paramName + " ignored because value is empty or longer than " + ADDITIONAL_REQ_PARAMS_MAX_SIZE);
-                }
-            }
-
-        }
-    }
-
     public AuthorizationEndpoint register() {
         event.event(EventType.REGISTER);
         action = Action.REGISTER;
@@ -243,7 +166,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         }
     }
 
-    private void checkClient() {
+    private void checkClient(String clientId) {
         if (clientId == null) {
             event.error(Errors.INVALID_REQUEST);
             throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
@@ -271,6 +194,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
     }
 
     private Response checkResponseType() {
+        String responseType = request.getResponseType();
+
         if (responseType == null) {
             logger.missingParameter(OAuth2Constants.RESPONSE_TYPE);
             event.error(Errors.INVALID_REQUEST);
@@ -292,7 +217,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
 
         OIDCResponseMode parsedResponseMode = null;
         try {
-            parsedResponseMode = OIDCResponseMode.parse(responseMode, parsedResponseType);
+            parsedResponseMode = OIDCResponseMode.parse(request.getResponseMode(), parsedResponseType);
         } catch (IllegalArgumentException iae) {
             logger.invalidParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
             event.error(Errors.INVALID_REQUEST);
@@ -325,20 +250,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         return null;
     }
 
-    private Response checkOIDCParams(MultivaluedMap<String, String> params) {
-        if (params.getFirst(OIDCLoginProtocol.REQUEST_PARAM) != null) {
-            logger.unsupportedParameter(OIDCLoginProtocol.REQUEST_PARAM);
-            event.error(Errors.INVALID_REQUEST);
-            return redirectErrorToClient(parsedResponseMode, OAuthErrorException.REQUEST_NOT_SUPPORTED, null);
-        }
-
-        if (params.getFirst(OIDCLoginProtocol.REQUEST_URI_PARAM) != null) {
-            logger.unsupportedParameter(OIDCLoginProtocol.REQUEST_URI_PARAM);
-            event.error(Errors.INVALID_REQUEST);
-            return redirectErrorToClient(parsedResponseMode, OAuthErrorException.REQUEST_URI_NOT_SUPPORTED, null);
-        }
-
-        if (parsedResponseType.isImplicitOrHybridFlow() && nonce == null) {
+    private Response checkOIDCParams() {
+        if (parsedResponseType.isImplicitOrHybridFlow() && request.getNonce() == null) {
             logger.missingParameter(OIDCLoginProtocol.NONCE_PARAM);
             event.error(Errors.INVALID_REQUEST);
             return redirectErrorToClient(parsedResponseMode, OAuthErrorException.INVALID_REQUEST, "Missing parameter: nonce");
@@ -355,14 +268,16 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
             errorResponseBuilder.addParam(OAuth2Constants.ERROR_DESCRIPTION, errorDescription);
         }
 
-        if (state != null) {
-            errorResponseBuilder.addParam(OAuth2Constants.STATE, state);
+        if (request.getState() != null) {
+            errorResponseBuilder.addParam(OAuth2Constants.STATE, request.getState());
         }
 
         return errorResponseBuilder.build();
     }
 
     private void checkRedirectUri() {
+        String redirectUriParam = request.getRedirectUriParam();
+
         event.detail(Details.REDIRECT_URI, redirectUriParam);
 
         redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client);
@@ -377,27 +292,28 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
         clientSession.setRedirectUri(redirectUri);
         clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
-        clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType);
-        clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam);
+        clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, request.getResponseType());
+        clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, request.getRedirectUriParam());
         clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
 
-        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
-        if (nonce != null) clientSession.setNote(OIDCLoginProtocol.NONCE_PARAM, nonce);
-        if (maxAge != null) clientSession.setNote(OIDCLoginProtocol.MAX_AGE_PARAM, maxAge);
-        if (scope != null) clientSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
-        if (loginHint != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
-        if (prompt != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, prompt);
-        if (idpHint != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, idpHint);
-        if (responseMode != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, responseMode);
-
-        if (additionalReqParams != null) {
-            for (String paramName : additionalReqParams.keySet()) {
-                clientSession.setNote(CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, additionalReqParams.get(paramName));
+        if (request.getState() != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, request.getState());
+        if (request.getNonce() != null) clientSession.setNote(OIDCLoginProtocol.NONCE_PARAM, request.getNonce());
+        if (request.getMaxAge() != null) clientSession.setNote(OIDCLoginProtocol.MAX_AGE_PARAM, String.valueOf(request.getMaxAge()));
+        if (request.getScope() != null) clientSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, request.getScope());
+        if (request.getLoginHint() != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, request.getLoginHint());
+        if (request.getPrompt() != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, request.getPrompt());
+        if (request.getIdpHint() != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint());
+        if (request.getResponseMode() != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
+
+        if (request.getAdditionalReqParams() != null) {
+            for (String paramName : request.getAdditionalReqParams().keySet()) {
+                clientSession.setNote(CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, request.getAdditionalReqParams().get(paramName));
             }
         }
     }
 
     private Response buildAuthorizationCodeAuthorizationResponse() {
+        String idpHint = request.getIdpHint();
 
         if (idpHint != null && !"".equals(idpHint)) {
             IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(idpHint);
@@ -413,7 +329,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         this.event.event(EventType.LOGIN);
         clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);
 
-        return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), TokenUtil.hasPrompt(prompt, OIDCLoginProtocol.PROMPT_VALUE_NONE), false);
+        return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), TokenUtil.hasPrompt(request.getPrompt(), OIDCLoginProtocol.PROMPT_VALUE_NONE), false);
     }
 
     private Response buildRegister() {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java
new file mode 100644
index 0000000..998a58c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.protocol.oidc.endpoints.request;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthorizationEndpointRequest {
+
+    String clientId;
+    String redirectUriParam;
+    String responseType;
+    String responseMode;
+    String state;
+    String scope;
+    String loginHint;
+    String prompt;
+    String nonce;
+    Integer maxAge;
+    String idpHint;
+    Map<String, String> additionalReqParams = new HashMap<>();
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public String getRedirectUriParam() {
+        return redirectUriParam;
+    }
+
+    public String getResponseType() {
+        return responseType;
+    }
+
+    public String getResponseMode() {
+        return responseMode;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public String getScope() {
+        return scope;
+    }
+
+    public String getLoginHint() {
+        return loginHint;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public String getNonce() {
+        return nonce;
+    }
+
+    public Integer getMaxAge() {
+        return maxAge;
+    }
+
+    public String getIdpHint() {
+        return idpHint;
+    }
+
+    public Map<String, String> getAdditionalReqParams() {
+        return additionalReqParams;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java
new file mode 100644
index 0000000..8db219c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.protocol.oidc.endpoints.request;
+
+import java.io.InputStream;
+import java.util.Map;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.keycloak.common.util.StreamUtil;
+import org.keycloak.connections.httpclient.HttpClientProvider;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.services.ErrorPageException;
+import org.keycloak.services.ServicesLogger;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthorizationEndpointRequestParserProcessor {
+
+    private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+
+    public static AuthorizationEndpointRequest parseRequest(EventBuilder event, KeycloakSession session, ClientModel client, MultivaluedMap<String, String> requestParams) {
+        try {
+            AuthorizationEndpointRequest request = new AuthorizationEndpointRequest();
+
+            new AuthzEndpointQueryStringParser(requestParams).parseRequest(request);
+
+            String requestParam = requestParams.getFirst(OIDCLoginProtocol.REQUEST_PARAM);
+            String requestUriParam = requestParams.getFirst(OIDCLoginProtocol.REQUEST_URI_PARAM);
+
+            if (requestParam != null && requestUriParam != null) {
+                throw new RuntimeException("Illegal to use both 'request' and 'request_uri' parameters together");
+            }
+
+            if (requestParam != null) {
+                new AuthzEndpointRequestObjectParser(requestParam, client).parseRequest(request);
+            } else if (requestUriParam != null) {
+                InputStream is = session.getProvider(HttpClientProvider.class).get(requestUriParam);
+                String retrievedRequest = StreamUtil.readString(is);
+
+                new AuthzEndpointRequestObjectParser(retrievedRequest, client).parseRequest(request);
+            }
+
+            return request;
+
+        } catch (Exception e) {
+            logger.invalidRequest(e);
+            event.error(Errors.INVALID_REQUEST);
+            throw new ErrorPageException(session, Messages.INVALID_REQUEST);
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java
new file mode 100644
index 0000000..8384fdc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.protocol.oidc.endpoints.request;
+
+import java.util.Set;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * Parse the parameters from request queryString
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class AuthzEndpointQueryStringParser extends AuthzEndpointRequestParser {
+
+    private final MultivaluedMap<String, String> requestParams;
+
+    public AuthzEndpointQueryStringParser(MultivaluedMap<String, String> requestParams) {
+        this.requestParams = requestParams;
+    }
+
+    @Override
+    protected String getParameter(String paramName) {
+        return requestParams.getFirst(paramName);
+    }
+
+    @Override
+    protected Integer getIntParameter(String paramName) {
+        String paramVal = requestParams.getFirst(paramName);
+        return paramVal==null ? null : Integer.parseInt(paramVal);
+    }
+
+    @Override
+    protected Set<String> keySet() {
+        return requestParams.keySet();
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java
new file mode 100644
index 0000000..658a2c0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.protocol.oidc.endpoints.request;
+
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSHeader;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.services.util.CertificateInfoHelper;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * Parse the parameters from OIDC "request" object
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
+
+    private final Map<String, Object> requestParams;
+
+    public AuthzEndpointRequestObjectParser(String requestObject, ClientModel client) throws Exception {
+        JWSInput input = new JWSInput(requestObject);
+        JWSHeader header = input.getHeader();
+
+        Algorithm requestedSignatureAlgorithm = OIDCAdvancedConfigWrapper.fromClientModel(client).getRequestObjectSignatureAlg();
+
+        if (requestedSignatureAlgorithm != null && requestedSignatureAlgorithm != header.getAlgorithm()) {
+            throw new RuntimeException("Request object signed with different algorithm than client requested algorithm");
+        }
+
+        if (header.getAlgorithm() == Algorithm.none) {
+            this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class);
+        } else if (header.getAlgorithm() == Algorithm.RS256) {
+            PublicKey clientPublicKey = CertificateInfoHelper.getSignatureValidationKey(client, JWTClientAuthenticator.ATTR_PREFIX);
+            boolean verified = RSAProvider.verify(input, clientPublicKey);
+            if (!verified) {
+                throw new RuntimeException("Failed to verify signature on 'request' object");
+            }
+
+            this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class);
+        } else {
+            throw new RuntimeException("Unsupported JWA algorithm used for signed request");
+        }
+    }
+
+    @Override
+    protected String getParameter(String paramName) {
+        Object val = this.requestParams.get(paramName);
+        return val==null ? null : val.toString();
+    }
+
+    @Override
+    protected Integer getIntParameter(String paramName) {
+        Object val = this.requestParams.get(paramName);
+        return val==null ? null : Integer.parseInt(getParameter(paramName));
+    }
+
+    @Override
+    protected Set<String> keySet() {
+        return requestParams.keySet();
+    }
+
+    static class TypedHashMap extends HashMap<String, Object> {
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java
new file mode 100644
index 0000000..e322d4b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.protocol.oidc.endpoints.request;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.keycloak.constants.AdapterConstants;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.services.ServicesLogger;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+abstract class AuthzEndpointRequestParser {
+
+    private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+
+    /**
+     * Max number of additional req params copied into client session note to prevent DoS attacks
+     *
+     */
+    public static final int ADDITIONAL_REQ_PARAMS_MAX_MUMBER = 5;
+
+    /**
+     * Max size of additional req param value copied into client session note to prevent DoS attacks - params with longer value are ignored
+     *
+     */
+    public static final int ADDITIONAL_REQ_PARAMS_MAX_SIZE = 200;
+
+    /** Set of known protocol GET params not to be stored into additionalReqParams} */
+    private static final Set<String> KNOWN_REQ_PARAMS = new HashSet<>();
+    static {
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLIENT_ID_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REDIRECT_URI_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.STATE_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.SCOPE_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.LOGIN_HINT_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.PROMPT_PARAM);
+        KNOWN_REQ_PARAMS.add(AdapterConstants.KC_IDP_HINT);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.NONCE_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.MAX_AGE_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
+    }
+
+
+    public void parseRequest(AuthorizationEndpointRequest request) {
+        String clientId = getParameter(OIDCLoginProtocol.CLIENT_ID_PARAM);
+
+        if (request.clientId != null && !request.clientId.equals(clientId)) {
+            throw new IllegalArgumentException("The client_id parameter doesn't match the one from OIDC 'request' or 'request_uri'");
+        }
+
+        request.clientId = clientId;
+        request.responseType = replaceIfNotNull(request.responseType, getParameter(OIDCLoginProtocol.RESPONSE_TYPE_PARAM));
+        request.responseMode = replaceIfNotNull(request.responseMode, getParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM));
+        request.redirectUriParam = replaceIfNotNull(request.redirectUriParam, getParameter(OIDCLoginProtocol.REDIRECT_URI_PARAM));
+        request.state = replaceIfNotNull(request.state, getParameter(OIDCLoginProtocol.STATE_PARAM));
+        request.scope = replaceIfNotNull(request.scope, getParameter(OIDCLoginProtocol.SCOPE_PARAM));
+        request.loginHint = replaceIfNotNull(request.loginHint, getParameter(OIDCLoginProtocol.LOGIN_HINT_PARAM));
+        request.prompt = replaceIfNotNull(request.prompt, getParameter(OIDCLoginProtocol.PROMPT_PARAM));
+        request.idpHint = replaceIfNotNull(request.idpHint, getParameter(AdapterConstants.KC_IDP_HINT));
+        request.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_PARAM));
+        request.maxAge = replaceIfNotNull(request.maxAge, getIntParameter(OIDCLoginProtocol.MAX_AGE_PARAM));
+
+        extractAdditionalReqParams(request.additionalReqParams);
+    }
+
+
+    protected void extractAdditionalReqParams(Map<String, String> additionalReqParams) {
+        for (String paramName : keySet()) {
+            if (!KNOWN_REQ_PARAMS.contains(paramName)) {
+                String value = getParameter(paramName);
+                if (value != null && value.trim().isEmpty()) {
+                    value = null;
+                }
+                if (value != null && value.length() <= ADDITIONAL_REQ_PARAMS_MAX_SIZE) {
+                    if (additionalReqParams.size() >= ADDITIONAL_REQ_PARAMS_MAX_MUMBER) {
+                        logger.debug("Maximal number of additional OIDC params (" + ADDITIONAL_REQ_PARAMS_MAX_MUMBER + ") exceeded, ignoring rest of them!");
+                        break;
+                    }
+                    additionalReqParams.put(paramName, value);
+                } else {
+                    logger.debug("OIDC Additional param " + paramName + " ignored because value is empty or longer than " + ADDITIONAL_REQ_PARAMS_MAX_SIZE);
+                }
+            }
+
+        }
+    }
+
+    protected <T> T replaceIfNotNull(T previousVal, T newVal) {
+        return newVal==null ? previousVal : newVal;
+    }
+
+
+    protected abstract String getParameter(String paramName);
+
+    protected abstract Integer getIntParameter(String paramName);
+
+    protected abstract Set<String> keySet();
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
index 37fe204..3233690 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
@@ -30,6 +30,8 @@ public class OIDCAdvancedConfigWrapper {
 
     private static final String USER_INFO_RESPONSE_SIGNATURE_ALG = "user.info.response.signature.alg";
 
+    private static final String REQUEST_OBJECT_SIGNATURE_ALG = "request.object.signature.alg";
+
     private final ClientModel clientModel;
     private final ClientRepresentation clientRep;
 
@@ -62,6 +64,16 @@ public class OIDCAdvancedConfigWrapper {
         return getUserInfoSignedResponseAlg() != null;
     }
 
+    public Algorithm getRequestObjectSignatureAlg() {
+        String alg = getAttribute(REQUEST_OBJECT_SIGNATURE_ALG);
+        return alg==null ? null : Enum.valueOf(Algorithm.class, alg);
+    }
+
+    public void setRequestObjectSignatureAlg(Algorithm alg) {
+        String algStr = alg==null ? null : alg.toString();
+        setAttribute(REQUEST_OBJECT_SIGNATURE_ALG, algStr);
+    }
+
 
     private String getAttribute(String attrKey) {
         if (clientModel != null) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index 3c4c3aa..c263405 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -66,6 +66,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
     public static final String REQUEST_PARAM = "request";
     public static final String REQUEST_URI_PARAM = "request_uri";
     public static final String UI_LOCALES_PARAM = OAuth2Constants.UI_LOCALES_PARAM;
+    public static final String CLAIMS_PARAM = "claims";
 
     public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
     public static final String ISSUER = "iss";
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
index dfca144..800630c 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
@@ -50,6 +50,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
 
     public static final List<String> DEFAULT_USER_INFO_SIGNING_ALG_VALUES_SUPPORTED  = list(Algorithm.RS256.toString());
 
+    public static final List<String> DEFAULT_REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED  = list(Algorithm.none.toString(), Algorithm.RS256.toString());
+
     public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS);
 
     public static final List<String> DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, OIDCResponseType.TOKEN, "id_token token", "code id_token", "code token", "code id_token token");
@@ -93,6 +95,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
 
         config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
         config.setUserInfoSigningAlgValuesSupported(DEFAULT_USER_INFO_SIGNING_ALG_VALUES_SUPPORTED);
+        config.setRequestObjectSigningAlgValuesSupported(DEFAULT_REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED);
         config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
         config.setSubjectTypesSupported(DEFAULT_SUBJECT_TYPES_SUPPORTED);
         config.setResponseModesSupported(DEFAULT_RESPONSE_MODES_SUPPORTED);
@@ -107,8 +110,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
 
         config.setScopesSupported(SCOPES_SUPPORTED);
 
-        config.setRequestParameterSupported(false);
-        config.setRequestUriParameterSupported(false);
+        config.setRequestParameterSupported(true);
+        config.setRequestUriParameterSupported(true);
 
         return config;
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
index ee5241b..181e0d2 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
@@ -67,6 +67,9 @@ public class OIDCConfigurationRepresentation {
     @JsonProperty("userinfo_signing_alg_values_supported")
     private List<String> userInfoSigningAlgValuesSupported;
 
+    @JsonProperty("request_object_signing_alg_values_supported")
+    private List<String> requestObjectSigningAlgValuesSupported;
+
     @JsonProperty("response_modes_supported")
     private List<String> responseModesSupported;
 
@@ -195,6 +198,14 @@ public class OIDCConfigurationRepresentation {
         this.userInfoSigningAlgValuesSupported = userInfoSigningAlgValuesSupported;
     }
 
+    public List<String> getRequestObjectSigningAlgValuesSupported() {
+        return requestObjectSigningAlgValuesSupported;
+    }
+
+    public void setRequestObjectSigningAlgValuesSupported(List<String> requestObjectSigningAlgValuesSupported) {
+        this.requestObjectSigningAlgValuesSupported = requestObjectSigningAlgValuesSupported;
+    }
+
     public List<String> getResponseModesSupported() {
         return responseModesSupported;
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java
index c856a81..d8f7fe7 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java
@@ -18,12 +18,15 @@
 package org.keycloak.protocol.oidc.utils;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.security.PublicKey;
 
-import org.keycloak.broker.provider.util.SimpleHttp;
+import org.keycloak.common.util.StreamUtil;
+import org.keycloak.connections.httpclient.HttpClientProvider;
 import org.keycloak.jose.jwk.JSONWebKeySet;
 import org.keycloak.jose.jwk.JWK;
 import org.keycloak.jose.jwk.JWKParser;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.util.JsonSerialization;
 
 /**
@@ -31,8 +34,9 @@ import org.keycloak.util.JsonSerialization;
  */
 public class JWKSUtils {
 
-    public static JSONWebKeySet sendJwksRequest(String jwksURI) throws IOException {
-        String keySetString = SimpleHttp.doGet(jwksURI).asString();
+    public static JSONWebKeySet sendJwksRequest(KeycloakSession session, String jwksURI) throws IOException {
+        InputStream is = session.getProvider(HttpClientProvider.class).get(jwksURI);
+        String keySetString = StreamUtil.readString(is);
         return JsonSerialization.readValue(keySetString, JSONWebKeySet.class);
     }
 
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
index a95cc09..e1939ef 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -89,14 +89,12 @@ public class DescriptionConverter {
         }
         client.setClientAuthenticatorType(clientAuthFactory.getId());
 
-        // Externalize to ClientAuthenticator itself?
-        if (authMethod != null && authMethod.equals(OIDCLoginProtocol.PRIVATE_KEY_JWT)) {
-
-            PublicKey publicKey = retrievePublicKey(clientOIDC);
-            if (publicKey == null) {
-                throw new ClientRegistrationException("Didn't find key of supported keyType for use " + JWK.Use.SIG.asString());
-            }
+        PublicKey publicKey = retrievePublicKey(session, clientOIDC);
+        if (authMethod != null && authMethod.equals(OIDCLoginProtocol.PRIVATE_KEY_JWT) && publicKey == null) {
+            throw new ClientRegistrationException("Didn't find key of supported keyType for use " + JWK.Use.SIG.asString());
+        }
 
+        if (publicKey != null) {
             String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
 
             CertificateRepresentation rep = new CertificateRepresentation();
@@ -104,20 +102,24 @@ public class DescriptionConverter {
             CertificateInfoHelper.updateClientRepresentationCertificateInfo(client, rep, JWTClientAuthenticator.ATTR_PREFIX);
         }
 
+        OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
         if (clientOIDC.getUserinfoSignedResponseAlg() != null) {
-            String userInfoSignedResponseAlg = clientOIDC.getUserinfoSignedResponseAlg();
-            Algorithm algorithm = Enum.valueOf(Algorithm.class, userInfoSignedResponseAlg);
+            Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getUserinfoSignedResponseAlg());
+            configWrapper.setUserInfoSignedResponseAlg(algorithm);
+        }
 
-            OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUserInfoSignedResponseAlg(algorithm);
+        if (clientOIDC.getRequestObjectSigningAlg() != null) {
+            Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getRequestObjectSigningAlg());
+            configWrapper.setRequestObjectSignatureAlg(algorithm);
         }
 
         return client;
     }
 
 
-    private static PublicKey retrievePublicKey(OIDCClientRepresentation clientOIDC) {
+    private static PublicKey retrievePublicKey(KeycloakSession session, OIDCClientRepresentation clientOIDC) {
         if (clientOIDC.getJwksUri() == null && clientOIDC.getJwks() == null) {
-            throw new ClientRegistrationException("Requested client authentication method '%s' but jwks_uri nor jwks were available in config");
+            return null;
         }
 
         if (clientOIDC.getJwksUri() != null && clientOIDC.getJwks() != null) {
@@ -129,7 +131,7 @@ public class DescriptionConverter {
             keySet = clientOIDC.getJwks();
         } else {
             try {
-                keySet = JWKSUtils.sendJwksRequest(clientOIDC.getJwksUri());
+                keySet = JWKSUtils.sendJwksRequest(session, clientOIDC.getJwksUri());
             } catch (IOException ioe) {
                 throw new ClientRegistrationException("Failed to send JWKS request to specified jwks_uri", ioe);
             }
@@ -166,6 +168,9 @@ public class DescriptionConverter {
         if (config.isUserInfoSignatureRequired()) {
             response.setUserinfoSignedResponseAlg(config.getUserInfoSignedResponseAlg().toString());
         }
+        if (config.getRequestObjectSignatureAlg() != null) {
+            response.setRequestObjectSigningAlg(config.getRequestObjectSignatureAlg().toString());
+        }
 
         return response;
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
index 958a849..8e7c9ac 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
@@ -116,7 +116,7 @@ public class IdentityProvidersResource {
         InputPart file = formDataMap.get("file").get(0);
         InputStream inputStream = file.getBody(InputStream.class, null);
         IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
-        Map<String, String> config = providerFactory.parseConfig(inputStream);
+        Map<String, String> config = providerFactory.parseConfig(session, inputStream);
         return config;
     }
 
@@ -143,7 +143,7 @@ public class IdentityProvidersResource {
         try {
             IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
             Map<String, String> config;
-            config = providerFactory.parseConfig(inputStream);
+            config = providerFactory.parseConfig(session, inputStream);
             return config;
         } finally {
             try {
diff --git a/services/src/main/java/org/keycloak/services/ServicesLogger.java b/services/src/main/java/org/keycloak/services/ServicesLogger.java
index de7eef7..af6c4c1 100644
--- a/services/src/main/java/org/keycloak/services/ServicesLogger.java
+++ b/services/src/main/java/org/keycloak/services/ServicesLogger.java
@@ -430,4 +430,8 @@ public interface ServicesLogger extends BasicLogger {
     @Message(id=96, value="Not found JWK of supported keyType under jwks_uri for usage: %s")
     void supportedJwkNotFound(String usage);
 
+    @LogMessage(level = WARN)
+    @Message(id=97, value="Invalid request")
+    void invalidRequest(@Cause Throwable t);
+
 }
diff --git a/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java b/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java
index b309927..359d28d 100644
--- a/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java
@@ -17,9 +17,18 @@
 
 package org.keycloak.services.util;
 
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
 import java.util.HashMap;
 
+import javax.ws.rs.core.Response;
+
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.ClientAuthenticationFlowContext;
+import org.keycloak.authentication.authenticators.client.ClientAuthUtil;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.idm.CertificateRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 
@@ -34,6 +43,8 @@ public class CertificateInfoHelper {
     public static final String PUBLIC_KEY = "public.key";
 
 
+    // CLIENT MODEL METHODS
+
     public static CertificateRepresentation getCertificateFromClient(ClientModel client, String attributePrefix) {
         String privateKeyAttribute = attributePrefix + "." + PRIVATE_KEY;
         String certificateAttribute = attributePrefix + "." + X509CERTIFICATE;
@@ -75,6 +86,32 @@ public class CertificateInfoHelper {
     }
 
 
+    public static PublicKey getSignatureValidationKey(ClientModel client, String attributePrefix) throws ModelException {
+        CertificateRepresentation certInfo = getCertificateFromClient(client, attributePrefix);
+
+        String encodedCertificate = certInfo.getCertificate();
+        String encodedPublicKey = certInfo.getPublicKey();
+
+        if (encodedCertificate == null && encodedPublicKey == null) {
+            throw new ModelException("Client doesn't have certificate or publicKey configured");
+        }
+
+        if (encodedCertificate != null && encodedPublicKey != null) {
+            throw new ModelException("Client has both publicKey and certificate configured");
+        }
+
+        // TODO: Caching of publicKeys / certificates, so it doesn't need to be always computed from pem. For performance reasons...
+        if (encodedCertificate != null) {
+            X509Certificate clientCert = KeycloakModelUtils.getCertificate(encodedCertificate);
+            return clientCert.getPublicKey();
+        } else {
+            return KeycloakModelUtils.getPublicKey(encodedPublicKey);
+        }
+    }
+
+
+    // CLIENT REPRESENTATION METHODS
+
     public static void updateClientRepresentationCertificateInfo(ClientRepresentation client, CertificateRepresentation rep, String attributePrefix) {
         String privateKeyAttribute = attributePrefix + "." + PRIVATE_KEY;
         String certificateAttribute = attributePrefix + "." + X509CERTIFICATE;
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java
new file mode 100644
index 0000000..4f9151c
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.rest.resource;
+
+import java.io.File;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.exportimport.ExportImportManager;
+import org.keycloak.models.KeycloakSession;
+
+import static org.keycloak.exportimport.ExportImportConfig.ACTION;
+import static org.keycloak.exportimport.ExportImportConfig.DEFAULT_USERS_PER_FILE;
+import static org.keycloak.exportimport.ExportImportConfig.DIR;
+import static org.keycloak.exportimport.ExportImportConfig.FILE;
+import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
+import static org.keycloak.exportimport.ExportImportConfig.REALM_NAME;
+import static org.keycloak.exportimport.ExportImportConfig.USERS_PER_FILE;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TestingExportImportResource {
+
+    private final KeycloakSession session;
+
+    public TestingExportImportResource(KeycloakSession session) {
+        this.session = session;
+    }
+
+    @GET
+    @Path("/run-import")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response runImport() {
+        new ExportImportManager(session).runImport();
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("/run-export")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response runExport() {
+        new ExportImportManager(session).runExport();
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("/get-users-per-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Integer getUsersPerFile() {
+        String usersPerFile = System.getProperty(USERS_PER_FILE, String.valueOf(DEFAULT_USERS_PER_FILE));
+        return Integer.parseInt(usersPerFile.trim());
+    }
+
+    @PUT
+    @Path("/set-users-per-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile) {
+        System.setProperty(USERS_PER_FILE, String.valueOf(usersPerFile));
+    }
+
+    @GET
+    @Path("/get-dir")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getDir() {
+        return System.getProperty(DIR);
+    }
+
+    @PUT
+    @Path("/set-dir")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public String setDir(@QueryParam("dir") String dir) {
+        return System.setProperty(DIR, dir);
+    }
+
+    @PUT
+    @Path("/export-import-provider")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider) {
+        System.setProperty(PROVIDER, exportImportProvider);
+    }
+
+    @PUT
+    @Path("/export-import-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setFile(@QueryParam("file") String file) {
+        System.setProperty(FILE, file);
+    }
+
+    @PUT
+    @Path("/export-import-action")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setAction(@QueryParam("exportImportAction") String exportImportAction) {
+        System.setProperty(ACTION, exportImportAction);
+    }
+
+    @PUT
+    @Path("/set-realm-name")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setRealmName(@QueryParam("realmName") String realmName) {
+        if (realmName != null && !realmName.isEmpty()) {
+            System.setProperty(REALM_NAME, realmName);
+        } else {
+            System.getProperties().remove(REALM_NAME);
+        }
+    }
+
+    @GET
+    @Path("/get-test-dir")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getExportImportTestDirectory() {
+        System.setProperty("project.build.directory", "target");
+        String absolutePath = new File(System.getProperty("project.build.directory", "target")).getAbsolutePath();
+        return absolutePath;
+    }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
new file mode 100644
index 0000000..6ea488f
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.rest.resource;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.BadRequestException;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.jose.jwk.JSONWebKeySet;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.jose.jwk.JWKBuilder;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TestingOIDCEndpointsApplicationResource {
+
+    public static final String PRIVATE_KEY = "privateKey";
+    public static final String PUBLIC_KEY = "publicKey";
+
+    private final TestApplicationResourceProviderFactory.OIDCClientData clientData;
+
+    public TestingOIDCEndpointsApplicationResource(TestApplicationResourceProviderFactory.OIDCClientData oidcClientData) {
+        this.clientData = oidcClientData;
+    }
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/generate-keys")
+    @NoCache
+    public Map<String, String> generateKeys() {
+        try {
+            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+            generator.initialize(2048);
+            clientData.setSigningKeyPair(generator.generateKeyPair());
+        } catch (NoSuchAlgorithmException e) {
+            throw new BadRequestException("Error generating signing keypair", e);
+        }
+
+        String privateKeyPem = KeycloakModelUtils.getPemFromKey(clientData.getSigningKeyPair().getPrivate());
+        String publicKeyPem = KeycloakModelUtils.getPemFromKey(clientData.getSigningKeyPair().getPublic());
+
+        Map<String, String> res = new HashMap<>();
+        res.put(PRIVATE_KEY, privateKeyPem);
+        res.put(PUBLIC_KEY, publicKeyPem);
+        return res;
+    }
+
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/get-jwks")
+    @NoCache
+    public JSONWebKeySet getJwks() {
+        JSONWebKeySet keySet = new JSONWebKeySet();
+
+        if (clientData.getSigningKeyPair() == null) {
+            keySet.setKeys(new JWK[] {});
+        } else {
+            keySet.setKeys(new JWK[] { JWKBuilder.create().rs256(clientData.getSigningKeyPair().getPublic()) });
+        }
+
+        return keySet;
+    }
+
+
+    @GET
+    @Path("/set-oidc-request")
+    @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
+    @NoCache
+    public void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId,
+                               @QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge,
+                               @QueryParam("jwaAlgorithm") String jwaAlgorithm) {
+        Map<String, Object> oidcRequest = new HashMap<>();
+        oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, clientId);
+        oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
+        oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
+        if (maxAge != null) {
+            oidcRequest.put(OIDCLoginProtocol.MAX_AGE_PARAM, Integer.parseInt(maxAge));
+        }
+
+        Algorithm alg = Enum.valueOf(Algorithm.class, jwaAlgorithm);
+        if (alg == Algorithm.none) {
+            clientData.setOidcRequest(new JWSBuilder().jsonContent(oidcRequest).none());
+        } else if (alg == Algorithm.RS256) {
+            if (clientData.getSigningKeyPair() == null) {
+                throw new BadRequestException("Requested RS256, but signing key not set");
+            }
+
+            PrivateKey privateKey = clientData.getSigningKeyPair().getPrivate();
+            clientData.setOidcRequest(new JWSBuilder().jsonContent(oidcRequest).rsa256(privateKey));
+        } else {
+            throw new BadRequestException("Unknown argument: " + jwaAlgorithm);
+        }
+    }
+
+
+    @GET
+    @Path("/get-oidc-request")
+    @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
+    @NoCache
+    public String getOIDCRequest() {
+        return clientData.getOidcRequest();
+    }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java
index bc63c99..5f392a0 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java
@@ -27,6 +27,8 @@ import org.keycloak.representations.adapters.action.PushNotBeforeAction;
 import org.keycloak.representations.adapters.action.TestAvailabilityAction;
 import org.keycloak.services.resource.RealmResourceProvider;
 import org.keycloak.services.resources.RealmsResource;
+import org.keycloak.testsuite.rest.resource.TestingExportImportResource;
+import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -53,14 +55,16 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
     private final BlockingQueue<LogoutAction> adminLogoutActions;
     private final BlockingQueue<PushNotBeforeAction> adminPushNotBeforeActions;
     private final BlockingQueue<TestAvailabilityAction> adminTestAvailabilityAction;
+    private final TestApplicationResourceProviderFactory.OIDCClientData oidcClientData;
 
     public TestApplicationResourceProvider(KeycloakSession session, BlockingQueue<LogoutAction> adminLogoutActions,
             BlockingQueue<PushNotBeforeAction> adminPushNotBeforeActions,
-            BlockingQueue<TestAvailabilityAction> adminTestAvailabilityAction) {
+            BlockingQueue<TestAvailabilityAction> adminTestAvailabilityAction, TestApplicationResourceProviderFactory.OIDCClientData oidcClientData) {
         this.session = session;
         this.adminLogoutActions = adminLogoutActions;
         this.adminPushNotBeforeActions = adminPushNotBeforeActions;
         this.adminTestAvailabilityAction = adminTestAvailabilityAction;
+        this.oidcClientData = oidcClientData;
     }
 
     @POST
@@ -164,6 +168,11 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
         return sb.toString();
     }
 
+    @Path("/oidc-client-endpoints")
+    public TestingOIDCEndpointsApplicationResource getTestingOIDCClientEndpoints() {
+        return new TestingOIDCEndpointsApplicationResource(oidcClientData);
+    }
+
     @Override
     public Object getResource() {
         return this;
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java
index 98ca2ba..6bd7dc2 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java
@@ -28,6 +28,7 @@ import org.keycloak.representations.adapters.action.TestAvailabilityAction;
 import org.keycloak.services.resource.RealmResourceProvider;
 import org.keycloak.services.resource.RealmResourceProviderFactory;
 
+import java.security.KeyPair;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingDeque;
 
@@ -40,9 +41,11 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv
     private BlockingQueue<PushNotBeforeAction> pushNotBeforeActions = new LinkedBlockingDeque<>();
     private BlockingQueue<TestAvailabilityAction> testAvailabilityActions = new LinkedBlockingDeque<>();
 
+    private final OIDCClientData oidcClientData = new OIDCClientData();
+
     @Override
     public RealmResourceProvider create(KeycloakSession session) {
-        return new TestApplicationResourceProvider(session, adminLogoutActions, pushNotBeforeActions, testAvailabilityActions);
+        return new TestApplicationResourceProvider(session, adminLogoutActions, pushNotBeforeActions, testAvailabilityActions, oidcClientData);
     }
 
     @Override
@@ -62,4 +65,26 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv
         return "app";
     }
 
+
+    public static class OIDCClientData {
+
+        private KeyPair signingKeyPair;
+        private String oidcRequest;
+
+        public KeyPair getSigningKeyPair() {
+            return signingKeyPair;
+        }
+
+        public void setSigningKeyPair(KeyPair signingKeyPair) {
+            this.signingKeyPair = signingKeyPair;
+        }
+
+        public String getOidcRequest() {
+            return oidcRequest;
+        }
+
+        public void setOidcRequest(String oidcRequest) {
+            this.oidcRequest = oidcRequest;
+        }
+    }
 }
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index b658d35..2085cfa 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -23,15 +23,20 @@ import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import org.infinispan.Cache;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.common.util.Time;
 import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 import org.keycloak.events.Event;
 import org.keycloak.events.admin.AdminEvent;
 import org.keycloak.events.admin.ResourceType;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
 import org.keycloak.representations.idm.AdminEventRepresentation;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.services.managers.ClientSessionCode;
@@ -76,6 +81,7 @@ import org.keycloak.models.UserProvider;
 import org.keycloak.representations.idm.AuthDetailsRepresentation;
 import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.rest.resource.TestingExportImportResource;
 
 import static org.keycloak.exportimport.ExportImportConfig.*;
 
@@ -565,22 +571,6 @@ public class TestingResourceProvider implements RealmResourceProvider {
     }
 
     @GET
-    @Path("/run-import")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response runImport() {
-        new ExportImportManager(session).runImport();
-        return Response.ok().build();
-    }
-
-    @GET
-    @Path("/run-export")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response runExport() {
-        new ExportImportManager(session).runExport();
-        return Response.ok().build();
-    }
-
-    @GET
     @Path("/valid-credentials")
     @Produces(MediaType.APPLICATION_JSON)
     public boolean validCredentials(@QueryParam("realmName") String realmName, @QueryParam("userName") String userName, @QueryParam("password") String password) {
@@ -647,83 +637,14 @@ public class TestingResourceProvider implements RealmResourceProvider {
         return ModelToRepresentation.toRepresentation(user);
     }
 
+    @Path("/export-import")
+    public TestingExportImportResource getExportImportResource() {
+        return new TestingExportImportResource(session);
+    }
+
     private RealmModel getRealmByName(String realmName) {
         RealmProvider realmProvider = session.getProvider(RealmProvider.class);
         return realmProvider.getRealmByName(realmName);
     }
 
-    @GET
-    @Path("/get-users-per-file")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Integer getUsersPerFile() {
-        String usersPerFile = System.getProperty(USERS_PER_FILE, String.valueOf(DEFAULT_USERS_PER_FILE));
-        return Integer.parseInt(usersPerFile.trim());
-    }
-
-    @PUT
-    @Path("/set-users-per-file")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile) {
-        System.setProperty(USERS_PER_FILE, String.valueOf(usersPerFile));
-    }
-
-    @GET
-    @Path("/get-dir")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public String getDir() {
-        return System.getProperty(DIR);
-    }
-
-    @PUT
-    @Path("/set-dir")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public String setDir(@QueryParam("dir") String dir) {
-        return System.setProperty(DIR, dir);
-    }
-
-    @PUT
-    @Path("/export-import-provider")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider) {
-        System.setProperty(PROVIDER, exportImportProvider);
-    }
-
-    @PUT
-    @Path("/export-import-file")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setFile(@QueryParam("file") String file) {
-        System.setProperty(FILE, file);
-    }
-
-    @PUT
-    @Path("/export-import-action")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setAction(@QueryParam("exportImportAction") String exportImportAction) {
-        System.setProperty(ACTION, exportImportAction);
-    }
-
-    @PUT
-    @Path("/set-realm-name")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setRealmName(@QueryParam("realmName") String realmName) {
-        if (realmName != null && !realmName.isEmpty()) {
-            System.setProperty(REALM_NAME, realmName);
-        } else {
-            System.getProperties().remove(REALM_NAME);
-        }
-    }
-
-    @GET
-    @Path("/get-test-dir")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public String getExportImportTestDirectory() {
-        System.setProperty("project.build.directory", "target");
-        String absolutePath = new File(System.getProperty("project.build.directory", "target")).getAbsolutePath();
-        return absolutePath;
-    }
-
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java
index 2efc26b..2d277fd 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java
@@ -20,7 +20,9 @@ package org.keycloak.testsuite.client.resources;
 import org.keycloak.representations.adapters.action.LogoutAction;
 import org.keycloak.representations.adapters.action.PushNotBeforeAction;
 import org.keycloak.representations.adapters.action.TestAvailabilityAction;
+import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
 
+import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
@@ -53,4 +55,6 @@ public interface TestApplicationResource {
     @Path("/clear-admin-actions")
     Response clearAdminActions();
 
+    @Path("/oidc-client-endpoints")
+    TestOIDCEndpointsApplicationResource oidcClientEndpoints();
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java
new file mode 100644
index 0000000..8c5f98b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.client.resources;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.keycloak.testsuite.util.OAuthClient;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TestApplicationResourceUrls {
+
+    private static UriBuilder oidcClientEndpoints() {
+        return UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)
+                .path(TestApplicationResource.class)
+                .path(TestApplicationResource.class, "oidcClientEndpoints");
+    }
+
+    public static String clientRequestUri() {
+        UriBuilder builder = oidcClientEndpoints()
+                .path(TestOIDCEndpointsApplicationResource.class, "getOIDCRequest");
+
+        return builder.build().toString();
+    }
+
+    public static String clientJwksUri() {
+        UriBuilder builder = oidcClientEndpoints()
+                .path(TestOIDCEndpointsApplicationResource.class, "getJwks");
+
+        return builder.build().toString();
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java
new file mode 100644
index 0000000..27fa360
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.client.resources;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface TestingExportImportResource {
+
+    @GET
+    @Path("/run-import")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response runImport();
+
+    @GET
+    @Path("/run-export")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response runExport();
+
+    @GET
+    @Path("/get-users-per-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Integer getUsersPerFile();
+
+    @PUT
+    @Path("/set-users-per-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile);
+
+    @GET
+    @Path("/get-dir")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getDir();
+
+    @PUT
+    @Path("/set-dir")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public String setDir(@QueryParam("dir") String dir);
+
+    @PUT
+    @Path("/export-import-provider")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider);
+
+    @PUT
+    @Path("/export-import-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setFile(@QueryParam("file") String file);
+
+    @PUT
+    @Path("/export-import-action")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setAction(@QueryParam("exportImportAction") String exportImportAction);
+
+    @PUT
+    @Path("/set-realm-name")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setRealmName(@QueryParam("realmName") String realmName);
+
+    @GET
+    @Path("/get-test-dir")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getExportImportTestDirectory();
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
index fcf5d83..0dbcd58 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
@@ -17,8 +17,8 @@
 
 package org.keycloak.testsuite.client.resources;
 
-import java.util.Date;
 import java.util.List;
+
 import org.keycloak.representations.idm.AdminEventRepresentation;
 import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
 import org.keycloak.representations.idm.EventRepresentation;
@@ -37,7 +37,6 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.util.Map;
 import org.jboss.resteasy.annotations.cache.NoCache;
-import org.keycloak.exportimport.ExportImportManager;
 
 /**
  * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@@ -205,16 +204,6 @@ public interface TestingResource {
     @Path("/update-pass-through-auth-state")
     @Produces(MediaType.APPLICATION_JSON)
     AuthenticatorState updateAuthenticator(AuthenticatorState state);
-
-    @GET
-    @Path("/run-import")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response runImport();
-
-    @GET
-    @Path("/run-export")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response runExport();
     
     @GET
     @Path("/valid-credentials")
@@ -250,53 +239,7 @@ public interface TestingResource {
     @Produces(MediaType.APPLICATION_JSON)
     public UserRepresentation getUserByServiceAccountClient(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId);
 
+    @Path("export-import")
+    TestingExportImportResource exportImport();
 
-    @GET
-    @Path("/get-users-per-file")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Integer getUsersPerFile();
-
-    @PUT
-    @Path("/set-users-per-file")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile);
-
-    @GET
-    @Path("/get-dir")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public String getDir();
-
-    @PUT
-    @Path("/set-dir")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public String setDir(@QueryParam("dir") String dir);
-
-    @PUT
-    @Path("/export-import-provider")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider);
-
-    @PUT
-    @Path("/export-import-file")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setFile(@QueryParam("file") String file);
-
-    @PUT
-    @Path("/export-import-action")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setAction(@QueryParam("exportImportAction") String exportImportAction);
-
-    @PUT
-    @Path("/set-realm-name")
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void setRealmName(@QueryParam("realmName") String realmName);
-
-    @GET
-    @Path("/get-test-dir")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Produces(MediaType.APPLICATION_JSON)
-    public String getExportImportTestDirectory();
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java
new file mode 100644
index 0000000..54d6c35
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.client.resources;
+
+import java.util.Map;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.keycloak.jose.jwk.JSONWebKeySet;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface TestOIDCEndpointsApplicationResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/generate-keys")
+    Map<String, String> generateKeys();
+
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/get-jwks")
+    JSONWebKeySet getJwks();
+
+
+    @GET
+    @Path("/set-oidc-request")
+    @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
+    void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId,
+                        @QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge,
+                        @QueryParam("jwaAlgorithm") String jwaAlgorithm);
+
+    @GET
+    @Path("/get-oidc-request")
+    @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
+    String getOIDCRequest();
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 586351d..2253828 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -105,6 +105,10 @@ public class OAuthClient {
 
     private String nonce;
 
+    private String request;
+
+    private String requestUri;
+
     private Map<String, PublicKey> publicKeys = new HashMap<>();
 
     public void init(Keycloak adminClient, WebDriver driver) {
@@ -121,6 +125,9 @@ public class OAuthClient {
         clientSessionState = null;
         clientSessionHost = null;
         maxAge = null;
+        nonce = null;
+        request = null;
+        requestUri = null;
     }
 
     public AuthorizationEndpointResponse doLogin(String username, String password) {
@@ -536,6 +543,12 @@ public class OAuthClient {
         if (maxAge != null) {
             b.queryParam(OIDCLoginProtocol.MAX_AGE_PARAM, maxAge);
         }
+        if (request != null) {
+            b.queryParam(OIDCLoginProtocol.REQUEST_PARAM, request);
+        }
+        if (requestUri != null) {
+            b.queryParam(OIDCLoginProtocol.REQUEST_URI_PARAM, requestUri);
+        }
         return b.build(realm).toString();
     }
 
@@ -644,6 +657,16 @@ public class OAuthClient {
         return this;
     }
 
+    public OAuthClient request(String request) {
+        this.request = request;
+        return this;
+    }
+
+    public OAuthClient requestUri(String requestUri) {
+        this.requestUri = requestUri;
+        return this;
+    }
+
     public String getRealm() {
         return realm;
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
index 608d7a7..19d6413 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
@@ -50,12 +50,16 @@ import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.oidc.OIDCClientRepresentation;
 import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
+import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
+import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
 import org.keycloak.testsuite.util.OAuthClient;
 import java.security.PrivateKey;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
@@ -236,8 +240,11 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
         clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
         clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.PRIVATE_KEY_JWT);
 
-        // Corresponds to PRIVATE_KEY
-        JSONWebKeySet keySet = loadJson(getClass().getResourceAsStream("/clientreg-test/jwks.json"), JSONWebKeySet.class);
+        // Generate keys for client
+        TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+        Map<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+
+        JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks();
         clientRep.setJwks(keySet);
 
         OIDCClientRepresentation response = reg.oidc().create(clientRep);
@@ -246,7 +253,7 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
         Assert.assertNull(response.getClientSecretExpiresAt());
 
         // Tries to authenticate client with privateKey JWT
-        String signedJwt = getClientSignedJWT(response.getClientId());
+        String signedJwt = getClientSignedJWT(response.getClientId(), generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY));
         OAuthClient.AccessTokenResponse accessTokenResponse = doClientCredentialsGrantRequest(signedJwt);
         Assert.assertEquals(200, accessTokenResponse.getStatusCode());
         AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
@@ -260,8 +267,11 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
         clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
         clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.PRIVATE_KEY_JWT);
 
-        // Use the realmKey for client authentication too
-        clientRep.setJwksUri(oauth.getCertsUrl(REALM_NAME));
+        // Generate keys for client
+        TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+        Map<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+
+        clientRep.setJwksUri(TestApplicationResourceUrls.clientJwksUri());
 
         OIDCClientRepresentation response = reg.oidc().create(clientRep);
         Assert.assertEquals(OIDCLoginProtocol.PRIVATE_KEY_JWT, response.getTokenEndpointAuthMethod());
@@ -269,7 +279,7 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
         Assert.assertNull(response.getClientSecretExpiresAt());
 
         // Tries to authenticate client with privateKey JWT
-        String signedJwt = getClientSignedJWT(response.getClientId());
+        String signedJwt = getClientSignedJWT(response.getClientId(), generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY));
         OAuthClient.AccessTokenResponse accessTokenResponse = doClientCredentialsGrantRequest(signedJwt);
         Assert.assertEquals(200, accessTokenResponse.getStatusCode());
         AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
@@ -280,24 +290,27 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
     public void testSignaturesRequired() throws Exception {
         OIDCClientRepresentation clientRep = createRep();
         clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString());
+        clientRep.setRequestObjectSigningAlg(Algorithm.RS256.toString());
 
         OIDCClientRepresentation response = reg.oidc().create(clientRep);
         Assert.assertEquals(Algorithm.RS256.toString(), response.getUserinfoSignedResponseAlg());
+        Assert.assertEquals(Algorithm.RS256.toString(), response.getRequestObjectSigningAlg());
         Assert.assertNotNull(response.getClientSecret());
 
         // Test Keycloak representation
         ClientRepresentation kcClient = getClient(response.getClientId());
         OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
         Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.RS256);
+        Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.RS256);
     }
 
 
     // Client auth with signedJWT - helper methods
 
-    private String getClientSignedJWT(String clientId) {
+    private String getClientSignedJWT(String clientId, String privateKeyPem) {
         String realmInfoUrl = KeycloakUriBuilder.fromUri(getAuthServerRoot()).path(ServiceUrlConstants.REALM_INFO_PATH).build(REALM_NAME).toString();
 
-        PrivateKey privateKey = KeycloakModelUtils.getPrivateKey(PRIVATE_KEY);
+        PrivateKey privateKey = KeycloakModelUtils.getPrivateKey(privateKeyPem);
 
         // Use token-endpoint as audience as OIDC conformance testsuite is using it too.
         JWTClientCredentialsProvider jwtProvider = new JWTClientCredentialsProvider() {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
index 6abaf97..43c6fa9 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
@@ -75,11 +75,11 @@ public class ExportImportTest extends AbstractExportImportTest {
 
     @Test
     public void testDirFullExportImport() throws Throwable {
-        testingClient.testing().setProvider(DirExportProviderFactory.PROVIDER_ID);
-        String targetDirPath = testingClient.testing().getExportImportTestDirectory()+ File.separator + "dirExport";
+        testingClient.testing().exportImport().setProvider(DirExportProviderFactory.PROVIDER_ID);
+        String targetDirPath = testingClient.testing().exportImport().getExportImportTestDirectory()+ File.separator + "dirExport";
         DirExportProvider.recursiveDeleteDir(new File(targetDirPath));
-        testingClient.testing().setDir(targetDirPath);
-        testingClient.testing().setUsersPerFile(ExportImportConfig.DEFAULT_USERS_PER_FILE);
+        testingClient.testing().exportImport().setDir(targetDirPath);
+        testingClient.testing().exportImport().setUsersPerFile(ExportImportConfig.DEFAULT_USERS_PER_FILE);
 
         testFullExportImport();
 
@@ -89,11 +89,13 @@ public class ExportImportTest extends AbstractExportImportTest {
 
     @Test
     public void testDirRealmExportImport() throws Throwable {
-        testingClient.testing().setProvider(DirExportProviderFactory.PROVIDER_ID);
-        String targetDirPath = testingClient.testing().getExportImportTestDirectory() + File.separator + "dirRealmExport";
+        testingClient.testing()
+                .exportImport()
+                .setProvider(DirExportProviderFactory.PROVIDER_ID);
+        String targetDirPath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "dirRealmExport";
         DirExportProvider.recursiveDeleteDir(new File(targetDirPath));
-        testingClient.testing().setDir(targetDirPath);
-        testingClient.testing().setUsersPerFile(3);
+        testingClient.testing().exportImport().setDir(targetDirPath);
+        testingClient.testing().exportImport().setUsersPerFile(3);
 
         testRealmExportImport();
 
@@ -104,18 +106,18 @@ public class ExportImportTest extends AbstractExportImportTest {
 
     @Test
     public void testSingleFileFullExportImport() throws Throwable {
-        testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
-        String targetFilePath = testingClient.testing().getExportImportTestDirectory() + File.separator + "singleFile-full.json";
-        testingClient.testing().setFile(targetFilePath);
+        testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
+        String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-full.json";
+        testingClient.testing().exportImport().setFile(targetFilePath);
 
         testFullExportImport();
     }
 
     @Test
     public void testSingleFileRealmExportImport() throws Throwable {
-        testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
-        String targetFilePath = testingClient.testing().getExportImportTestDirectory() + File.separator + "singleFile-realm.json";
-        testingClient.testing().setFile(targetFilePath);
+        testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
+        String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-realm.json";
+        testingClient.testing().exportImport().setFile(targetFilePath);
 
         testRealmExportImport();
     }
@@ -126,14 +128,14 @@ public class ExportImportTest extends AbstractExportImportTest {
         removeRealm("test-realm");
 
         // Set the realm, which doesn't have builtin clients/roles inside JSON
-        testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
+        testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
         URL url = ExportImportTest.class.getResource("/model/testrealm.json");
         String targetFilePath = new File(url.getFile()).getAbsolutePath();
-        testingClient.testing().setFile(targetFilePath);
+        testingClient.testing().exportImport().setFile(targetFilePath);
 
-        testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT);
+        testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
 
-        testingClient.testing().runImport();
+        testingClient.testing().exportImport().runImport();
 
         RealmResource testRealmRealm = adminClient.realm("test-realm");
 
@@ -158,14 +160,14 @@ public class ExportImportTest extends AbstractExportImportTest {
         realm.components().add(component);
 
 
-        testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
+        testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
 
-        String targetFilePath = testingClient.testing().getExportImportTestDirectory() + File.separator + "singleFile-realm.json";
-        testingClient.testing().setFile(targetFilePath);
-        testingClient.testing().setAction(ExportImportConfig.ACTION_EXPORT);
-        testingClient.testing().setRealmName("component-realm");
+        String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-realm.json";
+        testingClient.testing().exportImport().setFile(targetFilePath);
+        testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT);
+        testingClient.testing().exportImport().setRealmName("component-realm");
 
-        testingClient.testing().runExport();
+        testingClient.testing().exportImport().runExport();
 
         // Delete some realm (and some data in admin realm)
         adminClient.realm("component-realm").remove();
@@ -173,9 +175,9 @@ public class ExportImportTest extends AbstractExportImportTest {
         Assert.assertEquals(3, adminClient.realms().findAll().size());
 
         // Configure import
-        testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT);
+        testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
 
-        testingClient.testing().runImport();
+        testingClient.testing().exportImport().runImport();
 
         realmRep = realm.toRepresentation();
 
@@ -203,10 +205,10 @@ public class ExportImportTest extends AbstractExportImportTest {
     }
 
     private void testFullExportImport() throws LifecycleException {
-        testingClient.testing().setAction(ExportImportConfig.ACTION_EXPORT);
-        testingClient.testing().setRealmName("");
+        testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT);
+        testingClient.testing().exportImport().setRealmName("");
 
-        testingClient.testing().runExport();
+        testingClient.testing().exportImport().runExport();
 
         removeRealm("test");
         removeRealm("test-realm");
@@ -218,9 +220,9 @@ public class ExportImportTest extends AbstractExportImportTest {
         assertNotAuthenticated("test", "user3", "password");
 
         // Configure import
-        testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT);
+        testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
 
-        testingClient.testing().runImport();
+        testingClient.testing().exportImport().runImport();
 
         // Ensure data are imported back
         Assert.assertEquals(3, adminClient.realms().findAll().size());
@@ -232,10 +234,10 @@ public class ExportImportTest extends AbstractExportImportTest {
     }
 
     private void testRealmExportImport() throws LifecycleException {
-        testingClient.testing().setAction(ExportImportConfig.ACTION_EXPORT);
-        testingClient.testing().setRealmName("test");
+        testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT);
+        testingClient.testing().exportImport().setRealmName("test");
 
-        testingClient.testing().runExport();
+        testingClient.testing().exportImport().runExport();
 
         // Delete some realm (and some data in admin realm)
         adminClient.realm("test").remove();
@@ -248,9 +250,9 @@ public class ExportImportTest extends AbstractExportImportTest {
         assertNotAuthenticated("test", "user3", "password");
 
         // Configure import
-        testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT);
+        testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
 
-        testingClient.testing().runImport();
+        testingClient.testing().exportImport().runImport();
 
         // Ensure data are imported back, but just for "test" realm
         Assert.assertEquals(3, adminClient.realms().findAll().size());
@@ -273,27 +275,4 @@ public class ExportImportTest extends AbstractExportImportTest {
         Assert.assertEquals(expectedResult, testingClient.testing().validCredentials(realmName, username, password));
     }
 
-    private static String getExportImportTestDirectory() {
-        String dirPath = null;
-        String relativeDirExportImportPath = "testsuite" + File.separator +
-                                             "integration-arquillian" + File.separator +
-                                             "tests" + File.separator +
-                                             "base" + File.separator +
-                                             "target" + File.separator +
-                                             "export-import";
-
-        if (System.getProperties().containsKey("maven.home")) {
-            dirPath = System.getProperty("user.dir").replaceFirst("testsuite.integration.*", Matcher.quoteReplacement(relativeDirExportImportPath));
-        } else {
-            for (String c : System.getProperty("java.class.path").split(File.pathSeparator)) {
-                if (c.contains(File.separator + "testsuite" + File.separator + "integration-arquillian" + File.separator)) {
-                    dirPath = c.replaceFirst("testsuite.integration-arquillian.*", Matcher.quoteReplacement(relativeDirExportImportPath));
-                }
-            }
-        }
-
-        String absolutePath = new File(dirPath).getAbsolutePath();
-        return absolutePath;
-    }
-
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
index 191581e..e01057d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
@@ -19,30 +19,42 @@ package org.keycloak.testsuite.oidc;
 
 import java.util.List;
 
-
 import org.jboss.arquillian.graphene.page.Page;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuthErrorException;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
 import org.keycloak.common.util.Time;
 import org.keycloak.events.Details;
+import org.keycloak.jose.jws.Algorithm;
 import org.keycloak.models.Constants;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.representations.IDToken;
+import org.keycloak.representations.idm.CertificateRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.util.CertificateInfoHelper;
 import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.TestRealmKeycloakTest;
 import org.keycloak.testsuite.admin.AbstractAdminTest;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
+import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
 import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
 import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
 import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -68,6 +80,9 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest {
     @Page
     protected OAuthGrantPage grantPage;
 
+    @Page
+    protected ErrorPage errorPage;
+
 
     @Override
     public void configureTestRealm(RealmRepresentation testRealm) {
@@ -308,29 +323,98 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest {
     // REQUEST & REQUEST_URI
 
     @Test
-    public void requestParam() {
-        driver.navigate().to(oauth.getLoginFormUrl() + "&request=abc");
+    public void requestParamUnsigned() throws Exception {
+        String validRedirectUri = oauth.getRedirectUri();
+        TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
 
-        assertFalse(loginPage.isCurrent());
+        // Send request object with invalid redirect uri.
+        oidcClientEndpointsResource.setOIDCRequest("test", "test-app", "http://invalid", null, Algorithm.none.toString());
+        String requestStr = oidcClientEndpointsResource.getOIDCRequest();
+
+        oauth.request(requestStr);
+        oauth.openLoginForm();
+        Assert.assertTrue(errorPage.isCurrent());
+        assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
+
+        // Assert the value from request object has bigger priority then from the query parameter.
+        oauth.redirectUri("http://invalid");
+        oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString());
+        requestStr = oidcClientEndpointsResource.getOIDCRequest();
+
+        oauth.request(requestStr);
+        OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+        Assert.assertNotNull(response.getCode());
+        Assert.assertEquals("mystate", response.getState());
         assertTrue(appPage.isCurrent());
+    }
 
-        // Assert error response was sent because not logged in
-        OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth);
-        Assert.assertNull(resp.getCode());
-        Assert.assertEquals(OAuthErrorException.REQUEST_NOT_SUPPORTED, resp.getError());
+    @Test
+    public void requestUriParamUnsigned() throws Exception {
+        String validRedirectUri = oauth.getRedirectUri();
+        TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+
+        // Send request object with invalid redirect uri.
+        oidcClientEndpointsResource.setOIDCRequest("test", "test-app", "http://invalid", null, Algorithm.none.toString());
+
+        oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
+        oauth.openLoginForm();
+        Assert.assertTrue(errorPage.isCurrent());
+        assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
+
+        // Assert the value from request object has bigger priority then from the query parameter.
+        oauth.redirectUri("http://invalid");
+        oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString());
+
+        OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+        Assert.assertNotNull(response.getCode());
+        Assert.assertEquals("mystate", response.getState());
+        assertTrue(appPage.isCurrent());
     }
 
     @Test
-    public void requestUriParam() {
-        driver.navigate().to(oauth.getLoginFormUrl() + "&request_uri=https%3A%2F%2Flocalhost%3A60784%2Fexport%2FqzHTG11W48.jwt");
+    public void requestUriParamSigned() throws Exception {
+        String validRedirectUri = oauth.getRedirectUri();
+        TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+
+        // Set required signature for request_uri
+        ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+        ClientRepresentation clientRep = clientResource.toRepresentation();
+        OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(Algorithm.RS256);
+        clientResource.update(clientRep);
+
+        // Verify unsigned request_uri will fail
+        oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString());
+        oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
+        oauth.openLoginForm();
+        Assert.assertTrue(errorPage.isCurrent());
+        assertEquals("Invalid Request", errorPage.getError());
 
-        assertFalse(loginPage.isCurrent());
+        // Generate keypair for client
+        String clientPublicKeyPem = oidcClientEndpointsResource.generateKeys().get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY);
+
+        // Verify signed request_uri will fail due to failed signature validation
+        oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.RS256.toString());
+        oauth.openLoginForm();
+        Assert.assertTrue(errorPage.isCurrent());
+        assertEquals("Invalid Request", errorPage.getError());
+
+
+        // Update clientModel with publicKey for signing
+        clientRep = clientResource.toRepresentation();
+        CertificateRepresentation cert = new CertificateRepresentation();
+        cert.setPublicKey(clientPublicKeyPem);
+        CertificateInfoHelper.updateClientRepresentationCertificateInfo(clientRep, cert, JWTClientAuthenticator.ATTR_PREFIX);
+        clientResource.update(clientRep);
+
+        // Check signed request_uri will pass
+        OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+        Assert.assertNotNull(response.getCode());
+        Assert.assertEquals("mystate", response.getState());
         assertTrue(appPage.isCurrent());
 
-        // Assert error response was sent because not logged in
-        OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth);
-        Assert.assertNull(resp.getCode());
-        Assert.assertEquals(OAuthErrorException.REQUEST_URI_NOT_SUPPORTED, resp.getError());
+        // Revert requiring signature for client
+        OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(null);
+        clientResource.update(clientRep);
     }
 
     // LOGIN_HINT
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
index 91ccc7e..7fea657 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
@@ -87,6 +87,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
             Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "public");
             Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.RS256.toString());
             Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), Algorithm.RS256.toString());
+            Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), Algorithm.none.toString(), Algorithm.RS256.toString());
 
             // Client authentication
             Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt");
@@ -101,8 +102,8 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
             Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS);
 
             // Request and Request_Uri
-            Assert.assertFalse(oidcConfig.getRequestParameterSupported());
-            Assert.assertFalse(oidcConfig.getRequestUriParameterSupported());
+            Assert.assertTrue(oidcConfig.getRequestParameterSupported());
+            Assert.assertTrue(oidcConfig.getRequestUriParameterSupported());
         } finally {
             client.close();
         }
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 4f15d6d..356cbb2 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -240,6 +240,8 @@ fine-oidc-endpoint-conf=Fine Grain OpenID Connect Configuration
 fine-oidc-endpoint-conf.tooltip=Expand this section to configure advanced settings of this client related to OpenID Connect protocol
 user-info-signed-response-alg=User Info Signed Response Algorithm
 user-info-signed-response-alg.tooltip=JWA algorithm used for signed User Info Endpoint response. If set to 'unsigned', then User Info Response won't be signed and will be returned in application/json format.
+request-object-signature-alg=Request Object Signature Algorithm
+request-object-signature-alg.tooltip=JWA algorithm, which client needs to use when sending OIDC request object specified by 'request' or 'request_uri' parameters. If set to 'any', then Request object can be signed by any algorithm (including 'none' ).
 fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration
 fine-saml-endpoint-conf.tooltip=Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service.
 assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 24efd88..3bbd471 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -797,6 +797,12 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
         "RS256"
     ];
 
+    $scope.requestObjectSignatureAlgorithms = [
+        "any",
+        "none",
+        "RS256"
+    ];
+
     $scope.realm = realm;
     $scope.samlAuthnStatement = false;
     $scope.samlMultiValuedRoles = false;
@@ -898,7 +904,11 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
             }
         }
 
-        $scope.userInfoSignedResponseAlg = getSignatureAlgorithm('user.info.response');
+        var attrVal1 = $scope.client.attributes['user.info.response.signature.alg'];
+        $scope.userInfoSignedResponseAlg = attrVal1==null ? 'unsigned' : attrVal1;
+
+        var attrVal2 = $scope.client.attributes['request.object.signature.alg'];
+         $scope.requestObjectSignatureAlg = attrVal2==null ? 'any' : attrVal2;
     }
 
     if (!$scope.create) {
@@ -964,23 +974,20 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
     };
 
     $scope.changeUserInfoSignedResponseAlg = function() {
-        changeSignatureAlgorithm('user.info.response', $scope.userInfoSignedResponseAlg);
+        if ($scope.userInfoSignedResponseAlg === 'unsigned') {
+            $scope.client.attributes['user.info.response.signature.alg'] = null;
+        } else {
+            $scope.client.attributes['user.info.response.signature.alg'] = $scope.userInfoSignedResponseAlg;
+        }
     };
 
-    function changeSignatureAlgorithm(attrPrefix, attrValue) {
-        var attrName = attrPrefix + '.signature.alg';
-        if (attrValue === 'unsigned') {
-            $scope.client.attributes[attrName] = null;
+    $scope.changeRequestObjectSignatureAlg = function() {
+        if ($scope.requestObjectSignatureAlg === 'any') {
+            $scope.client.attributes['request.object.signature.alg'] = null;
         } else {
-            $scope.client.attributes[attrName] = attrValue;
+            $scope.client.attributes['request.object.signature.alg'] = $scope.requestObjectSignatureAlg;
         }
-    }
-
-    function getSignatureAlgorithm(attrPrefix) {
-        var attrName = attrPrefix + '.signature.alg';
-        var attrVal = $scope.client.attributes[attrName];
-        return attrVal==null ? 'unsigned' : attrVal;
-    }
+    };
 
     $scope.$watch(function() {
         return $location.path();
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index af10a22..d8f0d24 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -348,6 +348,19 @@
                 </div>
                 <kc-tooltip>{{:: 'user-info-signed-response-alg.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group clearfix block" data-ng-show="protocol == 'openid-connect'">
+                <label class="col-md-2 control-label" for="requestObjectSignatureAlg">{{:: 'request-object-signature-alg' | translate}}</label>
+                <div class="col-sm-6">
+                    <div>
+                        <select class="form-control" id="requestObjectSignatureAlg"
+                                ng-change="changeRequestObjectSignatureAlg()"
+                                ng-model="requestObjectSignatureAlg"
+                                ng-options="sig for sig in requestObjectSignatureAlgorithms">
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'request-object-signature-alg.tooltip' | translate}}</kc-tooltip>
+            </div>
         </fieldset>
 
         <div class="form-group">