Implemented OAuth2 request access token with client credentials grant.

Change-Id: I98b8608d25eebf22eeeaf2637a181dd94c6a6fc2
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java b/core/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java
index cd85bb0..4cd3ee7 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java
@@ -17,7 +17,7 @@
  */
 public class FormRequestWrapper extends HttpServletRequestWrapper {
 
-    private MultivaluedMap<String, Object> form;
+    private MultivaluedMap<String, String> form;
 
 
     /**
@@ -28,7 +28,7 @@
      *             if the request is null
      */
     public FormRequestWrapper (HttpServletRequest request,
-                               MultivaluedMap<String, Object> form) {
+                               MultivaluedMap<String, String> form) {
         super(request);
         this.form = form;
     }
@@ -54,7 +54,7 @@
     }
 
 
-    public Map<String, Object> singleValueMap () {
+    public HashMap<String, Object> singleValueMap () {
         return toMap(this.form, false);
     }
 
@@ -66,8 +66,8 @@
      *            in value list and returns the result
      * @return key/value map
      */
-    public static Map<String, Object> toMap (
-            MultivaluedMap<String, Object> form, boolean strict) {
+    public static HashMap<String, Object> toMap (
+            MultivaluedMap<String, String> form, boolean strict) {
         if (form == null)
             return null;
         HashMap<String, Object> map = new HashMap<>();
@@ -87,7 +87,7 @@
 
 
     public void put (String key, String ... values) {
-        this.form.put(key, Arrays.<Object> asList(values));
+        this.form.put(key, Arrays.<String> asList(values));
     }
 
 }
diff --git a/full/Changes b/full/Changes
index b0fd244..733d853 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,5 +1,5 @@
 version 0.60.2
-10/04/2018
+16/04/2018
 	- implemented OAuth2 client registration (margaretha)
 	- implemented OAuth2 client authentication (margaretha)
 	- changed virtual corpus search to retrieval (margaretha)
@@ -7,6 +7,8 @@
 	- added client registration and deregistration tests (margaretha)
 	- implemented confidential client deregistration task (margaretha)
 	- fixed storing client secret (margaretha)
+	- implemented OAuth2 exception handler (margaretha)
+	- implemented OAuth2 request access token with client credentials grant (margaretha)
 	
 version 0.60.1
 28/03/2018
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/http/HttpUnauthorizedHandler.java b/full/src/main/java/de/ids_mannheim/korap/authentication/http/HttpUnauthorizedHandler.java
deleted file mode 100644
index 845d9ae..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/http/HttpUnauthorizedHandler.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package de.ids_mannheim.korap.authentication.http;
-
-import java.util.EnumSet;
-
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.ResponseBuilder;
-
-import org.springframework.stereotype.Component;
-
-import de.ids_mannheim.korap.constant.AuthenticationScheme;
-
-/** Implementation of HTTP authentication scheme (see RFC 7253 and 7617)
- *  for server creating responses with status 401 Unauthorized and 
- *  WWW-Authenticate header to unauthenticated requests.
- *    
- * @author margaretha
- *
- */
-@Component
-public class HttpUnauthorizedHandler {
-
-    public Response createUnauthenticatedResponse (String notification) {
-        ResponseBuilder builder = Response.status(Response.Status.UNAUTHORIZED);
-
-        for (AuthenticationScheme s : EnumSet
-                .allOf(AuthenticationScheme.class)) {
-            builder = builder.header(HttpHeaders.WWW_AUTHENTICATE,
-                    s.displayName() + " realm=\"Kustvakt\"");
-        }
-
-        return builder.entity(notification).build();
-    }
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/constant/AuthenticationScheme.java b/full/src/main/java/de/ids_mannheim/korap/constant/AuthenticationScheme.java
index 42f296c..3db6a3e 100644
--- a/full/src/main/java/de/ids_mannheim/korap/constant/AuthenticationScheme.java
+++ b/full/src/main/java/de/ids_mannheim/korap/constant/AuthenticationScheme.java
@@ -12,7 +12,8 @@
     // standard http
     BASIC, BEARER,
     // custom
-    SESSION, API;
+    // SESSION, has not been supported yet 
+    API;
 
     public String displayName () {
         return WordUtils.capitalizeFully(name());
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java b/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
index b8546ae..d6878f4 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
@@ -65,6 +65,10 @@
         try {
             return (OAuth2Client) q.getSingleResult();
         }
+        catch (NoResultException e) {
+            throw new KustvaktException(StatusCodes.CLIENT_NOT_FOUND,
+                    "Unknown client with "+clientId+".", "invalid_client");
+        }
         catch (Exception e) {
             throw new KustvaktException(StatusCodes.CLIENT_NOT_FOUND,
                     e.getMessage(), "invalid_client");
diff --git a/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java b/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
index 35b6dce..9383929 100644
--- a/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
+++ b/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
@@ -33,6 +33,7 @@
     private int urlHashCode;
     @Column(name = "redirect_uri")
     private String redirectURI;
+    @Column(name = "registered_by")
     private String registeredBy;
 
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
index 0109ea8..92b8837 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
@@ -4,7 +4,6 @@
 
 import org.apache.commons.validator.routines.UrlValidator;
 import org.apache.oltu.oauth2.common.error.OAuthError;
-import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
index 9338bfe..1944b2b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
+++ b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
@@ -1,6 +1,7 @@
 package de.ids_mannheim.korap.service;
 
-import javax.servlet.http.HttpServletRequest;
+import java.util.Set;
+
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.oltu.oauth2.as.issuer.MD5Generator;
@@ -52,22 +53,25 @@
      * @throws OAuthProblemException 
      * @throws OAuthSystemException 
      */
-    public OAuthResponse requestAccessToken (HttpServletRequest request,
-            String authorization, String grantType, String authorizationCode,
-            String redirectURI, String clientId, String username,
-            String password, String scope) throws KustvaktException {
+    public OAuthResponse requestAccessToken (OAuthTokenRequest oAuthRequest,
+            String authorization)
+            throws KustvaktException, OAuthSystemException {
+
+        String grantType = oAuthRequest.getGrantType();
 
         if (grantType.equals(GrantType.AUTHORIZATION_CODE.toString())) {
             return requestAccessTokenWithAuthorizationCode(authorization,
-                    authorizationCode, redirectURI, clientId);
+                    oAuthRequest.getCode(), oAuthRequest.getRedirectURI(),
+                    oAuthRequest.getClientId());
         }
         else if (grantType.equals(GrantType.PASSWORD.toString())) {
-            return requestAccessTokenWithPassword(authorization, username,
-                    password, scope);
+            return requestAccessTokenWithPassword(authorization,
+                    oAuthRequest.getUsername(), oAuthRequest.getPassword(),
+                    oAuthRequest.getScopes());
         }
         else if (grantType.equals(GrantType.CLIENT_CREDENTIALS.toString())) {
             return requestAccessTokenWithClientCredentials(authorization,
-                    scope);
+                    oAuthRequest.getScopes());
         }
         else {
             throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
@@ -115,11 +119,11 @@
      * @param authorization
      * @param username
      * @param password
-     * @param scope
+     * @param scopes
      * @return
      */
     private OAuthResponse requestAccessTokenWithPassword (String authorization,
-            String username, String password, String scope) {
+            String username, String password, Set<String> scopes) {
 
 
 
@@ -129,13 +133,15 @@
     /** Clients must authenticate
      * 
      * @param authorization
-     * @param scope
+     * @param scopes
+     * @param request 
      * @return
-     * @throws OAuthProblemException 
      * @throws KustvaktException 
+     * @throws OAuthSystemException 
      */
     private OAuthResponse requestAccessTokenWithClientCredentials (
-            String authorization, String scope) throws KustvaktException {
+            String authorization, Set<String> scopes)
+            throws KustvaktException, OAuthSystemException {
 
         if (authorization == null || authorization.isEmpty()) {
             throw new KustvaktException(
@@ -144,12 +150,10 @@
                     OAuthError.TokenResponse.INVALID_CLIENT);
         }
         else {
-            OAuth2Client client =
-                    clientService.authenticateClientByBasicAuthorization(
-                            authorization, null);
-            //TODO
+            clientService.authenticateClientByBasicAuthorization(authorization,
+                    null);
+            return createsAccessTokenResponse();
         }
-        return null;
     }
 
 
@@ -159,31 +163,20 @@
      * @throws OAuthSystemException 
      * 
      */
-    private OAuthResponse createsAccessTokenResponse (
-            HttpServletRequest request) throws OAuthSystemException {
-        OAuthTokenRequest oauthRequest = null;
+    private OAuthResponse createsAccessTokenResponse ()
+            throws OAuthSystemException {
         OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
         OAuthResponse r = null;
-        try {
-            oauthRequest = new OAuthTokenRequest(request);
-            String authorizationCode = oauthRequest.getCode();
 
-            String accessToken = oauthIssuerImpl.accessToken();
-            String refreshToken = oauthIssuerImpl.refreshToken();
+        String accessToken = oauthIssuerImpl.accessToken();
+        String refreshToken = oauthIssuerImpl.refreshToken();
 
-            r = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK)
-                    .setAccessToken(accessToken)
-                    .setTokenType(TokenType.BEARER.name())
-                    .setExpiresIn(String.valueOf(config.getLongTokenTTL()))
-                    .setRefreshToken(refreshToken).buildJSONMessage();
-            // scope
-
-        }
-        catch (OAuthProblemException e) {
-            r = OAuthResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
-                    .error(e).buildJSONMessage();
-        }
-
+        r = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK)
+                .setAccessToken(accessToken)
+                .setTokenType(TokenType.BEARER.toString())
+                .setExpiresIn(String.valueOf(config.getLongTokenTTL()))
+                .setRefreshToken(refreshToken).buildJSONMessage();
+        // scope
         return r;
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/KustvaktExceptionHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/KustvaktExceptionHandler.java
index e17cd0a..6597608 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/KustvaktExceptionHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/KustvaktExceptionHandler.java
@@ -1,14 +1,13 @@
 package de.ids_mannheim.korap.web;
 
+import java.util.EnumSet;
+
 import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
 
-import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
-import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
-import org.apache.oltu.oauth2.common.message.OAuthResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import de.ids_mannheim.korap.authentication.http.HttpUnauthorizedHandler;
+import de.ids_mannheim.korap.constant.AuthenticationScheme;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.interfaces.db.AuditingIface;
@@ -20,9 +19,6 @@
  */
 public class KustvaktExceptionHandler extends CoreResponseHandler {
 
-    @Autowired
-    private HttpUnauthorizedHandler handler;
-
     public KustvaktExceptionHandler (AuditingIface iface) {
         super(iface);
     }
@@ -35,7 +31,7 @@
                 || e.getStatusCode() >= StatusCodes.AUTHENTICATION_FAILED) {
             String notification = buildNotification(e.getStatusCode(),
                     e.getMessage(), e.getEntity());
-            r = handler.createUnauthenticatedResponse(notification);
+            r = createUnauthenticatedResponse(notification);
         }
         else if (e.hasNotification()) {
             r = Response.status(getStatus(e.getStatusCode()))
@@ -48,38 +44,15 @@
         return new WebApplicationException(r);
     }
 
-    public WebApplicationException throwit (OAuthProblemException e) {
-        OAuthResponse or = null;
-        try {
-            or = OAuthResponse.errorResponse(e.getResponseStatus()).error(e)
-                    .buildJSONMessage();
-        }
-        catch (OAuthSystemException e1) {
-            //            return throwit(new KustvaktException(
-            //                    StatusCodes.OAUTH2_SYSTEM_ERROR, e1.getMessage(), e1));
-            return throwit(StatusCodes.OAUTH2_SYSTEM_ERROR, e1.getMessage());
+    public Response createUnauthenticatedResponse (String notification) {
+        ResponseBuilder builder = Response.status(Response.Status.UNAUTHORIZED);
+
+        for (AuthenticationScheme s : EnumSet
+                .allOf(AuthenticationScheme.class)) {
+            builder = builder.header(HttpHeaders.WWW_AUTHENTICATE,
+                    s.displayName() + " realm=\"Kustvakt\"");
         }
 
-        Response r = Response.status(or.getResponseStatus())
-                .entity(or.getBody()).build();
-        return new WebApplicationException(r);
-
+        return builder.entity(notification).build();
     }
-
-    //    public WebApplicationException throwAuthenticationException (
-    //            String message) {
-    //        String notification =
-    //                buildNotification(StatusCodes.AUTHORIZATION_FAILED,
-    //                        "Authorization failed", message);
-    //        return new WebApplicationException(
-    //                handler.createUnauthenticatedResponse(notification));
-    //    }
-
-    //    public WebApplicationException throwAuthenticationException (
-    //            KustvaktException e) {
-    //        String notification = buildNotification(e.getStatusCode(),
-    //                e.getMessage(), e.getEntity());
-    //        return new WebApplicationException(
-    //                handler.createUnauthenticatedResponse(notification));
-    //    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ExceptionHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
similarity index 79%
rename from full/src/main/java/de/ids_mannheim/korap/web/OAuth2ExceptionHandler.java
rename to full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
index 3d849af..5df68b2 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ExceptionHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
@@ -1,22 +1,23 @@
 package de.ids_mannheim.korap.web;
 
 import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.Response.Status;
 
+import org.apache.http.HttpHeaders;
 import org.apache.oltu.oauth2.common.error.OAuthError;
 import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
 import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
 import org.apache.oltu.oauth2.common.message.OAuthResponse;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.interfaces.db.AuditingIface;
 
-/** OAuth2ExceptionHandler maps {@link KustvaktException} to 
- * {@link OAuthProblemException} and creates {@link Response} 
- * accordingly. 
+/** OAuth2ResponseHandler builds {@link Response}s from 
+ * {@link OAuthResponse}s and handles exceptions by building 
+ * OAuth error responses accordingly. 
  * 
  * <br/><br/>
  * 
@@ -63,12 +64,29 @@
  * @author margaretha
  *
  */
-public class OAuth2ExceptionHandler extends CoreResponseHandler {
+public class OAuth2ResponseHandler extends KustvaktExceptionHandler {
 
-    public OAuth2ExceptionHandler (AuditingIface iface) {
+    public OAuth2ResponseHandler (AuditingIface iface) {
         super(iface);
     }
 
+    public WebApplicationException throwit (OAuthSystemException e) {
+        return throwit(StatusCodes.OAUTH2_SYSTEM_ERROR, e.getMessage());
+    }
+
+    public WebApplicationException throwit (OAuthProblemException e) {
+        OAuthResponse oAuthResponse = null;
+        try {
+            oAuthResponse = OAuthResponse.errorResponse(e.getResponseStatus())
+                    .error(e).buildJSONMessage();
+        }
+        catch (OAuthSystemException e1) {
+            throwit(e1);
+        }
+        Response r = createResponse(oAuthResponse);
+        return new WebApplicationException(r);
+    }
+
     @Override
     public WebApplicationException throwit (KustvaktException e) {
         OAuthResponse oAuthResponse = null;
@@ -95,9 +113,7 @@
             }
         }
         catch (OAuthSystemException e1) {
-            Response r = Response.status(Status.BAD_REQUEST)
-                    .entity(e1.getMessage()).build();
-            return new WebApplicationException(r);
+            return throwit(e1);
         }
 
         Response r = createResponse(oAuthResponse);
@@ -112,10 +128,13 @@
                 .error(oAuthProblemException).buildJSONMessage();
     }
 
-    private Response createResponse (OAuthResponse oAuthResponse) {
+    public Response createResponse (OAuthResponse oAuthResponse) {
         ResponseBuilder builder =
                 Response.status(oAuthResponse.getResponseStatus());
         builder.entity(oAuthResponse.getBody());
+        builder.header(HttpHeaders.CACHE_CONTROL, "no-store");
+        builder.header(HttpHeaders.PRAGMA, "no-store");
+
         if (oAuthResponse.getResponseStatus() == Status.UNAUTHORIZED
                 .getStatusCode()) {
             builder.header(HttpHeaders.WWW_AUTHENTICATE,
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/AnnotationController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/AnnotationController.java
index 68ffb47..704e4a7 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/AnnotationController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/AnnotationController.java
@@ -39,7 +39,7 @@
 public class AnnotationController {
 
     @Autowired
-    private KustvaktExceptionHandler responseHandler;
+    private KustvaktExceptionHandler kustvaktExceptionHandler;
 
     @Autowired
     private AnnotationService annotationService;
@@ -71,7 +71,7 @@
     @Consumes(MediaType.APPLICATION_JSON)
     public List<FoundryDto> getFoundryDescriptions (String json) {
         if (json == null || json.isEmpty()) {
-            throw responseHandler
+            throw kustvaktExceptionHandler
                     .throwit(new KustvaktException(StatusCodes.MISSING_ARGUMENT,
                             "Missing a json string.", ""));
         }
@@ -81,7 +81,7 @@
             node = JsonUtils.readTree(json);
         }
         catch (KustvaktException e1) {
-            throw responseHandler.throwit(e1);
+            throw kustvaktExceptionHandler.throwit(e1);
         }
 
         String language;
@@ -94,7 +94,7 @@
                 language = "en";
             }
             else if (!(language.equals("en") || language.equals("de"))) {
-                throw responseHandler.throwit(
+                throw kustvaktExceptionHandler.throwit(
                         new KustvaktException(StatusCodes.UNSUPPORTED_VALUE,
                                 "Unsupported value:", language));
             }
@@ -105,16 +105,16 @@
             codes = JsonUtils.convert(node.get("codes"), List.class);
         }
         catch (IOException | NullPointerException e) {
-            throw responseHandler.throwit(new KustvaktException(
+            throw kustvaktExceptionHandler.throwit(new KustvaktException(
                     StatusCodes.INVALID_ARGUMENT, "Bad argument:", json));
         }
         if (codes == null) {
-            throw responseHandler.throwit(
+            throw kustvaktExceptionHandler.throwit(
                     new KustvaktException(StatusCodes.MISSING_ATTRIBUTE,
                             "Missing attribute:", "codes"));
         }
         else if (codes.isEmpty()) {
-            throw responseHandler
+            throw kustvaktExceptionHandler
                     .throwit(new KustvaktException(StatusCodes.NO_RESULT_FOUND,
                             "No result found.", "codes:[]"));
         }
@@ -123,7 +123,7 @@
             return annotationService.getFoundryDtos(codes, language);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
index d3163e6..75cc85b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
@@ -2,32 +2,34 @@
 
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
-import javax.ws.rs.FormParam;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.SecurityContext;
 
-import org.apache.http.HttpHeaders;
+import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
 import org.apache.oltu.oauth2.common.message.OAuthResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.service.OAuth2Service;
-import de.ids_mannheim.korap.web.OAuth2ExceptionHandler;
+import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
+import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
 
 @Controller
 @Path("/oauth2")
 public class OAuth2Controller {
 
     @Autowired
-    private OAuth2ExceptionHandler responseHandler;
+    private OAuth2ResponseHandler responseHandler;
     @Autowired
     private OAuth2Service oauth2Service;
 
@@ -55,32 +57,28 @@
     public Response requestAccessToken (@Context HttpServletRequest request,
             @Context SecurityContext securityContext,
             @HeaderParam("Authorization") String authorization,
-            // required for all grants
-            @FormParam("grant_type") String grantType,
-            // required for Authorization Code Grant
-            @FormParam("code") String authorizationCode,
-            @FormParam("redirect_uri") String redirectURI,
-            @FormParam("client_id") String client_id,
-            // required for Resource Owner Password Grant
-            @FormParam("username") String username,
-            @FormParam("password") String password,
-            // optional for Resource Owner Password and Client Credentials Grants
-            @FormParam("scope") String scope) {
+            MultivaluedMap<String, String> form) {
 
         try {
-            OAuthResponse oauth2Response = oauth2Service.requestAccessToken(request,
-                    authorization, grantType, authorizationCode, redirectURI,
-                    client_id, username, password, scope);
+            OAuthTokenRequest oAuthRequest = null;
+            try {
+                oAuthRequest = new OAuthTokenRequest(
+                        new FormRequestWrapper(request, form));
+            }
+            catch (OAuthProblemException e) {
+                throw responseHandler.throwit(e);
+            }
 
-            ResponseBuilder builder =
-                    Response.status(oauth2Response.getResponseStatus());
-            builder.entity(oauth2Response.getBody());
-            builder.header(HttpHeaders.CACHE_CONTROL, "no-store");
-            builder.header(HttpHeaders.PRAGMA, "no-store");
-            return builder.build();
+            OAuthResponse oAuthResponse = oauth2Service
+                    .requestAccessToken(oAuthRequest, authorization);
+
+            return responseHandler.createResponse(oAuthResponse);
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
         }
+        catch (OAuthSystemException e) {
+            throw responseHandler.throwit(e);
+        }
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
index fa406ac..9c4834c 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -21,8 +21,7 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.service.OAuth2ClientService;
-import de.ids_mannheim.korap.web.KustvaktExceptionHandler;
-import de.ids_mannheim.korap.web.OAuth2ExceptionHandler;
+import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
 import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
 import de.ids_mannheim.korap.web.filter.BlockingFilter;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
@@ -41,7 +40,7 @@
     @Autowired
     private OAuth2ClientService clientService;
     @Autowired
-    private OAuth2ExceptionHandler responseHandler;
+    private OAuth2ResponseHandler responseHandler;
 
     /** Registers a client application. Before starting an OAuth process, 
      * client applications have to be registered first. Only registered
@@ -88,7 +87,7 @@
      * @return HTTP Response OK if successful.
      */
     @DELETE
-    @Path("deregister")
+    @Path("deregister/public")
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
     public Response deregisterPublicClient (
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
index e457cea..a24ffc9 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
@@ -74,7 +74,7 @@
 public class OAuthController {
 
     @Autowired
-    CoreResponseHandler kustvaktExceptionHandler;
+    private CoreResponseHandler kustvaktExceptionHandler;
     
     private OAuth2Handler handler;
     @Autowired
@@ -201,7 +201,7 @@
             @Context SecurityContext context,
             @HeaderParam(ContainerRequest.USER_AGENT) String agent,
             @HeaderParam(ContainerRequest.HOST) String host,
-            MultivaluedMap<String, Object> form) throws OAuthSystemException,
+            MultivaluedMap<String, String> form) throws OAuthSystemException,
             URISyntaxException {
         // user needs to be authenticated to this service!
         TokenContext c = (TokenContext) context.getUserPrincipal();
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
index 357ceb5..910225d 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
@@ -50,7 +50,7 @@
 public class UserGroupController {
 
     @Autowired
-    private KustvaktExceptionHandler responseHandler;
+    private KustvaktExceptionHandler kustvaktExceptionHandler;
     @Autowired
     private UserGroupService service;
 
@@ -73,7 +73,7 @@
             return service.retrieveUserGroup(context.getUsername());
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -101,7 +101,7 @@
                     context.getUsername(), status);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -123,7 +123,7 @@
             return service.searchById(context.getUsername(), groupId);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
 
     }
@@ -159,7 +159,7 @@
             return Response.ok().build();
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -181,7 +181,7 @@
             return Response.ok().build();
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -205,7 +205,7 @@
             return Response.ok().build();
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -229,7 +229,7 @@
             return Response.ok().build();
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -257,7 +257,7 @@
             return Response.ok().build();
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -285,7 +285,7 @@
             return Response.ok().build();
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -308,7 +308,7 @@
             return Response.ok().build();
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -335,7 +335,7 @@
             return Response.ok().build();
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
index d2894c8..3d3c037 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
@@ -55,7 +55,7 @@
 public class VirtualCorpusController {
 
     @Autowired
-    private KustvaktExceptionHandler responseHandler;
+    private KustvaktExceptionHandler kustvaktExceptionHandler;
     @Autowired
     private VirtualCorpusService service;
 
@@ -81,7 +81,7 @@
             service.storeVC(vc, context.getUsername());
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
         return Response.ok().build();
     }
@@ -109,7 +109,7 @@
             service.editVC(vc, context.getUsername());
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
         return Response.ok().build();
     }
@@ -131,7 +131,7 @@
             return service.searchVCById(context.getUsername(), vcId);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -158,7 +158,7 @@
             return service.listVCByUser(context.getUsername(), createdBy);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -179,7 +179,7 @@
             return service.listOwnerVC(context.getUsername());
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -208,7 +208,7 @@
             return service.listVCByType(context.getUsername(), createdBy, type);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -230,7 +230,7 @@
             service.deleteVC(context.getUsername(), vcId);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
         return Response.ok().build();
     }
@@ -255,7 +255,7 @@
             service.shareVC(context.getUsername(), vcId, groupId);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
         return Response.ok().build();
     }
@@ -276,7 +276,7 @@
             service.deleteVCAccess(accessId, context.getUsername());
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
         return Response.ok().build();
     }
@@ -304,7 +304,7 @@
             return service.listVCAccessByVC(context.getUsername(), vcId);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 
@@ -328,7 +328,7 @@
             return service.listVCAccessByGroup(context.getUsername(), groupId);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java b/full/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
index f28331c..e56831a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
@@ -46,7 +46,7 @@
     private AuthenticationManagerIface authManager;
 
     @Autowired
-    private KustvaktExceptionHandler kustvaktResponseHandler;
+    private KustvaktExceptionHandler kustvaktExceptionHandler;
 
     @Autowired
     private HttpAuthorizationHandler authorizationHandler;
@@ -62,7 +62,7 @@
             data = authorizationHandler.parseBasicToken(data);
         }
         catch (KustvaktException e) {
-            throw kustvaktResponseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
 
         String host = cr.getHeaderValue(ContainerRequest.HOST);
@@ -82,7 +82,7 @@
             properties.put("user", user);
         }
         catch (KustvaktException e) {
-            throw kustvaktResponseHandler.throwit(e);
+            throw kustvaktExceptionHandler.throwit(e);
         }
 
         TokenContext c = new TokenContext();
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java b/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
index 94975d1..41547b4 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
@@ -38,7 +38,7 @@
     private AuthenticationManagerIface authenticationManager;
 
     @Autowired
-    private KustvaktExceptionHandler kustvaktResponseHandler;
+    private KustvaktExceptionHandler kustvaktExceptionHandler;
 
     @Override
     public ContainerRequest filter (ContainerRequest request) {
@@ -60,11 +60,12 @@
                         context = authenticationManager.getTokenContext(
                                 TokenType.BASIC, authData.getToken(), host, ua);
                         break;
-                    case SESSION:
-                        context = authenticationManager.getTokenContext(
-                                TokenType.SESSION, authData.getToken(), host,
-                                ua);
-                        break;
+                      // EM: has not been tested yet
+//                    case SESSION:
+//                        context = authenticationManager.getTokenContext(
+//                                TokenType.SESSION, authData.getToken(), host,
+//                                ua);
+//                        break;
                     // EM: bearer or api
                     default:
                         context = authenticationManager.getTokenContext(
@@ -75,7 +76,7 @@
                 request.setSecurityContext(new KustvaktContext(context));
             }
             catch (KustvaktException e) {
-                throw kustvaktResponseHandler.throwit(e);
+                throw kustvaktExceptionHandler.throwit(e);
             }
         }
         return request;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java b/full/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java
index 0bb563a..b1f0e69 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java
@@ -27,7 +27,7 @@
 public class BlockingFilter implements ContainerRequestFilter, ResourceFilter {
 
     @Autowired
-    private KustvaktExceptionHandler kustvaktResponseHandler;
+    private KustvaktExceptionHandler kustvaktExceptionHandler;
 
     @Override
     public ContainerRequest filter (ContainerRequest request) {
@@ -37,12 +37,12 @@
             context = (TokenContext) request.getUserPrincipal();
         }
         catch (UnsupportedOperationException e) {
-            throw kustvaktResponseHandler.throwit(new KustvaktException(
+            throw kustvaktExceptionHandler.throwit(new KustvaktException(
                     StatusCodes.UNSUPPORTED_OPERATION, e.getMessage(), e));
         }
 
         if (context == null || context.isDemo()) {
-            throw kustvaktResponseHandler.throwit(new KustvaktException(
+            throw kustvaktExceptionHandler.throwit(new KustvaktException(
                     StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: guest", "guest"));
         }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/filter/NonDemoBlockingFilter.java b/full/src/main/java/de/ids_mannheim/korap/web/filter/NonDemoBlockingFilter.java
index da8c444..eda335a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/filter/NonDemoBlockingFilter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/filter/NonDemoBlockingFilter.java
@@ -30,7 +30,7 @@
         implements ContainerRequestFilter, ResourceFilter {
 
     @Autowired
-    private KustvaktExceptionHandler kustvaktResponseHandler;
+    private KustvaktExceptionHandler kustvaktExceptionHandler;
 
     @Override
     public ContainerRequest filter (ContainerRequest request) {
@@ -39,12 +39,12 @@
             context = (TokenContext) request.getUserPrincipal();
         }
         catch (UnsupportedOperationException e) {
-            throw kustvaktResponseHandler.throwit(new KustvaktException(
+            throw kustvaktExceptionHandler.throwit(new KustvaktException(
                     StatusCodes.UNSUPPORTED_OPERATION, e.getMessage(), e));
         }
 
         if (context == null || context.isDemo()) {
-            throw kustvaktResponseHandler.throwit(
+            throw kustvaktExceptionHandler.throwit(
                     new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                             "Operation is not permitted for guest users"));
         }
diff --git a/full/src/main/resources/db/insert/V3.5__insert_oauth2_clients.sql b/full/src/main/resources/db/insert/V3.5__insert_oauth2_clients.sql
new file mode 100644
index 0000000..8633a11
--- /dev/null
+++ b/full/src/main/resources/db/insert/V3.5__insert_oauth2_clients.sql
@@ -0,0 +1,15 @@
+-- test clients
+
+-- plain secret value is "secret"
+INSERT INTO oauth2_client(id,name,secret,type,url,url_hashcode,redirect_uri,
+  registered_by) 
+VALUES ("fCBbQkAyYzI4NzUxMg==","test confidential client",
+  "$2a$08$vi1FbuN3p6GcI1tSxMAoeuIYL8Yw3j6A8wJthaN8ZboVnrQaTwLPq",
+  "CONFIDENTIAL","http://confidential.client.com", -1097645390, 
+  "https://confidential.client.com/redirect", "system");
+  
+INSERT INTO oauth2_client(id,name,secret,type,url,url_hashcode,redirect_uri,
+  registered_by) 
+VALUES ("8bIDtZnH6NvRkW2Fq==","test public client",null,
+  "PUBLIC","http://public.client.com", -1408041551,
+  "https://public.client.com/redirect","system");
\ No newline at end of file
diff --git a/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql b/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
index f616782..ad3af94 100644
--- a/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
+++ b/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
@@ -9,7 +9,7 @@
 	url TEXT NOT NULL,
 	url_hashcode UNIQUE INTEGER NOT NULL,
 	redirect_uri TEXT NOT NULL,
-	registeredBy VARCHAR(100) NOT NULL
+	registered_by VARCHAR(100) NOT NULL
 );
 
 --
diff --git a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
index 4b42442..835acd6 100644
--- a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
+++ b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
@@ -9,7 +9,7 @@
 	url TEXT NOT NULL,
 	url_hashcode INTEGER NOT NULL,
 	redirect_uri TEXT NOT NULL,
-	registeredBy VARCHAR(100) NOT NULL
+	registered_by VARCHAR(100) NOT NULL
 );
 
 CREATE UNIQUE INDEX client_id_index on oauth2_client(id);
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
index 483c6e2..6e1904b 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -5,8 +5,8 @@
 import static org.junit.Assert.assertTrue;
 
 import java.util.List;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;
 
 import javax.ws.rs.core.MultivaluedMap;
 
@@ -29,7 +29,6 @@
 import de.ids_mannheim.korap.config.SpringJerseyTest;
 import de.ids_mannheim.korap.constant.OAuth2ClientType;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 
@@ -100,8 +99,8 @@
         OAuth2ClientJson json = new OAuth2ClientJson();
         json.setName("OAuth2PublicClient");
         json.setType(OAuth2ClientType.PUBLIC);
-        json.setUrl("http://public.client.com");
-        json.setRedirectURI("https://public.client.com/redirect");
+        json.setUrl("http://test.public.client.com");
+        json.setRedirectURI("https://test.public.client.com/redirect");
 
         ClientResponse response = resource().path("oauth2").path("client")
                 .path("register")
@@ -130,7 +129,7 @@
         form.add("client_id", clientId);
 
         ClientResponse response = resource().path("oauth2").path("client")
-                .path("deregister")
+                .path("deregister").path("public")
                 .header(Attributes.AUTHORIZATION,
                         handler.createBasicAuthorizationHeaderValue(username,
                                 "pass"))
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
index 794ea6b..46671ff 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -1,13 +1,14 @@
 package de.ids_mannheim.korap.web.controller;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response.Status;
 
 import org.apache.http.entity.ContentType;
 import org.apache.oltu.oauth2.common.error.OAuthError;
-import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.apache.oltu.oauth2.common.message.types.TokenType;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -32,7 +33,55 @@
 
     @Autowired
     private HttpAuthorizationHandler handler;
-    private String username = "OAuth2ControllerTest";
+
+    private ClientResponse testRequestTokenConfidentialClient (
+            MultivaluedMap<String, String> form)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        return resource().path("oauth2").path("token")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(
+                                "fCBbQkAyYzI4NzUxMg==", "secret"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+    }
+
+    @Test
+    public void testRequestTokenClientCredentialsGrant ()
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("grant_type", "client_credentials");
+
+        ClientResponse response = testRequestTokenConfidentialClient(form);
+        String entity = response.getEntity(String.class);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(entity);
+        // length?
+        assertNotNull(node.at("/access_token").asText());
+        assertNotNull(node.at("/refresh_token").asText());
+        assertEquals(TokenType.BEARER.toString(),
+                node.at("/token_type").asText());
+        assertNotNull(node.at("/expires_in").asText());
+    }
+
+    @Test
+    public void testRequestTokenMissingGrantType ()
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        ClientResponse response = testRequestTokenConfidentialClient(form);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+    }
 
     @Test
     public void testRequestTokenUnsupportedGrant ()
@@ -43,23 +92,19 @@
         form.add("grant_type", "blahblah");
 
         ClientResponse response = resource().path("oauth2").path("token")
-                .header(Attributes.AUTHORIZATION,
-                        handler.createBasicAuthorizationHeaderValue(username,
-                                "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(HttpHeaders.CONTENT_TYPE,
                         ContentType.APPLICATION_FORM_URLENCODED)
                 .entity(form).post(ClientResponse.class);
 
         String entity = response.getEntity(String.class);
-//        System.out.println(entity);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
         JsonNode node = JsonUtils.readTree(entity);
-        assertEquals("blahblah is not supported.",
+        assertEquals("Invalid grant_type parameter value",
                 node.get("error_description").asText());
-        assertEquals(OAuthError.TokenResponse.UNSUPPORTED_GRANT_TYPE,
-                node.get("error"));
+        assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+                node.get("error").asText());
     }
 
 }
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
index 71b1162..ad2874a 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
@@ -54,12 +54,12 @@
             if (header.getKey().equals(ContainerRequest.WWW_AUTHENTICATE)) {
                 assertEquals("Api realm=\"Kustvakt\"",
                         header.getValue().get(0));
-                assertEquals("Session realm=\"Kustvakt\"",
-                        header.getValue().get(1));
+//                assertEquals("Session realm=\"Kustvakt\"",
+//                        header.getValue().get(1));
                 assertEquals("Bearer realm=\"Kustvakt\"",
-                        header.getValue().get(2));
+                        header.getValue().get(1));
                 assertEquals("Basic realm=\"Kustvakt\"",
-                        header.getValue().get(3));
+                        header.getValue().get(2));
             }
         }
     }
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index 66b6c97..0445d65 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -184,7 +184,7 @@
 	<bean id="kustvaktExceptionHandler" class="de.ids_mannheim.korap.web.KustvaktExceptionHandler">
 		<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
 	</bean>
-	<bean id="oauth2ExceptionHandler" class="de.ids_mannheim.korap.web.OAuth2ExceptionHandler">
+	<bean id="oauth2ResponseHandler" class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
 		<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
 	</bean>