keycloak-aplcache

KEYCLOAK-571 OpenID Connect Discovery KEYCLOAK-1091 JSON

3/9/2015 5:40:41 AM

Changes

core/pom.xml 6(+6 -0)

Details

core/pom.xml 6(+6 -0)

diff --git a/core/pom.xml b/core/pom.xml
index 5754ce3..3efc75a 100755
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -56,6 +56,12 @@
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>nimbus-jose-jwt</artifactId>
+            <version>3.9</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <resources>
diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWK.java b/core/src/main/java/org/keycloak/jose/jwk/JWK.java
new file mode 100644
index 0000000..d292f41
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwk/JWK.java
@@ -0,0 +1,62 @@
+package org.keycloak.jose.jwk;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JWK {
+
+    public static final String KEY_ID = "kid";
+
+    public static final String KEY_TYPE = "kty";
+
+    public static final String ALGORITHM = "alg";
+
+    public static final String PUBLIC_KEY_USE = "use";
+
+    @JsonProperty(KEY_ID)
+    private String keyId;
+
+    @JsonProperty(KEY_TYPE)
+    private String keyType;
+
+    @JsonProperty(ALGORITHM)
+    private String algorithm;
+
+    @JsonProperty(PUBLIC_KEY_USE)
+    private String publicKeyUse;
+
+    public String getKeyId() {
+        return keyId;
+    }
+
+    public void setKeyId(String keyId) {
+        this.keyId = keyId;
+    }
+
+    public String getKeyType() {
+        return keyType;
+    }
+
+    public void setKeyType(String keyType) {
+        this.keyType = keyType;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public String getPublicKeyUse() {
+        return publicKeyUse;
+    }
+
+    public void setPublicKeyUse(String publicKeyUse) {
+        this.publicKeyUse = publicKeyUse;
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java b/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java
new file mode 100644
index 0000000..bc3a228
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java
@@ -0,0 +1,79 @@
+package org.keycloak.jose.jwk;
+
+import org.keycloak.util.Base64Url;
+
+import java.math.BigInteger;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JWKBuilder {
+
+    public static final String DEFAULT_PUBLIC_KEY_USE = "sig";
+    public static final String DEFAULT_MESSAGE_DIGEST = "SHA-256";
+
+
+    private JWKBuilder() {
+    }
+
+    public static JWKBuilder create() {
+        return new JWKBuilder();
+    }
+
+    public JWK rs256(PublicKey key) {
+        RSAPublicKey rsaKey = (RSAPublicKey) key;
+
+        RSAPublicJWK k = new RSAPublicJWK();
+        k.setKeyId(createKeyId(key));
+        k.setKeyType(RSAPublicJWK.RSA);
+        k.setAlgorithm(RSAPublicJWK.RS256);
+        k.setPublicKeyUse(DEFAULT_PUBLIC_KEY_USE);
+        k.setModulus(Base64Url.encode(toIntegerBytes(rsaKey.getModulus())));
+        k.setPublicExponent(Base64Url.encode(toIntegerBytes(rsaKey.getPublicExponent())));
+
+        return k;
+    }
+
+    private String createKeyId(Key key) {
+        try {
+            return Base64Url.encode(MessageDigest.getInstance(DEFAULT_MESSAGE_DIGEST).digest(key.getEncoded()));
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Copied from org.apache.commons.codec.binary.Base64
+     */
+    private static byte[] toIntegerBytes(final BigInteger bigInt) {
+        int bitlen = bigInt.bitLength();
+        // round bitlen
+        bitlen = ((bitlen + 7) >> 3) << 3;
+        final byte[] bigBytes = bigInt.toByteArray();
+
+        if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
+            return bigBytes;
+        }
+        // set up params for copying everything but sign bit
+        int startSrc = 0;
+        int len = bigBytes.length;
+
+        // if bigInt is exactly byte-aligned, just skip signbit in copy
+        if ((bigInt.bitLength() % 8) == 0) {
+            startSrc = 1;
+            len--;
+        }
+        final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
+        final byte[] resizedBytes = new byte[bitlen / 8];
+        System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
+        return resizedBytes;
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java
new file mode 100644
index 0000000..38f02d8
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java
@@ -0,0 +1,54 @@
+package org.keycloak.jose.jwk;
+
+import org.codehaus.jackson.type.TypeReference;
+import org.keycloak.util.Base64Url;
+import org.keycloak.util.JsonSerialization;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JWKParser {
+
+    private static TypeReference<Map<String,String>> typeRef = new TypeReference<Map<String,String>>() {};
+
+    private Map<String, String> values;
+
+    private JWKParser() {
+    }
+
+    public static JWKParser create() {
+        return new JWKParser();
+    }
+
+    public JWKParser parse(String jwk) {
+        try {
+            this.values = JsonSerialization.mapper.readValue(jwk, typeRef);
+            return this;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public PublicKey toPublicKey() {
+        String algorithm = values.get(JWK.KEY_TYPE);
+        if (RSAPublicJWK.RSA.equals(algorithm)) {
+            BigInteger modulus = new BigInteger(1, Base64Url.decode(values.get(RSAPublicJWK.MODULUS)));
+            BigInteger publicExponent = new BigInteger(1, Base64Url.decode(values.get(RSAPublicJWK.PUBLIC_EXPONENT)));
+
+            try {
+                return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else {
+            throw new RuntimeException("Unsupported algorithm " + algorithm);
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwk/RSAPublicJWK.java b/core/src/main/java/org/keycloak/jose/jwk/RSAPublicJWK.java
new file mode 100644
index 0000000..8090599
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwk/RSAPublicJWK.java
@@ -0,0 +1,38 @@
+package org.keycloak.jose.jwk;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RSAPublicJWK extends JWK {
+
+    public static final String RSA = "RSA";
+    public static final String RS256 = "RS256";
+
+    public static final String MODULUS = "n";
+    public static final String PUBLIC_EXPONENT = "e";
+
+    @JsonProperty(MODULUS)
+    private String modulus;
+
+    @JsonProperty("e")
+    private String publicExponent;
+
+    public String getModulus() {
+        return modulus;
+    }
+
+    public void setModulus(String modulus) {
+        this.modulus = modulus;
+    }
+
+    public String getPublicExponent() {
+        return publicExponent;
+    }
+
+    public void setPublicExponent(String publicExponent) {
+        this.publicExponent = publicExponent;
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/OAuth2Constants.java b/core/src/main/java/org/keycloak/OAuth2Constants.java
index 07071ff..5aba901 100644
--- a/core/src/main/java/org/keycloak/OAuth2Constants.java
+++ b/core/src/main/java/org/keycloak/OAuth2Constants.java
@@ -25,6 +25,10 @@ public interface OAuth2Constants {
 
     String REFRESH_TOKEN = "refresh_token";
 
+    String AUTHORIZATION_CODE = "authorization_code";
+
+    String PASSWORD = "password";
+
 }
 
 
diff --git a/core/src/main/java/org/keycloak/util/JsonSerialization.java b/core/src/main/java/org/keycloak/util/JsonSerialization.java
index a1a93ba..ff080de 100755
--- a/core/src/main/java/org/keycloak/util/JsonSerialization.java
+++ b/core/src/main/java/org/keycloak/util/JsonSerialization.java
@@ -3,6 +3,7 @@ package org.keycloak.util;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.SerializationConfig;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.type.TypeReference;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/core/src/test/java/org/keycloak/jose/jwk/JWKBuilderTest.java b/core/src/test/java/org/keycloak/jose/jwk/JWKBuilderTest.java
new file mode 100644
index 0000000..7b6a861
--- /dev/null
+++ b/core/src/test/java/org/keycloak/jose/jwk/JWKBuilderTest.java
@@ -0,0 +1,70 @@
+package org.keycloak.jose.jwk;
+
+import com.nimbusds.jose.jwk.RSAKey;
+import org.junit.Test;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.util.Base64Url;
+import org.keycloak.util.JsonSerialization;
+import sun.security.rsa.RSAPublicKeyImpl;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JWKBuilderTest {
+
+    @Test
+    public void publicRs256() throws Exception {
+        PublicKey publicKey = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic();
+
+        JWK jwk = JWKBuilder.create().rs256(publicKey);
+
+        assertNotNull(jwk.getKeyId());
+        assertEquals("RSA", jwk.getKeyType());
+        assertEquals("RS256", jwk.getAlgorithm());
+        assertEquals("sig", jwk.getPublicKeyUse());
+
+        assertTrue(jwk instanceof RSAPublicJWK);
+        assertNotNull(((RSAPublicJWK) jwk).getModulus());
+        assertNotNull(((RSAPublicJWK) jwk).getPublicExponent());
+
+        String jwkJson = JsonSerialization.writeValueAsString(jwk);
+
+        // Parse
+        assertArrayEquals(publicKey.getEncoded(), JWKParser.create().parse(jwkJson).toPublicKey().getEncoded());
+
+        // Parse with 3rd party lib
+        assertArrayEquals(publicKey.getEncoded(), RSAKey.parse(jwkJson).toRSAPublicKey().getEncoded());
+    }
+
+    @Test
+    public void parse() throws NoSuchAlgorithmException, InvalidKeySpecException {
+        String jwkJson = "{" +
+                "   \"kty\": \"RSA\"," +
+                "   \"alg\": \"RS256\"," +
+                "   \"use\": \"sig\"," +
+                "   \"kid\": \"3121adaa80ace09f89d80899d4a5dc4ce33d0747\"," +
+                "   \"n\": \"soFDjoZ5mQ8XAA7reQAFg90inKAHk0DXMTizo4JuOsgzUbhcplIeZ7ks83hsEjm8mP8lUVaHMPMAHEIp3gu6Xxsg-s73ofx1dtt_Fo7aj8j383MFQGl8-FvixTVobNeGeC0XBBQjN8lEl-lIwOa4ZoERNAShplTej0ntDp7TQm0=\"," +
+                "   \"e\": \"AQAB\"" +
+                "  }";
+
+        PublicKey key = JWKParser.create().parse(jwkJson).toPublicKey();
+        assertEquals("RSA", key.getAlgorithm());
+        assertEquals("X.509", key.getFormat());
+    }
+
+}
diff --git a/events/api/src/main/java/org/keycloak/events/Errors.java b/events/api/src/main/java/org/keycloak/events/Errors.java
index f6531eb..7a4404d 100755
--- a/events/api/src/main/java/org/keycloak/events/Errors.java
+++ b/events/api/src/main/java/org/keycloak/events/Errors.java
@@ -5,6 +5,8 @@ package org.keycloak.events;
  */
 public interface Errors {
 
+    String INVALID_REQUEST = "invalid_request";
+
     String REALM_DISABLED = "realm_disabled";
 
     String CLIENT_NOT_FOUND = "client_not_found";
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 080b7e8..a6a2330 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -19,6 +19,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.HttpAuthenticationManager;
@@ -236,7 +237,7 @@ public class SamlService {
             String redirect = null;
             URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
             if (redirectUri != null && !"null".equals(redirectUri)) {  // "null" is for testing purposes
-                redirect = OIDCLoginProtocolService.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
+                redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
             } else {
                 if (bindingType.equals(SamlProtocol.SAML_POST_BINDING)) {
                     redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
@@ -376,7 +377,7 @@ public class SamlService {
             }
 
             if (redirectUri != null) {
-                redirectUri = OIDCLoginProtocolService.verifyRedirectUri(uriInfo, redirectUri, realm, client);
+                redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
                 if (redirectUri == null) {
                     return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
                 }
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
new file mode 100644
index 0000000..6f6441f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -0,0 +1,321 @@
+package org.keycloak.protocol.oidc.endpoints;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.services.ErrorPageException;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.managers.HttpAuthenticationManager;
+import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.services.resources.flows.Urls;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AuthorizationEndpoint {
+
+    private static final Logger logger = Logger.getLogger(AuthorizationEndpoint.class);
+
+    private enum Action {
+        REGISTER, CODE
+    }
+
+    @Context
+    private KeycloakSession session;
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    private HttpHeaders headers;
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    private final AuthenticationManager authManager;
+    private final RealmModel realm;
+    private final EventBuilder event;
+
+    private ClientModel client;
+    private ClientSessionModel clientSession;
+
+    private Action action;
+
+    private String clientId;
+    private String redirectUri;
+    private String redirectUriParam;
+    private String responseType;
+    private String state;
+    private String scope;
+    private String loginHint;
+    private String prompt;
+    private String idpHint;
+
+    private String legacyResponseType;
+
+    public AuthorizationEndpoint(AuthenticationManager authManager, RealmModel realm, EventBuilder event) {
+        this.authManager = authManager;
+        this.realm = realm;
+        this.event = event;
+        event.event(EventType.LOGIN);
+    }
+
+    @GET
+    public Response build() {
+        switch (action) {
+            case REGISTER:
+                return buildRegister();
+            case CODE:
+                return buildAuthorizationCodeAuthorizationResponse();
+        }
+
+        throw new RuntimeException("Unknown action " + action);
+    }
+
+    /**
+     * @deprecated
+     */
+    public AuthorizationEndpoint legacy(String legacyResponseType) {
+        // TODO Change to warn once adapters has been updated
+        logger.debugv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri());
+        this.legacyResponseType = legacyResponseType;
+        return this;
+    }
+
+    public AuthorizationEndpoint register() {
+        event.event(EventType.REGISTER);
+        action = Action.REGISTER;
+
+        if (!realm.isRegistrationAllowed()) {
+            throw new ErrorPageException(session, realm, uriInfo, "Registration not allowed");
+        }
+
+        return this;
+    }
+
+    public AuthorizationEndpoint init() {
+        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
+
+        clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
+        responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_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.REDIRECT_URI_PARAM);
+        idpHint = params.getFirst(OIDCLoginProtocol.K_IDP_HINT);
+
+        checkSsl();
+        checkRealm();
+        checkClient();
+        checkResponseType();
+        checkRedirectUri();
+
+        createClientSession();
+
+        return this;
+    }
+
+    private void checkSsl() {
+        if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
+            event.error(Errors.SSL_REQUIRED);
+            throw new ErrorPageException(session, realm, uriInfo, "HTTPS required");
+        }
+    }
+
+    private void checkRealm() {
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            throw new ErrorPageException(session, realm, uriInfo, "Realm not enabled");
+        }
+    }
+
+    private void checkClient() {
+        if (clientId == null) {
+            event.error(Errors.INVALID_REQUEST);
+            throw new ErrorPageException(session, realm, uriInfo, "Missing paramater: " + OIDCLoginProtocol.CLIENT_ID_PARAM);
+        }
+
+        event.client(clientId);
+
+        client = realm.findClient(clientId);
+        if (client == null) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            throw new ErrorPageException(session, realm, uriInfo, "Client not found");
+        }
+
+        if ((client instanceof ApplicationModel) && ((ApplicationModel) client).isBearerOnly()) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new ErrorPageException(session, realm, uriInfo, "Bearer only clients are not allowed to initiate browser login");
+        }
+
+        if (client.isDirectGrantsOnly()) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new ErrorPageException(session, realm, uriInfo, "Direct grants only clients are not allowed to initiate browser login");
+        }
+    }
+
+    private void checkResponseType() {
+        if (responseType == null) {
+            if (legacyResponseType != null) {
+                responseType = legacyResponseType;
+            } else {
+                event.error(Errors.INVALID_REQUEST);
+                throw new ErrorPageException(session, realm, uriInfo, "Missing query parameter: " + OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+            }
+        }
+
+        event.detail(Details.RESPONSE_TYPE, responseType);
+
+        if (responseType.equals(OAuth2Constants.CODE)) {
+            action = Action.CODE;
+        } else {
+            event.error(Errors.INVALID_REQUEST);
+            throw new ErrorPageException(session, realm, uriInfo, "Invalid " + OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+        }
+    }
+
+    private void checkRedirectUri() {
+        event.detail(Details.REDIRECT_URI, redirectUriParam);
+
+        redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client);
+        if (redirectUri == null) {
+            event.error(Errors.INVALID_REDIRECT_URI);
+            throw new ErrorPageException(session, realm, uriInfo, "Invalid " + OIDCLoginProtocol.REDIRECT_URI_PARAM);
+        }
+    }
+
+    private void createClientSession() {
+        clientSession = session.sessions().createClientSession(realm, client);
+        clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        clientSession.setRedirectUri(redirectUri);
+        clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+        clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
+        clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType);
+        clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam);
+
+        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
+        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(OIDCLoginProtocol.K_IDP_HINT, idpHint);
+    }
+
+    private Response buildAuthorizationCodeAuthorizationResponse() {
+        String accessCode = new ClientSessionCode(realm, clientSession).getCode();
+
+        if (idpHint != null && !"".equals(idpHint)) {
+            IdentityProviderModel identityProviderModel = realm.getIdentityProviderById(idpHint);
+
+            if (identityProviderModel == null) {
+                return Flows.forms(session, realm, null, uriInfo)
+                        .setError("Could not find an identity provider with the identifier [" + idpHint + "].")
+                        .createErrorPage();
+            }
+            return buildRedirectToIdentityProvider(idpHint, accessCode);
+        }
+
+        Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
+        if (response != null) return response;
+
+        // SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?)
+        HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event);
+        HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate();
+        if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse();
+
+        if (prompt != null && prompt.equals("none")) {
+            OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo);
+            return oauth.cancelLogin(clientSession);
+        }
+
+        List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+        for (IdentityProviderModel identityProvider : identityProviders) {
+            if (identityProvider.isAuthenticateByDefault()) {
+                return buildRedirectToIdentityProvider(identityProvider.getId(), accessCode);
+            }
+        }
+
+        List<RequiredCredentialModel> requiredCredentials = realm.getRequiredCredentials();
+        if (requiredCredentials.isEmpty()) {
+            if (!identityProviders.isEmpty()) {
+                if (identityProviders.size() == 1) {
+                    return buildRedirectToIdentityProvider(identityProviders.get(0).getId(), accessCode);
+                }
+
+                return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + realm.getName() + "] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.").createErrorPage();
+            }
+
+            return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + realm.getName() + "] does not support any credential type.").createErrorPage();
+        }
+
+        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
+                .setClientSessionCode(accessCode);
+
+        // Attach state from SPNEGO authentication
+        if (httpAuthOutput.getChallenge() != null) {
+            httpAuthOutput.getChallenge().sendChallenge(forms);
+        }
+
+        String rememberMeUsername = AuthenticationManager.getRememberMeUsername(realm, headers);
+
+        if (loginHint != null || rememberMeUsername != null) {
+            MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
+
+            if (loginHint != null) {
+                formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
+            } else {
+                formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
+                formData.add("rememberMe", "on");
+            }
+
+            forms.setFormData(formData);
+        }
+
+        return forms.createLogin();
+    }
+
+    private Response buildRegister() {
+        authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
+
+        return Flows.forms(session, realm, client, uriInfo)
+                .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode())
+                .createRegistration();
+    }
+
+    private Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
+        logger.debug("Automatically redirect to identity provider: " + providerId);
+        return Response.temporaryRedirect(
+                Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), providerId, this.realm.getName(), accessCode))
+                .build();
+    }
+
+}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java
new file mode 100644
index 0000000..30359d1
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java
@@ -0,0 +1,91 @@
+package org.keycloak.protocol.oidc.endpoints;
+
+import org.jboss.resteasy.spi.BadRequestException;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.util.StreamUtil;
+import org.keycloak.util.UriUtils;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginStatusIframeEndpoint {
+
+    @Context
+    private UriInfo uriInfo;
+
+    private RealmModel realm;
+
+    public LoginStatusIframeEndpoint(RealmModel realm) {
+        this.realm = realm;
+    }
+
+    @GET
+    @Produces(MediaType.TEXT_HTML)
+    public Response getLoginStatusIframe(@QueryParam("client_id") String client_id,
+                                         @QueryParam("origin") String origin) {
+        if (!UriUtils.isOrigin(origin)) {
+            throw new BadRequestException("Invalid origin");
+        }
+
+        ClientModel client = realm.findClient(client_id);
+        if (client == null) {
+            throw new NotFoundException("could not find client");
+        }
+
+        InputStream is = getClass().getClassLoader().getResourceAsStream("login-status-iframe.html");
+        if (is == null) throw new NotFoundException("Could not find login-status-iframe.html ");
+
+        boolean valid = false;
+        for (String o : client.getWebOrigins()) {
+            if (o.equals("*") || o.equals(origin)) {
+                valid = true;
+                break;
+            }
+        }
+
+        for (String r : RedirectUtils.resolveValidRedirects(uriInfo, client.getRedirectUris())) {
+            int i = r.indexOf('/', 8);
+            if (i != -1) {
+                r = r.substring(0, i);
+            }
+
+            if (r.equals(origin)) {
+                valid = true;
+                break;
+            }
+        }
+
+        if (!valid) {
+            throw new BadRequestException("Invalid origin");
+        }
+
+        try {
+            String file = StreamUtil.readString(is);
+            file = file.replace("ORIGIN", origin);
+
+            CacheControl cacheControl = new CacheControl();
+            cacheControl.setNoTransform(false);
+            cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
+
+            return Response.ok(file).cacheControl(cacheControl).build();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
new file mode 100644
index 0000000..16ab80c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -0,0 +1,166 @@
+package org.keycloak.protocol.oidc.endpoints;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.OAuthErrorException;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.resources.Cors;
+import org.keycloak.services.resources.flows.Flows;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LogoutEndpoint {
+
+    @Context
+    private KeycloakSession session;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    private HttpHeaders headers;
+
+    @Context
+    private UriInfo uriInfo;
+
+    private TokenManager tokenManager;
+    private AuthenticationManager authManager;
+    private RealmModel realm;
+    private EventBuilder event;
+
+    public LogoutEndpoint(TokenManager tokenManager, AuthenticationManager authManager, RealmModel realm, EventBuilder event) {
+        this.tokenManager = tokenManager;
+        this.authManager = authManager;
+        this.realm = realm;
+        this.event = event;
+    }
+
+    /**
+     * Logout user session.  User must be logged in via a session cookie.
+     *
+     * @param redirectUri
+     * @return
+     */
+    @GET
+    @NoCache
+    public Response logout(final @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri) {
+        event.event(EventType.LOGOUT);
+        if (redirectUri != null) {
+            event.detail(Details.REDIRECT_URI, redirectUri);
+        }
+        // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
+        AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
+        if (authResult != null) {
+            logout(authResult.getSession());
+        }
+
+        if (redirectUri != null) {
+            String validatedRedirect = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
+            if (validatedRedirect == null) {
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
+            }
+            return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
+        } else {
+            return Response.ok().build();
+        }
+    }
+
+    /**
+     * Logout a session via a non-browser invocation.  Similar signature to refresh token except there is no grant_type.
+     * You must pass in the refresh token and
+     * authenticate the client if it is not public.
+     *
+     * If the client is a confidential client
+     * you must include the client-id (application name or oauth client name) and secret in an Basic Auth Authorization header.
+     *
+     * If the client is a public client, then you must include a "client_id" form parameter with the app's or oauth client's name.
+     *
+     * returns 204 if successful, 400 if not with a json error response.
+     *
+     * @param authorizationHeader
+     * @param form
+     * @return
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response logoutToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader,
+                                final MultivaluedMap<String, String> form) {
+        checkSsl();
+
+        event.event(EventType.LOGOUT);
+
+        ClientModel client = authorizeClient(authorizationHeader, form, event);
+        String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
+        if (refreshToken == null) {
+            event.error(Errors.INVALID_TOKEN);
+            throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
+        }
+        try {
+            RefreshToken token = tokenManager.verifyRefreshToken(realm, refreshToken);
+            UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
+            if (userSessionModel != null) {
+                logout(userSessionModel);
+            }
+        } catch (OAuthErrorException e) {
+            event.error(Errors.INVALID_TOKEN);
+            throw new ErrorResponseException(e.getError(), e.getDescription(), Response.Status.BAD_REQUEST);
+        }
+        return Cors.add(request, Response.noContent()).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+    }
+
+    private void logout(UserSessionModel userSession) {
+        authManager.logout(session, realm, userSession, uriInfo, clientConnection);
+        event.user(userSession.getUser()).session(userSession).success();
+    }
+
+    private ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData, EventBuilder event) {
+        ClientModel client = AuthorizeClientUtil.authorizeClient(authorizationHeader, formData, event, realm);
+
+        if ( (client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
+            throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
+        }
+
+        return client;
+    }
+
+    private void checkSsl() {
+        if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
+            throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN);
+        }
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
new file mode 100644
index 0000000..ab78e6c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -0,0 +1,350 @@
+package org.keycloak.protocol.oidc.endpoints;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.OAuthErrorException;
+import org.keycloak.constants.AdapterConstants;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.UserSessionProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.resources.Cors;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TokenEndpoint {
+
+    private static final Logger logger = Logger.getLogger(TokenEndpoint.class);
+
+    private enum Action {
+        AUTHORIZATION_CODE, REFRESH_TOKEN, PASSWORD
+    }
+
+    @Context
+    private KeycloakSession session;
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    private HttpHeaders headers;
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    private final TokenManager tokenManager;
+    private final AuthenticationManager authManager;
+    private final RealmModel realm;
+    private final EventBuilder event;
+
+    private Action action;
+
+    private String clientId;
+    private String grantType;
+    private String code;
+    private String redirectUri;
+
+    private String legacyGrantType;
+
+    public TokenEndpoint(TokenManager tokenManager, AuthenticationManager authManager, RealmModel realm, EventBuilder event) {
+        this.tokenManager = tokenManager;
+        this.authManager = authManager;
+        this.realm = realm;
+        this.event = event;
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response build(final MultivaluedMap<String, String> formData) {
+        switch (action) {
+            case AUTHORIZATION_CODE:
+                return buildAuthorizationCodeAccessTokenResponse(formData);
+            case REFRESH_TOKEN:
+                return buildRefreshToken(formData);
+            case PASSWORD:
+                return buildResourceOwnerPasswordCredentialsGrant(formData);
+        }
+
+        throw new RuntimeException("Unknown action " + action);
+    }
+
+    @OPTIONS
+    public Response preflight() {
+        if (logger.isDebugEnabled()) {
+            logger.debugv("CORS preflight from: {0}", headers.getRequestHeaders().getFirst("Origin"));
+        }
+        return Cors.add(request, Response.ok()).auth().preflight().build();
+    }
+
+    /**
+     * @deprecated
+     */
+    public TokenEndpoint legacy(String legacyGrantType) {
+        // TODO Change to warn once adapters has been updated
+        logger.debugv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri());
+        this.legacyGrantType = legacyGrantType;
+        return this;
+    }
+
+    public TokenEndpoint init() {
+        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
+
+        clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
+        grantType = params.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM);
+        code = params.getFirst(OIDCLoginProtocol.CODE_PARAM);
+        redirectUri = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
+
+        checkSsl();
+        checkRealm();
+        checkGrantType();
+
+        return this;
+    }
+
+    private void checkSsl() {
+        if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
+            throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN);
+        }
+    }
+
+    private void checkRealm() {
+        if (!realm.isEnabled()) {
+            throw new ErrorResponseException("access_denied", "Realm not enabled", Response.Status.FORBIDDEN);
+        }
+    }
+
+    private ClientModel authorizeClient(final MultivaluedMap<String, String> formData) {
+        String authorizationHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+        ClientModel client = AuthorizeClientUtil.authorizeClient(authorizationHeader, formData, event, realm);
+
+        if ((client instanceof ApplicationModel) && ((ApplicationModel) client).isBearerOnly()) {
+            throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
+        }
+
+        return client;
+    }
+
+    private void checkGrantType() {
+        if (grantType == null) {
+            if (legacyGrantType != null) {
+                grantType = legacyGrantType;
+            } else {
+                throw new ErrorResponseException("invalid_request", "Missing query parameter: " + OIDCLoginProtocol.GRANT_TYPE_PARAM, Response.Status.BAD_REQUEST);
+            }
+        }
+
+        if (grantType.equals(OAuth2Constants.AUTHORIZATION_CODE)) {
+            event.event(EventType.CODE_TO_TOKEN);
+            action = Action.AUTHORIZATION_CODE;
+        } else if (grantType.equals(OAuth2Constants.REFRESH_TOKEN)) {
+            event.event(EventType.REFRESH_TOKEN);
+            action = Action.REFRESH_TOKEN;
+        } else if (grantType.equals(OAuth2Constants.PASSWORD)) {
+            event.event(EventType.LOGIN);
+            action = Action.PASSWORD;
+        } else {
+            throw new ErrorResponseException(Errors.INVALID_REQUEST, "Invalid " + OIDCLoginProtocol.GRANT_TYPE_PARAM, Response.Status.BAD_REQUEST);
+        }
+    }
+
+    public Response buildAuthorizationCodeAccessTokenResponse(final MultivaluedMap<String, String> formData) {
+        String code = formData.getFirst(OAuth2Constants.CODE);
+        if (code == null) {
+            event.error(Errors.INVALID_CODE);
+            throw new ErrorResponseException("invalid_request", "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
+        }
+
+        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
+        if (accessCode == null) {
+            String[] parts = code.split("\\.");
+            if (parts.length == 2) {
+                try {
+                    event.detail(Details.CODE_ID, new String(parts[1]));
+                } catch (Throwable t) {
+                }
+            }
+            event.error(Errors.INVALID_CODE);
+            throw new ErrorResponseException("invalid_grant", "Code not found", Response.Status.BAD_REQUEST);
+        }
+
+        ClientSessionModel clientSession = accessCode.getClientSession();
+        event.detail(Details.CODE_ID, clientSession.getId());
+        if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN)) {
+            event.error(Errors.INVALID_CODE);
+            throw new ErrorResponseException("invalid_grant", "Code is expired", Response.Status.BAD_REQUEST);
+        }
+
+        accessCode.setAction(null);
+        UserSessionModel userSession = clientSession.getUserSession();
+        event.user(userSession.getUser());
+        event.session(userSession.getId());
+
+        ClientModel client = authorizeClient(formData);
+
+        String redirectUri = clientSession.getNote(OIDCLoginProtocol.REDIRECT_URI_PARAM);
+        if (redirectUri != null && !redirectUri.equals(formData.getFirst(OAuth2Constants.REDIRECT_URI))) {
+            event.error(Errors.INVALID_CODE);
+            throw new ErrorResponseException("invalid_grant", "Incorrect redirect_uri", Response.Status.BAD_REQUEST);
+        }
+
+        if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
+            event.error(Errors.INVALID_CODE);
+            throw new ErrorResponseException("invalid_grant", "Auth error", Response.Status.BAD_REQUEST);
+        }
+
+        UserModel user = session.users().getUserById(userSession.getUser().getId(), realm);
+        if (user == null) {
+            event.error(Errors.USER_NOT_FOUND);
+            throw new ErrorResponseException("invalid_grant", "User not found", Response.Status.BAD_REQUEST);
+        }
+
+        if (!user.isEnabled()) {
+            event.error(Errors.USER_DISABLED);
+            throw new ErrorResponseException("invalid_grant", "User disabled", Response.Status.BAD_REQUEST);
+        }
+
+        if (!AuthenticationManager.isSessionValid(realm, userSession)) {
+            event.error(Errors.USER_SESSION_NOT_FOUND);
+            throw new ErrorResponseException("invalid_grant", "Session not active", Response.Status.BAD_REQUEST);
+        }
+
+        String adapterSessionId = formData.getFirst(AdapterConstants.APPLICATION_SESSION_STATE);
+        if (adapterSessionId != null) {
+            String adapterSessionHost = formData.getFirst(AdapterConstants.APPLICATION_SESSION_HOST);
+            logger.debugf("Adapter Session '%s' saved in ClientSession for client '%s'. Host is '%s'", adapterSessionId, client.getClientId(), adapterSessionHost);
+
+            event.detail(AdapterConstants.APPLICATION_SESSION_STATE, adapterSessionId);
+            clientSession.setNote(AdapterConstants.APPLICATION_SESSION_STATE, adapterSessionId);
+            event.detail(AdapterConstants.APPLICATION_SESSION_HOST, adapterSessionHost);
+            clientSession.setNote(AdapterConstants.APPLICATION_SESSION_HOST, adapterSessionHost);
+        }
+
+        AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
+
+        AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
+                .accessToken(token)
+                .generateIDToken()
+                .generateRefreshToken().build();
+
+        event.success();
+
+        return Cors.add(request, Response.ok(res).type(MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+    }
+
+    public Response buildRefreshToken(final MultivaluedMap<String, String> formData) {
+        ClientModel client = authorizeClient(formData);
+
+        String refreshToken = formData.getFirst(OAuth2Constants.REFRESH_TOKEN);
+        if (refreshToken == null) {
+            throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
+        }
+
+        AccessTokenResponse res;
+        try {
+            res = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event);
+        } catch (OAuthErrorException e) {
+            event.error(Errors.INVALID_TOKEN);
+            throw new ErrorResponseException(e.getError(), e.getDescription(), Response.Status.BAD_REQUEST);
+        }
+
+        event.success();
+
+        return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+    }
+
+    public Response buildResourceOwnerPasswordCredentialsGrant(final MultivaluedMap<String, String> formData) {
+        if (!realm.isPasswordCredentialGrantAllowed()) {
+            throw new ErrorResponseException("not_enabled", "Direct Grant REST API not enabled", Response.Status.FORBIDDEN);
+        }
+
+        event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
+
+        String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
+        if (username == null) {
+            event.error(Errors.USERNAME_MISSING);
+            throw new ErrorResponseException("invalid_request", "Missing parameter: username", Response.Status.UNAUTHORIZED);
+        }
+        event.detail(Details.USERNAME, username);
+
+        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
+        if (user != null) event.user(user);
+
+        ClientModel client = authorizeClient(formData);
+
+        AuthenticationManager.AuthenticationStatus authenticationStatus = authManager.authenticateForm(session, clientConnection, realm, formData);
+        Map<String, String> err;
+
+        switch (authenticationStatus) {
+            case SUCCESS:
+                break;
+            case ACCOUNT_TEMPORARILY_DISABLED:
+            case ACTIONS_REQUIRED:
+                event.error(Errors.USER_TEMPORARILY_DISABLED);
+                throw new ErrorResponseException("invalid_grant", "Account temporarily disabled", Response.Status.BAD_REQUEST);
+            case ACCOUNT_DISABLED:
+                event.error(Errors.USER_DISABLED);
+                throw new ErrorResponseException("invalid_grant", "Account disabled", Response.Status.BAD_REQUEST);
+            default:
+                event.error(Errors.INVALID_USER_CREDENTIALS);
+                throw new ErrorResponseException("invalid_grant", "Invalid user credentials", Response.Status.UNAUTHORIZED);
+        }
+
+        String scope = formData.getFirst(OAuth2Constants.SCOPE);
+
+        UserSessionProvider sessions = session.sessions();
+
+        UserSessionModel userSession = sessions.createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "oauth_credentials", false);
+        event.session(userSession);
+
+        ClientSessionModel clientSession = sessions.createClientSession(realm, client);
+        clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
+
+        TokenManager.attachClientSession(userSession, clientSession);
+
+        AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
+                .generateAccessToken(session, scope, client, user, userSession, clientSession)
+                .generateRefreshToken()
+                .generateIDToken()
+                .build();
+
+        event.success();
+
+        return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
+    }
+
+}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java
new file mode 100644
index 0000000..caef436
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java
@@ -0,0 +1,103 @@
+package org.keycloak.protocol.oidc.endpoints;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.ClientConnection;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.OAuthErrorException;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.services.ErrorResponseException;
+
+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.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ValidateTokenEndpoint {
+
+    private static final Logger logger = Logger.getLogger(ValidateTokenEndpoint.class);
+
+    @Context
+    private KeycloakSession session;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    @Context
+    private UriInfo uriInfo;
+
+    private TokenManager tokenManager;
+    private RealmModel realm;
+    private EventBuilder event;
+
+    public ValidateTokenEndpoint(TokenManager tokenManager, RealmModel realm, EventBuilder event) {
+        this.tokenManager = tokenManager;
+        this.realm = realm;
+        this.event = event;
+    }
+
+    /**
+     * Validate encoded access token.
+     *
+     * @param tokenString
+     * @return Unmarshalled token
+     */
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response validateAccessToken(@QueryParam("access_token") String tokenString) {
+        checkSsl();
+
+        event.event(EventType.VALIDATE_ACCESS_TOKEN);
+        AccessToken token = null;
+        try {
+            token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName());
+        } catch (Exception e) {
+            Map<String, String> err = new HashMap<String, String>();
+            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
+            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token invalid");
+            logger.error("Invalid token. Token verification failed.");
+            event.error(Errors.INVALID_TOKEN);
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
+                    .build();
+        }
+        event.user(token.getSubject()).session(token.getSessionState()).detail(Details.VALIDATE_ACCESS_TOKEN, token.getId());
+
+        try {
+            tokenManager.validateToken(session, uriInfo, clientConnection, realm, token);
+        } catch (OAuthErrorException e) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, e.getError());
+            if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
+            event.error(Errors.INVALID_TOKEN);
+            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
+        }
+        event.success();
+
+        return Response.ok(token, MediaType.APPLICATION_JSON_TYPE).build();
+    }
+
+    private void checkSsl() {
+        if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
+            throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN);
+        }
+    }
+
+}
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 9258264..3900f1a 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -46,11 +46,15 @@ public class OIDCLoginProtocol implements LoginProtocol {
     public static final String LOGIN_PROTOCOL = "openid-connect";
     public static final String STATE_PARAM = "state";
     public static final String SCOPE_PARAM = "scope";
+    public static final String CODE_PARAM = "code";
     public static final String RESPONSE_TYPE_PARAM = "response_type";
+    public static final String GRANT_TYPE_PARAM = "grant_type";
     public static final String REDIRECT_URI_PARAM = "redirect_uri";
     public static final String CLIENT_ID_PARAM = "client_id";
     public static final String PROMPT_PARAM = "prompt";
     public static final String LOGIN_HINT_PARAM = "login_hint";
+    public static final String K_IDP_HINT = "k_idp_hint";
+
     private static final Logger log = Logger.getLogger(OIDCLoginProtocol.class);
 
     protected KeycloakSession session;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
index 3de9a87..c2ec74b 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
@@ -2,83 +2,49 @@ package org.keycloak.protocol.oidc;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
-import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
-import org.jboss.resteasy.spi.BadRequestException;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.HttpResponse;
-import org.jboss.resteasy.spi.NotAcceptableException;
-import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
-import org.jboss.resteasy.spi.UnauthorizedException;
 import org.keycloak.ClientConnection;
-import org.keycloak.Config;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
 import org.keycloak.RSATokenVerifier;
-import org.keycloak.constants.AdapterConstants;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.jose.jwk.JWKBuilder;
 import org.keycloak.login.LoginFormsProvider;
-import org.keycloak.models.ApplicationModel;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.Constants;
-import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.RequiredCredentialModel;
-import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
+import org.keycloak.protocol.oidc.endpoints.LoginStatusIframeEndpoint;
+import org.keycloak.protocol.oidc.endpoints.LogoutEndpoint;
+import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
+import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint;
+import org.keycloak.protocol.oidc.endpoints.ValidateTokenEndpoint;
+import org.keycloak.protocol.oidc.representations.JSONWebKeySet;
 import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.AccessTokenResponse;
-import org.keycloak.representations.RefreshToken;
-import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
-import org.keycloak.services.managers.ClientSessionCode;
-import org.keycloak.services.managers.HttpAuthenticationManager;
-import org.keycloak.services.resources.Cors;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.flows.Flows;
-import org.keycloak.services.resources.flows.Urls;
-import org.keycloak.util.BasicAuthHelper;
-import org.keycloak.util.StreamUtil;
-import org.keycloak.util.UriUtils;
 
-import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
-import javax.ws.rs.HeaderParam;
-import javax.ws.rs.OPTIONS;
-import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.CacheControl;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.SecurityContext;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.Providers;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
-
-import static org.keycloak.constants.AdapterConstants.K_IDP_HINT;
 
 /**
  * Resource class for the oauth/openid connect token service
@@ -90,32 +56,16 @@ public class OIDCLoginProtocolService {
 
     protected static final Logger logger = Logger.getLogger(OIDCLoginProtocolService.class);
 
-    protected RealmModel realm;
-    protected TokenManager tokenManager;
+    private RealmModel realm;
+    private TokenManager tokenManager;
     private EventBuilder event;
-    protected AuthenticationManager authManager;
+    private AuthenticationManager authManager;
 
     @Context
-    protected Providers providers;
-    @Context
-    protected SecurityContext securityContext;
-    @Context
-    protected UriInfo uriInfo;
-    @Context
-    protected HttpHeaders headers;
-    @Context
-    protected HttpRequest request;
-    @Context
-    protected HttpResponse response;
-    @Context
-    protected KeycloakSession session;
-    @Context
-    protected ClientConnection clientConnection;
+    private UriInfo uriInfo;
 
-    /*
     @Context
-    protected ResourceContext resourceContext;
-    */
+    private KeycloakSession session;
 
     public OIDCLoginProtocolService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
         this.realm = realm;
@@ -133,12 +83,6 @@ public class OIDCLoginProtocolService {
         return baseUriBuilder.path(RealmsResource.class).path("{realm}/protocol/" + OIDCLoginProtocol.LOGIN_PROTOCOL);
     }
 
-    public static UriBuilder accessCodeToTokenUrl(UriInfo uriInfo) {
-        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
-        return accessCodeToTokenUrl(baseUriBuilder);
-
-    }
-
     public static UriBuilder accessCodeToTokenUrl(UriBuilder baseUriBuilder) {
         UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
         return uriBuilder.path(OIDCLoginProtocolService.class, "accessCodeToToken");
@@ -149,12 +93,6 @@ public class OIDCLoginProtocolService {
         return uriBuilder.path(OIDCLoginProtocolService.class, "validateAccessToken");
     }
 
-    public static UriBuilder grantAccessTokenUrl(UriInfo uriInfo) {
-        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
-        return grantAccessTokenUrl(baseUriBuilder);
-
-    }
-
     public static UriBuilder grantAccessTokenUrl(UriBuilder baseUriBuilder) {
         UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
         return uriBuilder.path(OIDCLoginProtocolService.class, "grantAccessToken");
@@ -186,811 +124,103 @@ public class OIDCLoginProtocolService {
     }
 
     /**
-     *
-     *
-     * @param client_id
-     * @param origin
-     * @return
-     */
-    @Path("login-status-iframe.html")
-    @GET
-    @Produces(MediaType.TEXT_HTML)
-    public Response getLoginStatusIframe(@QueryParam("client_id") String client_id,
-                                         @QueryParam("origin") String origin) {
-        if (!UriUtils.isOrigin(origin)) {
-            throw new BadRequestException("Invalid origin");
-        }
-
-        ClientModel client = realm.findClient(client_id);
-        if (client == null) {
-            throw new NotFoundException("could not find client");
-        }
-
-        InputStream is = getClass().getClassLoader().getResourceAsStream("login-status-iframe.html");
-        if (is == null) throw new NotFoundException("Could not find login-status-iframe.html ");
-
-        boolean valid = false;
-        for (String o : client.getWebOrigins()) {
-            if (o.equals("*") || o.equals(origin)) {
-                valid = true;
-                break;
-            }
-        }
-
-        for (String r : OIDCLoginProtocolService.resolveValidRedirects(uriInfo, client.getRedirectUris())) {
-            int i = r.indexOf('/', 8);
-            if (i != -1) {
-                r = r.substring(0, i);
-            }
-
-            if (r.equals(origin)) {
-                valid = true;
-                break;
-            }
-        }
-
-        if (!valid) {
-            throw new BadRequestException("Invalid origin");
-        }
-
-        try {
-            String file = StreamUtil.readString(is);
-            file = file.replace("ORIGIN", origin);
-
-            CacheControl cacheControl = new CacheControl();
-            cacheControl.setNoTransform(false);
-            cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
-
-            return Response.ok(file).cacheControl(cacheControl).build();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-
-    /**
-     * Direct grant REST invocation.  One stop call to obtain an access token.
-     *
-     * If the client is a confidential client
-     * you must include the client-id (application name or oauth client name) and secret in an Basic Auth Authorization header.
-     *
-     * If the client is a public client, then you must include a "client_id" form parameter with the app's or oauth client's name.
-     *
-     * The realm must be configured to allow these types of auth requests.  (Direct Grant API in admin console Settings page)
-     *
-     *
-     * @See  <a href="http://tools.ietf.org/html/rfc6749#section-4.3">http://tools.ietf.org/html/rfc6749#section-4.3</a>
-     *
-     * @param authorizationHeader
-     * @param form
-     * @return @see org.keycloak.representations.AccessTokenResponse
-     */
-    @Path("grants/access")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response grantAccessToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader,
-                                     final MultivaluedMap<String, String> form) {
-        if (!checkSsl()) {
-            return createError("https_required", "HTTPS required", Response.Status.FORBIDDEN);
-        }
-
-        if (!realm.isPasswordCredentialGrantAllowed()) {
-            return createError("not_enabled", "Direct Grant REST API not enabled", Response.Status.FORBIDDEN);
-        }
-
-        event.event(EventType.LOGIN).detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
-
-        String username = form.getFirst(AuthenticationManager.FORM_USERNAME);
-        if (username == null) {
-            event.error(Errors.USERNAME_MISSING);
-            throw new UnauthorizedException("No username");
-        }
-        event.detail(Details.USERNAME, username);
-
-        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
-        if (user != null) event.user(user);
-
-        ClientModel client = authorizeClient(authorizationHeader, form, event);
-
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            return createError("realm_disabled", "Realm is disabled", Response.Status.UNAUTHORIZED);
-        }
-
-        AuthenticationStatus authenticationStatus = authManager.authenticateForm(session, clientConnection, realm, form);
-        Map<String, String> err;
-
-        switch (authenticationStatus) {
-            case SUCCESS:
-                break;
-            case ACCOUNT_TEMPORARILY_DISABLED:
-            case ACTIONS_REQUIRED:
-                err = new HashMap<String, String>();
-                err.put(OAuth2Constants.ERROR, "invalid_grant");
-                err.put(OAuth2Constants.ERROR_DESCRIPTION, "AccountProvider temporarily disabled");
-                event.error(Errors.USER_TEMPORARILY_DISABLED);
-                return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                        .build();
-            case ACCOUNT_DISABLED:
-                err = new HashMap<String, String>();
-                err.put(OAuth2Constants.ERROR, "invalid_grant");
-                err.put(OAuth2Constants.ERROR_DESCRIPTION, "AccountProvider disabled");
-                event.error(Errors.USER_DISABLED);
-                return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                        .build();
-            default:
-                err = new HashMap<String, String>();
-                err.put(OAuth2Constants.ERROR, "invalid_grant");
-                err.put(OAuth2Constants.ERROR_DESCRIPTION, "Invalid user credentials");
-                event.error(Errors.INVALID_USER_CREDENTIALS);
-                return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                        .build();
-        }
-
-        String scope = form.getFirst(OAuth2Constants.SCOPE);
-
-        UserSessionProvider sessions = session.sessions();
-
-        UserSessionModel userSession = sessions.createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "oauth_credentials", false);
-        event.session(userSession);
-
-        ClientSessionModel clientSession = sessions.createClientSession(realm, client);
-        clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
-
-        TokenManager.attachClientSession(userSession, clientSession);
-
-        AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
-                .generateAccessToken(session, scope, client, user, userSession, clientSession)
-                .generateRefreshToken()
-                .generateIDToken()
-                .build();
-
-        event.success();
-
-        return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
-    }
-
-    /**
-     * Validate encoded access token.
-     *
-     * @param tokenString
-     * @return Unmarshalled token
-     */
-    @Path("validate")
-    @GET
-    @NoCache
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response validateAccessToken(@QueryParam("access_token") String tokenString) {
-        if (!checkSsl()) {
-            return createError("https_required", "HTTPS required", Response.Status.FORBIDDEN);
-        }
-        event.event(EventType.VALIDATE_ACCESS_TOKEN);
-        AccessToken token = null;
-        try {
-            token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName());
-        } catch (Exception e) {
-            Map<String, String> err = new HashMap<String, String>();
-            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
-            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token invalid");
-            logger.error("Invalid token. Token verification failed.");
-            event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                    .build();
-        }
-        event.user(token.getSubject()).session(token.getSessionState()).detail(Details.VALIDATE_ACCESS_TOKEN, token.getId());
-
-        try {
-            tokenManager.validateToken(session, uriInfo, clientConnection, realm, token);
-        } catch (OAuthErrorException e) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, e.getError());
-            if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
-            event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
-        }
-        event.success();
-
-        return Response.ok(token, MediaType.APPLICATION_JSON_TYPE).build();
-    }
-
-    /**
-     * CORS preflight path for refresh token requests
-     *
-     * @return
+     * Authorization endpoint
      */
-    @Path("refresh")
-    @OPTIONS
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response refreshAccessTokenPreflight() {
-        if (logger.isDebugEnabled()) {
-            logger.debugv("cors request from: {0}", request.getHttpHeaders().getRequestHeaders().getFirst("Origin"));
-        }
-        return Cors.add(request, Response.ok()).auth().preflight().build();
+    @Path("auth")
+    public Object auth() {
+        AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint.init();
     }
 
     /**
-     * URL for making refresh token requests.
-     *
-     * @See <a href="http://tools.ietf.org/html/rfc6749#section-6">http://tools.ietf.org/html/rfc6749#section-6</a>
-     *
-     * If the client is a confidential client
-     * you must include the client-id (application name or oauth client name) and secret in an Basic Auth Authorization header.
-     *
-     * If the client is a public client, then you must include a "client_id" form parameter with the app's or oauth client's name.
-     *
-     * @param authorizationHeader
-     * @param form
-     * @return
+     * Registration endpoint
      */
-    @Path("refresh")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response refreshAccessToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader,
-                                       final MultivaluedMap<String, String> form) {
-        if (!checkSsl()) {
-            return createError("https_required", "HTTPS required", Response.Status.FORBIDDEN);
-        }
-
-        event.event(EventType.REFRESH_TOKEN);
-
-        ClientModel client = authorizeClient(authorizationHeader, form, event);
-        String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
-        if (refreshToken == null) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_REQUEST);
-            error.put(OAuth2Constants.ERROR_DESCRIPTION, "No refresh token");
-            event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
-        }
-        AccessTokenResponse res;
-        try {
-            res = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event);
-        } catch (OAuthErrorException e) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, e.getError());
-            if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
-            event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
-        }
-
-
-        event.success();
-
-        return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+    @Path("registrations")
+    public Object registerPage() {
+        AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint.register().init();
     }
 
     /**
-     * CORS preflight path for access code to token
-     *
-     * @return
+     * Token endpoint
      */
-    @Path("access/codes")
-    @OPTIONS
-    @Produces("application/json")
-    public Response accessCodeToTokenPreflight() {
-        if (logger.isDebugEnabled()) {
-            logger.debugv("cors request from: {0}", request.getHttpHeaders().getRequestHeaders().getFirst("Origin"));
-        }
-        return Cors.add(request, Response.ok()).auth().preflight().build();
+    @Path("token")
+    public Object token() {
+        TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint.init();
     }
 
-    /**
-     * URL invoked by adapter to turn an access code to access token
-     *
-     * @See <a href="http://tools.ietf.org/html/rfc6749#section-4.1">http://tools.ietf.org/html/rfc6749#section-4.1</a>
-     *
-     * @param authorizationHeader
-     * @param formData
-     * @return
-     */
-    @Path("access/codes")
-    @POST
-    @Produces("application/json")
-    public Response accessCodeToToken(@HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, final MultivaluedMap<String, String> formData) {
-        if (!checkSsl()) {
-            throw new ForbiddenException("HTTPS required");
-        }
-
-        event.event(EventType.CODE_TO_TOKEN);
-
-        if (!realm.isEnabled()) {
-            event.error(Errors.REALM_DISABLED);
-            throw new UnauthorizedException("Realm not enabled");
-        }
-
-        String code = formData.getFirst(OAuth2Constants.CODE);
-        if (code == null) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, "invalid_request");
-            error.put(OAuth2Constants.ERROR_DESCRIPTION, "code not specified");
-            event.error(Errors.INVALID_CODE);
-            throw new BadRequestException("Code not specified", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
-        }
-        ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
-        if (accessCode == null) {
-            String[] parts = code.split("\\.");
-            if (parts.length == 2) {
-                try {
-                    event.detail(Details.CODE_ID, new String(parts[1]));
-                } catch (Throwable t) {
-                }
-            }
-            Map<String, String> res = new HashMap<String, String>();
-            res.put(OAuth2Constants.ERROR, "invalid_grant");
-            res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code not found");
-            event.error(Errors.INVALID_CODE);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
-                    .build();
-        }
-
-        ClientSessionModel clientSession = accessCode.getClientSession();
-        event.detail(Details.CODE_ID, clientSession.getId());
-        if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN)) {
-            Map<String, String> res = new HashMap<String, String>();
-            res.put(OAuth2Constants.ERROR, "invalid_grant");
-            res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code is expired");
-            event.error(Errors.INVALID_CODE);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
-                    .build();
-        }
-
-        accessCode.setAction(null);
-        UserSessionModel userSession = clientSession.getUserSession();
-        event.user(userSession.getUser());
-        event.session(userSession.getId());
-
-        ClientModel client = authorizeClient(authorizationHeader, formData, event);
-
-        String redirectUri = clientSession.getNote(OIDCLoginProtocol.REDIRECT_URI_PARAM);
-        if (redirectUri != null && !redirectUri.equals(formData.getFirst(OAuth2Constants.REDIRECT_URI))) {
-            Map<String, String> res = new HashMap<String, String>();
-            res.put(OAuth2Constants.ERROR, "invalid_grant");
-            res.put(OAuth2Constants.ERROR_DESCRIPTION, "Incorrect redirect_uri");
-            event.error(Errors.INVALID_CODE);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
-                    .build();
-        }
-
-        if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
-            Map<String, String> res = new HashMap<String, String>();
-            res.put(OAuth2Constants.ERROR, "invalid_grant");
-            res.put(OAuth2Constants.ERROR_DESCRIPTION, "Auth error");
-            event.error(Errors.INVALID_CODE);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
-                    .build();
-        }
-
-        UserModel user = session.users().getUserById(userSession.getUser().getId(), realm);
-        if (user == null) {
-            Map<String, String> res = new HashMap<String, String>();
-            res.put(OAuth2Constants.ERROR, "invalid_grant");
-            res.put(OAuth2Constants.ERROR_DESCRIPTION, "User not found");
-            event.error(Errors.INVALID_CODE);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
-                    .build();
-        }
-
-        if (!user.isEnabled()) {
-            Map<String, String> res = new HashMap<String, String>();
-            res.put(OAuth2Constants.ERROR, "invalid_grant");
-            res.put(OAuth2Constants.ERROR_DESCRIPTION, "User disabled");
-            event.error(Errors.INVALID_CODE);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
-                    .build();
-        }
-
-        if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection);
-            Map<String, String> res = new HashMap<String, String>();
-            res.put(OAuth2Constants.ERROR, "invalid_grant");
-            res.put(OAuth2Constants.ERROR_DESCRIPTION, "Session not active");
-            event.error(Errors.INVALID_CODE);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
-                    .build();
-        }
-
-        String adapterSessionId = formData.getFirst(AdapterConstants.APPLICATION_SESSION_STATE);
-        if (adapterSessionId != null) {
-            String adapterSessionHost = formData.getFirst(AdapterConstants.APPLICATION_SESSION_HOST);
-            logger.debugf("Adapter Session '%s' saved in ClientSession for client '%s'. Host is '%s'", adapterSessionId, client.getClientId(), adapterSessionHost);
-
-            event.detail(AdapterConstants.APPLICATION_SESSION_STATE, adapterSessionId);
-            clientSession.setNote(AdapterConstants.APPLICATION_SESSION_STATE, adapterSessionId);
-            event.detail(AdapterConstants.APPLICATION_SESSION_HOST, adapterSessionHost);
-            clientSession.setNote(AdapterConstants.APPLICATION_SESSION_HOST, adapterSessionHost);
-        }
-
-        AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
-
-        AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
-                .accessToken(token)
-                .generateIDToken()
-                .generateRefreshToken().build();
-
-        event.success();
-
-        return Cors.add(request, Response.ok(res)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+    @Path("login")
+    @Deprecated
+    public Object loginPage() {
+        AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint.legacy(OIDCLoginProtocol.CODE_PARAM).init();
     }
 
-    @Path("userinfo")
-    public Object issueUserInfo() {
-        UserInfoService userInfoEndpoint = new UserInfoService(this);
-
-        ResteasyProviderFactory.getInstance().injectProperties(userInfoEndpoint);
-
-        return userInfoEndpoint;
+    @Path("login-status-iframe.html")
+    public Object getLoginStatusIframe() {
+        LoginStatusIframeEndpoint endpoint = new LoginStatusIframeEndpoint(realm);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint;
     }
 
-    protected ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData, EventBuilder event) {
-        ClientModel client = authorizeClientBase(authorizationHeader, formData, event, realm);
-
-        if ( (client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, "invalid_client");
-            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Bearer-only not allowed");
-            event.error(Errors.INVALID_CLIENT);
-            throw new BadRequestException("Bearer-only not allowed", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
-        }
-
-        return client;
+    @Path("grants/access")
+    @Deprecated
+    public Object grantAccessToken() {
+        TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint.legacy(OAuth2Constants.PASSWORD).init();
     }
 
-    // Just authorize client without further checking about client type
-    public static ClientModel authorizeClientBase(String authorizationHeader, MultivaluedMap<String, String> formData, EventBuilder event, RealmModel realm) {
-        String client_id;
-        String clientSecret;
-        if (authorizationHeader != null) {
-            String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
-            if (usernameSecret == null) {
-                throw new UnauthorizedException("Bad Authorization header", Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + realm.getName() + "\"").build());
-            }
-            client_id = usernameSecret[0];
-            clientSecret = usernameSecret[1];
-        } else {
-            client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
-            clientSecret = formData.getFirst("client_secret");
-        }
-
-        if (client_id == null) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, "invalid_client");
-            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Could not find client");
-            throw new BadRequestException("Could not find client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
-        }
-
-        event.client(client_id);
-
-        ClientModel client = realm.findClient(client_id);
-        if (client == null) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, "invalid_client");
-            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Could not find client");
-            event.error(Errors.CLIENT_NOT_FOUND);
-            throw new BadRequestException("Could not find client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
-        }
-
-        if (!client.isEnabled()) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, "invalid_client");
-            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Client is not enabled");
-            event.error(Errors.CLIENT_DISABLED);
-            throw new BadRequestException("Client is not enabled", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
-        }
-
-        if (!client.isPublicClient()) {
-            if (clientSecret == null || !client.validateSecret(clientSecret)) {
-                Map<String, String> error = new HashMap<String, String>();
-                error.put(OAuth2Constants.ERROR, "unauthorized_client");
-                event.error(Errors.INVALID_CLIENT_CREDENTIALS);
-                throw new BadRequestException("Unauthorized Client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
-            }
-        }
-
-        return client;
+    @Path("refresh")
+    @Deprecated
+    public Object refreshAccessToken() {
+        TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint.legacy(OAuth2Constants.REFRESH_TOKEN).init();
     }
 
-    /**
-     * checks input and initializes variables based on a front page action like the login page or registration page
-     *
-     */
-    private class FrontPageInitializer {
-        protected String clientId;
-        protected String redirect;
-        protected String state;
-        protected String scopeParam;
-        protected String responseType;
-        protected String loginHint;
-        protected String prompt;
-        protected ClientSessionModel clientSession;
-
-        public Response processInput() {
-            event.client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
-            if (!checkSsl()) {
-                event.error(Errors.SSL_REQUIRED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
-            }
-            if (!realm.isEnabled()) {
-                event.error(Errors.REALM_DISABLED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
-            }
-
-            clientSession = null;
-            ClientModel client = realm.findClient(clientId);
-            if (client == null) {
-                event.error(Errors.CLIENT_NOT_FOUND);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
-            }
-
-            if (!client.isEnabled()) {
-                event.error(Errors.CLIENT_DISABLED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
-            }
-            if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
-                event.error(Errors.NOT_ALLOWED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login");
-            }
-            if (client.isDirectGrantsOnly()) {
-                event.error(Errors.NOT_ALLOWED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
-            }
-            String redirectUriParam = redirect;
-            redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
-            if (redirect == null) {
-                event.error(Errors.INVALID_REDIRECT_URI);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
-            }
-            clientSession = session.sessions().createClientSession(realm, client);
-            clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
-            clientSession.setRedirectUri(redirect);
-            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
-            clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
-            clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
-            clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam);
-            if (scopeParam != null) clientSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, scopeParam);
-            if (responseType != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType);
-            if (loginHint != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
-            if (prompt != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, prompt);
-            return null;
-        }
+    @Path("access/codes")
+    @Deprecated
+    public Object accessCodeToToken() {
+        TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint.legacy(OAuth2Constants.AUTHORIZATION_CODE).init();
     }
 
-    /**
-     * Login page.  Must be redirected to from the application or oauth client.
-     *
-     * @See <a href="http://tools.ietf.org/html/rfc6749#section-4.1">http://tools.ietf.org/html/rfc6749#section-4.1</a>
-     *
-     *
-     * @param responseType
-     * @param redirect
-     * @param clientId
-     * @param scopeParam
-     * @param state
-     * @param prompt
-     * @return
-     */
-    @Path("login")
-    @GET
-    public Response loginPage(@QueryParam(OIDCLoginProtocol.RESPONSE_TYPE_PARAM) String responseType,
-                              @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirect,
-                              @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId,
-                              @QueryParam(OIDCLoginProtocol.SCOPE_PARAM) String scopeParam,
-                              @QueryParam(OIDCLoginProtocol.STATE_PARAM) String state,
-                              @QueryParam(OIDCLoginProtocol.PROMPT_PARAM) String prompt,
-                              @QueryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM) String loginHint,
-                              @QueryParam(K_IDP_HINT) String idpHint) {
-        event.event(EventType.LOGIN);
-        FrontPageInitializer pageInitializer = new FrontPageInitializer();
-        pageInitializer.responseType = responseType;
-        pageInitializer.redirect = redirect;
-        pageInitializer.clientId = clientId;
-        pageInitializer.scopeParam = scopeParam;
-        pageInitializer.state = state;
-        pageInitializer.prompt = prompt;
-        pageInitializer.loginHint = loginHint;
-        Response response = pageInitializer.processInput();
-        if (response != null) return response;
-        ClientSessionModel clientSession = pageInitializer.clientSession;
-        String accessCode = new ClientSessionCode(realm, clientSession).getCode();
-
-        if (idpHint != null && !"".equals(idpHint)) {
-            IdentityProviderModel identityProviderModel = realm.getIdentityProviderById(idpHint);
-
-            if (identityProviderModel == null) {
-                return Flows.forms(session, realm, null, uriInfo)
-                        .setError("Could not find an identity provider with the identifier [" + idpHint + "].")
-                        .createErrorPage();
-            }
-            return redirectToIdentityProvider(idpHint, accessCode);
-        }
-
-        response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
-        if (response != null) return response;
-
-        // SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?)
-        HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event);
-        HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate();
-        if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse();
-
-        if (prompt != null && prompt.equals("none")) {
-            OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo);
-            return oauth.cancelLogin(clientSession);
-        }
-
-        List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
-        for (IdentityProviderModel identityProvider : identityProviders) {
-            if (identityProvider.isAuthenticateByDefault()) {
-                return redirectToIdentityProvider(identityProvider.getId(), accessCode);
-            }
-        }
-
-        List<RequiredCredentialModel> requiredCredentials = realm.getRequiredCredentials();
-        if (requiredCredentials.isEmpty()) {
-            if (!identityProviders.isEmpty()) {
-                if (identityProviders.size() == 1) {
-                    return redirectToIdentityProvider(identityProviders.get(0).getId(), accessCode);
-                }
-
-                return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + this.realm.getName() + "] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.").createErrorPage();
-            }
-
-            return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + this.realm.getName() + "] does not support any credential type.").createErrorPage();
-        }
-
-        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
-                .setClientSessionCode(accessCode);
-
-        // Attach state from SPNEGO authentication
-        if (httpAuthOutput.getChallenge() != null) {
-            httpAuthOutput.getChallenge().sendChallenge(forms);
-        }
-
-        String rememberMeUsername = AuthenticationManager.getRememberMeUsername(realm, headers);
-
-        if (loginHint != null || rememberMeUsername != null) {
-            MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
-
-            if (loginHint != null) {
-                formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
-            } else {
-                formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
-                formData.add("rememberMe", "on");
-            }
-
-            forms.setFormData(formData);
-        }
+    @Path("validate")
+    public Object validateAccessToken(@QueryParam("access_token") String tokenString) {
+        ValidateTokenEndpoint endpoint = new ValidateTokenEndpoint(tokenManager, realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint;
 
-        return forms.createLogin();
     }
 
-    /**
-     * Registration page.  Must be redirected to from login page.
-     *
-     * @param responseType
-     * @param redirect
-     * @param clientId
-     * @param scopeParam
-     * @param state
-     * @return
-     */
-    @Path("registrations")
     @GET
-    public Response registerPage(@QueryParam(OIDCLoginProtocol.RESPONSE_TYPE_PARAM) String responseType,
-                                 @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirect,
-                                 @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId,
-                                 @QueryParam(OIDCLoginProtocol.SCOPE_PARAM) String scopeParam,
-                                 @QueryParam(OIDCLoginProtocol.STATE_PARAM) String state) {
-        event.event(EventType.REGISTER);
-        if (!realm.isRegistrationAllowed()) {
-            event.error(Errors.REGISTRATION_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed");
-        }
-
-        FrontPageInitializer pageInitializer = new FrontPageInitializer();
-        pageInitializer.responseType = responseType;
-        pageInitializer.redirect = redirect;
-        pageInitializer.clientId = clientId;
-        pageInitializer.scopeParam = scopeParam;
-        pageInitializer.state = state;
-        Response response = pageInitializer.processInput();
-        if (response != null) return response;
-        ClientSessionModel clientSession = pageInitializer.clientSession;
-
-
-        authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
-
-        return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
-                .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode())
-                .createRegistration();
+    @Path("certs")
+    @Produces(MediaType.APPLICATION_JSON)
+    public JSONWebKeySet certs() {
+        JSONWebKeySet keySet = new JSONWebKeySet();
+        keySet.setKeys(new JWK[]{JWKBuilder.create().rs256(realm.getPublicKey())});
+        return keySet;
     }
 
-    /**
-     * Logout user session.  User must be logged in via a session cookie.
-     *
-     * @param redirectUri
-     * @return
-     */
-    @Path("logout")
-    @GET
-    @NoCache
-    public Response logout(final @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri) {
-        event.event(EventType.LOGOUT);
-        if (redirectUri != null) {
-            event.detail(Details.REDIRECT_URI, redirectUri);
-        }
-        // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
-        AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
-        if (authResult != null) {
-            logout(authResult.getSession());
-        }
-
-        if (redirectUri != null) {
-            String validatedRedirect = verifyRealmRedirectUri(uriInfo, redirectUri, realm);
-            if (validatedRedirect == null) {
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
-            }
-            return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
-        } else {
-            return Response.ok().build();
-        }
+    @Path("userinfo")
+    public Object issueUserInfo() {
+        UserInfoEndpoint endpoint = new UserInfoEndpoint(tokenManager, realm);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint;
     }
 
-    /**
-     * Logout a session via a non-browser invocation.  Similar signature to refresh token except there is no grant_type.
-     * You must pass in the refresh token and
-     * authenticate the client if it is not public.
-     *
-     * If the client is a confidential client
-     * you must include the client-id (application name or oauth client name) and secret in an Basic Auth Authorization header.
-     *
-     * If the client is a public client, then you must include a "client_id" form parameter with the app's or oauth client's name.
-     *
-     * returns 204 if successful, 400 if not with a json error response.
-     *
-     * @param authorizationHeader
-     * @param form
-     * @return
-     */
     @Path("logout")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response logoutToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader,
-                                       final MultivaluedMap<String, String> form) {
-        if (!checkSsl()) {
-            throw new NotAcceptableException("HTTPS required");
-        }
-
-        event.event(EventType.LOGOUT);
-
-        ClientModel client = authorizeClient(authorizationHeader, form, event);
-        String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
-        if (refreshToken == null) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_REQUEST);
-            error.put(OAuth2Constants.ERROR_DESCRIPTION, "No refresh token");
-            event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
-        }
-        try {
-            RefreshToken token = tokenManager.verifyRefreshToken(realm, refreshToken);
-            UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
-            if (userSessionModel != null) {
-                logout(userSessionModel);
-            }
-        } catch (OAuthErrorException e) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, e.getError());
-            if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
-            event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
-        }
-       return Cors.add(request, Response.noContent()).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
-    }
-
-    private void logout(UserSessionModel userSession) {
-        authManager.logout(session, realm, userSession, uriInfo, clientConnection);
-        event.user(userSession.getUser()).session(userSession).success();
+    public Object logout() {
+        LogoutEndpoint endpoint = new LogoutEndpoint(tokenManager, authManager, realm, event);
+        ResteasyProviderFactory.getInstance().injectProperties(endpoint);
+        return endpoint;
     }
 
     @Path("oauth/oob")
@@ -1004,146 +234,4 @@ public class OIDCLoginProtocolService {
         }
     }
 
-    public static boolean matchesRedirects(Set<String> validRedirects, String redirect) {
-        for (String validRedirect : validRedirects) {
-            if (validRedirect.endsWith("*")) {
-                // strip off *
-                int length = validRedirect.length() - 1;
-                validRedirect = validRedirect.substring(0, length);
-                if (redirect.startsWith(validRedirect)) return true;
-                // strip off trailing '/'
-                if (length - 1 > 0 && validRedirect.charAt(length - 1) == '/') length--;
-                validRedirect = validRedirect.substring(0, length);
-                if (validRedirect.equals(redirect)) return true;
-            } else if (validRedirect.equals(redirect)) return true;
-        }
-        return false;
-    }
-
-    public static Set<String> getValidateRedirectUris(RealmModel realm) {
-        Set<String> redirects = new HashSet<String>();
-        for (ApplicationModel client : realm.getApplications()) {
-            for (String redirect : client.getRedirectUris()) {
-                redirects.add(redirect);
-            }
-        }
-        for (OAuthClientModel client : realm.getOAuthClients()) {
-            for (String redirect : client.getRedirectUris()) {
-                redirects.add(redirect);
-            }
-        }
-        return redirects;
-    }
-
-    public static String verifyRealmRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm) {
-        Set<String> validRedirects = getValidateRedirectUris(realm);
-        return verifyRedirectUri(uriInfo, redirectUri, realm, validRedirects);
-    }
-
-    public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
-        Set<String> validRedirects = client.getRedirectUris();
-        return verifyRedirectUri(uriInfo, redirectUri, realm, validRedirects);
-    }
-
-    public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, Set<String> validRedirects) {
-        if (redirectUri == null) {
-            if (validRedirects.size() != 1) return null;
-            String validRedirect = validRedirects.iterator().next();
-            int idx = validRedirect.indexOf("/*");
-            if (idx > -1) {
-                validRedirect = validRedirect.substring(0, idx);
-            }
-            redirectUri = validRedirect;
-        } else if (validRedirects.isEmpty()) {
-            logger.debug("No Redirect URIs supplied");
-            redirectUri = null;
-        } else {
-            String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
-            Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects);
-
-            boolean valid = matchesRedirects(resolveValidRedirects, r);
-
-            if (!valid && r.startsWith(Constants.INSTALLED_APP_URL) && r.indexOf(':', Constants.INSTALLED_APP_URL.length()) >= 0) {
-                int i = r.indexOf(':', Constants.INSTALLED_APP_URL.length());
-
-                StringBuilder sb = new StringBuilder();
-                sb.append(r.substring(0, i));
-
-                i = r.indexOf('/', i);
-                if (i >= 0) {
-                    sb.append(r.substring(i));
-                }
-
-                r = sb.toString();
-
-                valid = matchesRedirects(resolveValidRedirects, r);
-            }
-            if (valid && redirectUri.startsWith("/")) {
-                redirectUri = relativeToAbsoluteURI(uriInfo, redirectUri);
-            }
-            redirectUri = valid ? redirectUri : null;
-        }
-
-        if (Constants.INSTALLED_APP_URN.equals(redirectUri)) {
-            return Urls.realmInstalledAppUrnCallback(uriInfo.getBaseUri(), realm.getName()).toString();
-        } else {
-            return redirectUri;
-        }
-    }
-
-    public static Set<String> resolveValidRedirects(UriInfo uriInfo, Set<String> validRedirects) {
-        // If the valid redirect URI is relative (no scheme, host, port) then use the request's scheme, host, and port
-        Set<String> resolveValidRedirects = new HashSet<String>();
-        for (String validRedirect : validRedirects) {
-            resolveValidRedirects.add(validRedirect); // add even relative urls.
-            if (validRedirect.startsWith("/")) {
-                validRedirect = relativeToAbsoluteURI(uriInfo, validRedirect);
-                logger.debugv("replacing relative valid redirect with: {0}", validRedirect);
-                resolveValidRedirects.add(validRedirect);
-            }
-        }
-        return resolveValidRedirects;
-    }
-
-    public static String relativeToAbsoluteURI(UriInfo uriInfo, String relative) {
-        URI baseUri = uriInfo.getBaseUri();
-        String uri = baseUri.getScheme() + "://" + baseUri.getHost();
-        if (baseUri.getPort() != -1) {
-            uri += ":" + baseUri.getPort();
-        }
-        relative = uri + relative;
-        return relative;
-    }
-
-    private boolean checkSsl() {
-        if (uriInfo.getBaseUri().getScheme().equals("https")) {
-            return true;
-        } else {
-            return !realm.getSslRequired().isRequired(clientConnection);
-        }
-    }
-
-    private Response createError(String error, String errorDescription, Response.Status status) {
-        Map<String, String> e = new HashMap<String, String>();
-        e.put(OAuth2Constants.ERROR, error);
-        if (errorDescription != null) {
-            e.put(OAuth2Constants.ERROR_DESCRIPTION, errorDescription);
-        }
-        return Response.status(status).entity(e).type("application/json").build();
-    }
-
-    private Response redirectToIdentityProvider(String providerId, String accessCode) {
-        logger.debug("Automatically redirect to identity provider: " + providerId);
-        return Response.temporaryRedirect(
-                Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), providerId, this.realm.getName(), accessCode))
-                .build();
-    }
-
-    TokenManager getTokenManager() {
-        return this.tokenManager;
-    }
-
-    RealmModel getRealm() {
-        return this.realm;
-    }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
new file mode 100644
index 0000000..54e4009
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
@@ -0,0 +1,70 @@
+package org.keycloak.protocol.oidc;
+
+import org.keycloak.OAuth2Constants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
+import org.keycloak.services.resources.RealmsResource;
+import org.keycloak.wellknown.WellKnownProvider;
+
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OIDCWellKnownProvider implements WellKnownProvider {
+
+    public static final List<String> DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = list("RS256");
+
+    public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN);
+
+    public static final List<String> DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE);
+
+    public static final List<String> DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public");
+
+    public static final List<String> DEFAULT_RESPONSE_MODES_SUPPORTED = list("query");
+
+    @Override
+    public Object getConfig(RealmModel realm, UriInfo uriInfo) {
+        UriBuilder uriBuilder = RealmsResource.protocolUrl(uriInfo);
+
+        OIDCConfigurationRepresentation config = new OIDCConfigurationRepresentation();
+        config.setIssuer(realm.getName());
+        config.setAuthorizationEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
+        config.setTokenEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
+        config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
+        config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
+
+        config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
+        config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
+        config.setSubjectTypesSupported(DEFAULT_SUBJECT_TYPES_SUPPORTED);
+        config.setResponseModesSupported(DEFAULT_RESPONSE_MODES_SUPPORTED);
+
+        if (!realm.isPasswordCredentialGrantAllowed()) {
+            config.setGrantTypesSupported(DEFAULT_GRANT_TYPES_SUPPORTED);
+        } else {
+            List<String> grantTypes = new LinkedList<>(DEFAULT_GRANT_TYPES_SUPPORTED);
+            grantTypes.add(OAuth2Constants.PASSWORD);
+            config.setGrantTypesSupported(grantTypes);
+        }
+
+        return config;
+    }
+
+    @Override
+    public void close() {
+    }
+
+    private static List<String> list(String... values) {
+        List<String> s = new LinkedList<>();
+        for (String v : values) {
+            s.add(v);
+        }
+        return s;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProviderFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProviderFactory.java
new file mode 100644
index 0000000..e49a993
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProviderFactory.java
@@ -0,0 +1,40 @@
+package org.keycloak.protocol.oidc;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.wellknown.WellKnownProvider;
+import org.keycloak.wellknown.WellKnownProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OIDCWellKnownProviderFactory implements WellKnownProviderFactory {
+
+    private WellKnownProvider provider;
+
+    @Override
+    public WellKnownProvider create(KeycloakSession session) {
+        return provider;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+        provider = new OIDCWellKnownProvider();
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+        provider = null;
+    }
+
+    @Override
+    public String getId() {
+        return "openid-configuration";
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/JSONWebKeySet.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/JSONWebKeySet.java
new file mode 100644
index 0000000..3ac9547
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/JSONWebKeySet.java
@@ -0,0 +1,22 @@
+package org.keycloak.protocol.oidc.representations;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.keycloak.jose.jwk.JWK;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JSONWebKeySet {
+
+    @JsonProperty("keys")
+    private JWK[] keys;
+
+    public JWK[] getKeys() {
+        return keys;
+    }
+
+    public void setKeys(JWK[] keys) {
+        this.keys = keys;
+    }
+
+}
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
new file mode 100644
index 0000000..0760b64
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
@@ -0,0 +1,123 @@
+package org.keycloak.protocol.oidc.representations;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+
+public class OIDCConfigurationRepresentation {
+
+    @JsonProperty("issuer")
+    private String issuer;
+
+    @JsonProperty("authorization_endpoint")
+    private String authorizationEndpoint;
+
+    @JsonProperty("token_endpoint")
+    private String tokenEndpoint;
+
+    @JsonProperty("userinfo_endpoint")
+    private String userinfoEndpoint;
+
+    @JsonProperty("jwks_uri")
+    private String jwksUri;
+
+    @JsonProperty("grant_types_supported")
+    private List<String> grantTypesSupported;
+
+    @JsonProperty("response_types_supported")
+    private List<String> responseTypesSupported;
+
+    @JsonProperty("subject_types_supported")
+    private List<String> subjectTypesSupported;
+
+    @JsonProperty("id_token_signing_alg_values_supported")
+    private List<String> idTokenSigningAlgValuesSupported;
+
+    @JsonProperty("response_modes_supported")
+    private List<String> responseModesSupported;
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    public void setIssuer(String issuer) {
+        this.issuer = issuer;
+    }
+
+    public String getAuthorizationEndpoint() {
+        return authorizationEndpoint;
+    }
+
+    public void setAuthorizationEndpoint(String authorizationEndpoint) {
+        this.authorizationEndpoint = authorizationEndpoint;
+    }
+
+    public String getTokenEndpoint() {
+        return tokenEndpoint;
+    }
+
+    public void setTokenEndpoint(String tokenEndpoint) {
+        this.tokenEndpoint = tokenEndpoint;
+    }
+
+    public String getUserinfoEndpoint() {
+        return userinfoEndpoint;
+    }
+
+    public void setUserinfoEndpoint(String userinfoEndpoint) {
+        this.userinfoEndpoint = userinfoEndpoint;
+    }
+
+    public String getJwksUri() {
+        return jwksUri;
+    }
+
+    public void setJwksUri(String jwksUri) {
+        this.jwksUri = jwksUri;
+    }
+
+    public List<String> getGrantTypesSupported() {
+        return grantTypesSupported;
+    }
+
+    public void setGrantTypesSupported(List<String> grantTypesSupported) {
+        this.grantTypesSupported = grantTypesSupported;
+    }
+
+    public List<String> getResponseTypesSupported() {
+        return responseTypesSupported;
+    }
+
+    public void setResponseTypesSupported(List<String> responseTypesSupported) {
+        this.responseTypesSupported = responseTypesSupported;
+    }
+
+    public List<String> getSubjectTypesSupported() {
+        return subjectTypesSupported;
+    }
+
+    public void setSubjectTypesSupported(List<String> subjectTypesSupported) {
+        this.subjectTypesSupported = subjectTypesSupported;
+    }
+
+    public List<String> getIdTokenSigningAlgValuesSupported() {
+        return idTokenSigningAlgValuesSupported;
+    }
+
+    public void setIdTokenSigningAlgValuesSupported(List<String> idTokenSigningAlgValuesSupported) {
+        this.idTokenSigningAlgValuesSupported = idTokenSigningAlgValuesSupported;
+    }
+
+    public List<String> getResponseModesSupported() {
+        return responseModesSupported;
+    }
+
+    public void setResponseModesSupported(List<String> responseModesSupported) {
+        this.responseModesSupported = responseModesSupported;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
new file mode 100644
index 0000000..1a78b64
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
@@ -0,0 +1,76 @@
+package org.keycloak.protocol.oidc.utils;
+
+import org.jboss.resteasy.spi.BadRequestException;
+import org.jboss.resteasy.spi.UnauthorizedException;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.util.BasicAuthHelper;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AuthorizeClientUtil {
+
+    public static ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData, EventBuilder event, RealmModel realm) {
+        String client_id;
+        String clientSecret;
+        if (authorizationHeader != null) {
+            String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
+            if (usernameSecret == null) {
+                throw new UnauthorizedException("Bad Authorization header", Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + realm.getName() + "\"").build());
+            }
+            client_id = usernameSecret[0];
+            clientSecret = usernameSecret[1];
+        } else {
+            client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
+            clientSecret = formData.getFirst("client_secret");
+        }
+
+        if (client_id == null) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, "invalid_client");
+            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Could not find client");
+            throw new BadRequestException("Could not find client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+        }
+
+        event.client(client_id);
+
+        ClientModel client = realm.findClient(client_id);
+        if (client == null) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, "invalid_client");
+            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Could not find client");
+            event.error(Errors.CLIENT_NOT_FOUND);
+            throw new BadRequestException("Could not find client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+        }
+
+        if (!client.isEnabled()) {
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, "invalid_client");
+            error.put(OAuth2Constants.ERROR_DESCRIPTION, "Client is not enabled");
+            event.error(Errors.CLIENT_DISABLED);
+            throw new BadRequestException("Client is not enabled", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+        }
+
+        if (!client.isPublicClient()) {
+            if (clientSecret == null || !client.validateSecret(clientSecret)) {
+                Map<String, String> error = new HashMap<String, String>();
+                error.put(OAuth2Constants.ERROR, "unauthorized_client");
+                event.error(Errors.INVALID_CLIENT_CREDENTIALS);
+                throw new BadRequestException("Unauthorized Client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+            }
+        }
+
+        return client;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
new file mode 100644
index 0000000..2fa3aee
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
@@ -0,0 +1,134 @@
+package org.keycloak.protocol.oidc.utils;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.flows.Urls;
+
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RedirectUtils {
+
+    private static final Logger logger = Logger.getLogger(RedirectUtils.class);
+
+    public static String verifyRealmRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm) {
+        Set<String> validRedirects = getValidateRedirectUris(realm);
+        return verifyRedirectUri(uriInfo, redirectUri, realm, validRedirects);
+    }
+
+    public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
+        Set<String> validRedirects = client.getRedirectUris();
+        return verifyRedirectUri(uriInfo, redirectUri, realm, validRedirects);
+    }
+
+    public static Set<String> resolveValidRedirects(UriInfo uriInfo, Set<String> validRedirects) {
+        // If the valid redirect URI is relative (no scheme, host, port) then use the request's scheme, host, and port
+        Set<String> resolveValidRedirects = new HashSet<String>();
+        for (String validRedirect : validRedirects) {
+            resolveValidRedirects.add(validRedirect); // add even relative urls.
+            if (validRedirect.startsWith("/")) {
+                validRedirect = relativeToAbsoluteURI(uriInfo, validRedirect);
+                logger.debugv("replacing relative valid redirect with: {0}", validRedirect);
+                resolveValidRedirects.add(validRedirect);
+            }
+        }
+        return resolveValidRedirects;
+    }
+
+    private static Set<String> getValidateRedirectUris(RealmModel realm) {
+        Set<String> redirects = new HashSet<String>();
+        for (ApplicationModel client : realm.getApplications()) {
+            for (String redirect : client.getRedirectUris()) {
+                redirects.add(redirect);
+            }
+        }
+        for (OAuthClientModel client : realm.getOAuthClients()) {
+            for (String redirect : client.getRedirectUris()) {
+                redirects.add(redirect);
+            }
+        }
+        return redirects;
+    }
+
+    private static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, Set<String> validRedirects) {
+        if (redirectUri == null) {
+            if (validRedirects.size() != 1) return null;
+            String validRedirect = validRedirects.iterator().next();
+            int idx = validRedirect.indexOf("/*");
+            if (idx > -1) {
+                validRedirect = validRedirect.substring(0, idx);
+            }
+            redirectUri = validRedirect;
+        } else if (validRedirects.isEmpty()) {
+            logger.debug("No Redirect URIs supplied");
+            redirectUri = null;
+        } else {
+            String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
+            Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects);
+
+            boolean valid = matchesRedirects(resolveValidRedirects, r);
+
+            if (!valid && r.startsWith(Constants.INSTALLED_APP_URL) && r.indexOf(':', Constants.INSTALLED_APP_URL.length()) >= 0) {
+                int i = r.indexOf(':', Constants.INSTALLED_APP_URL.length());
+
+                StringBuilder sb = new StringBuilder();
+                sb.append(r.substring(0, i));
+
+                i = r.indexOf('/', i);
+                if (i >= 0) {
+                    sb.append(r.substring(i));
+                }
+
+                r = sb.toString();
+
+                valid = matchesRedirects(resolveValidRedirects, r);
+            }
+            if (valid && redirectUri.startsWith("/")) {
+                redirectUri = relativeToAbsoluteURI(uriInfo, redirectUri);
+            }
+            redirectUri = valid ? redirectUri : null;
+        }
+
+        if (Constants.INSTALLED_APP_URN.equals(redirectUri)) {
+            return Urls.realmInstalledAppUrnCallback(uriInfo.getBaseUri(), realm.getName()).toString();
+        } else {
+            return redirectUri;
+        }
+    }
+
+    private static String relativeToAbsoluteURI(UriInfo uriInfo, String relative) {
+        URI baseUri = uriInfo.getBaseUri();
+        String uri = baseUri.getScheme() + "://" + baseUri.getHost();
+        if (baseUri.getPort() != -1) {
+            uri += ":" + baseUri.getPort();
+        }
+        relative = uri + relative;
+        return relative;
+    }
+
+    private static boolean matchesRedirects(Set<String> validRedirects, String redirect) {
+        for (String validRedirect : validRedirects) {
+            if (validRedirect.endsWith("*")) {
+                // strip off *
+                int length = validRedirect.length() - 1;
+                validRedirect = validRedirect.substring(0, length);
+                if (redirect.startsWith(validRedirect)) return true;
+                // strip off trailing '/'
+                if (length - 1 > 0 && validRedirect.charAt(length - 1) == '/') length--;
+                validRedirect = validRedirect.substring(0, length);
+                if (validRedirect.equals(redirect)) return true;
+            } else if (validRedirect.equals(redirect)) return true;
+        }
+        return false;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/ErrorPageException.java b/services/src/main/java/org/keycloak/services/ErrorPageException.java
new file mode 100644
index 0000000..3f8d435
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/ErrorPageException.java
@@ -0,0 +1,33 @@
+package org.keycloak.services;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.flows.Flows;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ErrorPageException extends WebApplicationException {
+
+    private final KeycloakSession session;
+    private final RealmModel realm;
+    private final UriInfo uriInfo;
+    private final String errorMessage;
+
+    public ErrorPageException(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String errorMessage) {
+        this.session = session;
+        this.realm = realm;
+        this.uriInfo = uriInfo;
+        this.errorMessage = errorMessage;
+    }
+
+    @Override
+    public Response getResponse() {
+        return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, errorMessage);
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/ErrorResponseException.java b/services/src/main/java/org/keycloak/services/ErrorResponseException.java
new file mode 100644
index 0000000..bf9f278
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/ErrorResponseException.java
@@ -0,0 +1,39 @@
+package org.keycloak.services;
+
+import org.keycloak.OAuth2Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.flows.Flows;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ErrorResponseException extends WebApplicationException {
+
+    private final String error;
+    private final String errorDescription;
+    private final Response.Status status;
+
+    public ErrorResponseException(String error, String errorDescription, Response.Status status) {
+        this.error = error;
+        this.errorDescription = errorDescription;
+        this.status = status;
+    }
+
+    @Override
+    public Response getResponse() {
+        Map<String, String> e = new HashMap<String, String>();
+        e.put(OAuth2Constants.ERROR, error);
+        if (errorDescription != null) {
+            e.put(OAuth2Constants.ERROR_DESCRIPTION, errorDescription);
+        }
+        return Response.status(status).entity(e).type("application/json").build();
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index d286a14..8b5297b 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -50,6 +50,7 @@ import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.services.ForbiddenException;
@@ -824,7 +825,7 @@ public class AccountService {
         ApplicationModel application = realm.getApplicationByName(referrer);
         if (application != null) {
             if (referrerUri != null) {
-                referrerUri = OIDCLoginProtocolService.verifyRedirectUri(uriInfo, referrerUri, realm, application);
+                referrerUri = RedirectUtils.verifyRedirectUri(uriInfo, referrerUri, realm, application);
             } else {
                 referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), application.getBaseUrl());
             }
@@ -835,7 +836,7 @@ public class AccountService {
         } else if (referrerUri != null) {
             ClientModel client = realm.getOAuthClient(referrer);
             if (client != null) {
-                referrerUri = OIDCLoginProtocolService.verifyRedirectUri(uriInfo, referrerUri, realm, application);
+                referrerUri = RedirectUtils.verifyRedirectUri(uriInfo, referrerUri, realm, application);
 
                 if (referrerUri != null) {
                     return new String[]{referrer, referrerUri};
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index e508a70..5732688 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -26,6 +26,7 @@ import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
 import org.keycloak.representations.idm.ApplicationMappingsRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
@@ -721,7 +722,7 @@ public class UsersResource {
 
         String redirect;
         if(redirectUri != null){
-            redirect = OIDCLoginProtocolService.verifyRedirectUri(uriInfo, redirectUri, realm, client);
+            redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
             if(redirect == null){
                 return Flows.errors().error("Invalid redirect uri.", Response.Status.BAD_REQUEST);
             }
diff --git a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
index b49058f..1761138 100755
--- a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
+++ b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
@@ -31,6 +31,7 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
 import org.keycloak.services.ForbiddenException;
 import org.keycloak.util.Time;
 
@@ -154,7 +155,7 @@ public class ClientsManagementService {
     }
 
     protected ApplicationModel authorizeApplication(String authorizationHeader, MultivaluedMap<String, String> formData) {
-        ClientModel client = OIDCLoginProtocolService.authorizeClientBase(authorizationHeader, formData, event, realm);
+        ClientModel client = AuthorizeClientUtil.authorizeClient(authorizationHeader, formData, event, realm);
 
         if (client.isPublicClient()) {
             Map<String, String> error = new HashMap<String, String>();
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 8a50c69..247a430 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -17,6 +17,7 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.BruteForceProtector;
 import org.keycloak.services.managers.EventsManager;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.wellknown.WellKnownProvider;
 
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
@@ -79,10 +80,8 @@ public class RealmsResource {
     }
 
     @Path("{realm}/login-status-iframe.html")
-    @GET
-    @Produces(MediaType.TEXT_HTML)
     @Deprecated
-    public Response getLoginStatusIframe(final @PathParam("realm") String name,
+    public Object getLoginStatusIframe(final @PathParam("realm") String name,
                                        @QueryParam("client_id") String client_id,
                                        @QueryParam("origin") String origin) {
         // backward compatibility
@@ -95,7 +94,7 @@ public class RealmsResource {
         OIDCLoginProtocolService endpoint = (OIDCLoginProtocolService)factory.createProtocolEndpoint(realm, event, authManager);
 
         ResteasyProviderFactory.getInstance().injectProperties(endpoint);
-        return endpoint.getLoginStatusIframe(client_id, origin);
+        return endpoint.getLoginStatusIframe();
 
     }
 
@@ -196,5 +195,15 @@ public class RealmsResource {
         return brokerService;
     }
 
+    @GET
+    @Path("{realm}/.well-known/{provider}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getWellKnown(final @PathParam("realm") String realmName,
+                              final @PathParam("provider") String providerName) {
+        RealmManager realmManager = new RealmManager(session);
+        RealmModel realm = locateRealm(realmName, realmManager);
+        WellKnownProvider wellKnown = session.getProvider(WellKnownProvider.class, providerName);
+        return Response.ok(wellKnown.getConfig(realm, uriInfo)).build();
+    }
 
 }
diff --git a/services/src/main/java/org/keycloak/wellknown/WellKnownProvider.java b/services/src/main/java/org/keycloak/wellknown/WellKnownProvider.java
new file mode 100755
index 0000000..d4b80d7
--- /dev/null
+++ b/services/src/main/java/org/keycloak/wellknown/WellKnownProvider.java
@@ -0,0 +1,16 @@
+package org.keycloak.wellknown;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.Provider;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface WellKnownProvider extends Provider {
+
+    Object getConfig(RealmModel realm, UriInfo uriInfo);
+
+}
diff --git a/services/src/main/java/org/keycloak/wellknown/WellKnownProviderFactory.java b/services/src/main/java/org/keycloak/wellknown/WellKnownProviderFactory.java
new file mode 100755
index 0000000..21d8b81
--- /dev/null
+++ b/services/src/main/java/org/keycloak/wellknown/WellKnownProviderFactory.java
@@ -0,0 +1,10 @@
+package org.keycloak.wellknown;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface WellKnownProviderFactory extends ProviderFactory<WellKnownProvider> {
+
+}
diff --git a/services/src/main/java/org/keycloak/wellknown/WellKnownSpi.java b/services/src/main/java/org/keycloak/wellknown/WellKnownSpi.java
new file mode 100755
index 0000000..7cb962d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/wellknown/WellKnownSpi.java
@@ -0,0 +1,27 @@
+package org.keycloak.wellknown;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class WellKnownSpi implements Spi {
+
+    @Override
+    public String getName() {
+        return "well-known";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return WellKnownProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return WellKnownProviderFactory.class;
+    }
+
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 30eeb3e..cb01455 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -1,3 +1,4 @@
 org.keycloak.protocol.LoginProtocolSpi
 org.keycloak.protocol.ProtocolMapperSpi
-org.keycloak.exportimport.ApplicationImportSpi
\ No newline at end of file
+org.keycloak.exportimport.ApplicationImportSpi
+org.keycloak.wellknown.WellKnownSpi
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.wellknown.WellKnownProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.wellknown.WellKnownProviderFactory
new file mode 100644
index 0000000..b0a54e2
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.wellknown.WellKnownProviderFactory
@@ -0,0 +1 @@
+org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
index cfe16a8..e1d83b3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
@@ -429,7 +429,7 @@ public class AdapterTestStrategy extends ExternalResource {
         Response response = target.request()
                 .header(HttpHeaders.AUTHORIZATION, header)
                 .post(Entity.form(form));
-        Assert.assertEquals(400, response.getStatus());
+        Assert.assertEquals(401, response.getStatus());
         response.close();
         client.close();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index b6f45bd..56538a7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -436,7 +436,19 @@ public class AccessTokenTest {
             Response response = grantTarget.request()
                     .header(HttpHeaders.AUTHORIZATION, header)
                     .post(Entity.form(form));
-            Assert.assertEquals(400, response.getStatus());
+            Assert.assertEquals(401, response.getStatus());
+            response.close();
+        }
+
+        {   // test invalid password
+            String header = BasicAuthHelper.createHeader("test-app", "password");
+            Form form = new Form();
+            form.param("username", "test-user@localhost");
+            form.param("password", "invalid");
+            Response response = grantTarget.request()
+                    .header(HttpHeaders.AUTHORIZATION, header)
+                    .post(Entity.form(form));
+            Assert.assertEquals(401, response.getStatus());
             response.close();
         }
 
@@ -477,7 +489,7 @@ public class AccessTokenTest {
             }
 
             Response response = executeGrantAccessTokenRequest(grantTarget);
-            Assert.assertEquals(401, response.getStatus());
+            Assert.assertEquals(403, response.getStatus());
             response.close();
 
             {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
index efd54fe..c75a030 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
@@ -108,7 +108,7 @@ public class OAuthRedirectUriTest {
             oauth.openLoginForm();
 
             Assert.assertTrue(errorPage.isCurrent());
-            Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+            Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
         } finally {
             keycloakRule.update(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -133,7 +133,7 @@ public class OAuthRedirectUriTest {
             oauth.openLoginForm();
 
             Assert.assertTrue(errorPage.isCurrent());
-            Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+            Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
         } finally {
             keycloakRule.update(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -158,7 +158,7 @@ public class OAuthRedirectUriTest {
             oauth.openLoginForm();
 
             Assert.assertTrue(errorPage.isCurrent());
-            Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+            Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
         } finally {
             keycloakRule.update(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -184,7 +184,7 @@ public class OAuthRedirectUriTest {
         oauth.openLoginForm();
 
         Assert.assertTrue(errorPage.isCurrent());
-        Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+        Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
     }
 
     @Test
@@ -244,7 +244,7 @@ public class OAuthRedirectUriTest {
             Assert.assertTrue(loginPage.isCurrent());
         } else {
             Assert.assertTrue(errorPage.isCurrent());
-            Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+            Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
         }
 
         if (expectValid) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index f172dec..ea269c3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -195,7 +195,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
 
         OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "invalid");
 
-        assertEquals(400, response.getStatusCode());
+        assertEquals(401, response.getStatusCode());
 
         assertEquals("invalid_grant", response.getError());
 
@@ -216,7 +216,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
 
         OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "invalid", "invalid");
 
-        assertEquals(400, response.getStatusCode());
+        assertEquals(401, response.getStatusCode());
 
         assertEquals("invalid_grant", response.getError());