Implemented authorization code request, simplified client
authentication, and added tests.

Change-Id: Id6695cacc6da75da64588499ea3a7c7b1ad64591
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java b/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java
index 991e8f4..1c22fea 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java
@@ -44,7 +44,7 @@
 import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.user.DemoUser;
 import de.ids_mannheim.korap.user.KorAPUser;
-import de.ids_mannheim.korap.user.ShibUser;
+import de.ids_mannheim.korap.user.ShibbolethUser;
 import de.ids_mannheim.korap.user.User;
 import de.ids_mannheim.korap.user.User.CorpusAccess;
 import de.ids_mannheim.korap.user.User.Location;
@@ -389,7 +389,7 @@
 						StatusCodes.LOGIN_FAILED, username);
 			}
 
-		} else if (unknown instanceof ShibUser) {
+		} else if (unknown instanceof ShibbolethUser) {
 			// todo
 		}
 		jlog.debug("Authentication done: "+unknown);
@@ -521,7 +521,7 @@
 			 * username); }
 			 */
 
-		} else if (unknown instanceof ShibUser) {
+		} else if (unknown instanceof ShibbolethUser) {
 			// todo
 		}
 
@@ -753,17 +753,20 @@
 	}
 
 	// todo:
-	private ShibUser createShibbUserAccount(Map<String, Object> attributes) throws KustvaktException {
+	private ShibbolethUser createShibbUserAccount(Map<String, Object> attributes) throws KustvaktException {
 		jlog.debug("creating shibboleth user account for user attr: {}", attributes);
 		Map<String, Object> safeMap = validator.validateMap(attributes);
 
 		// todo eppn non-unique.join with idp or use persistent_id as username
 		// identifier
-		ShibUser user = User.UserFactory.getShibInstance((String) safeMap.get(Attributes.EPPN),
-				(String) safeMap.get(Attributes.MAIL), (String) safeMap.get(Attributes.CN));
-		user.setAffiliation((String) safeMap.get(Attributes.EDU_AFFIL));
-		user.setAccountCreation(TimeUtils.getNow().getMillis());
+		// EM: disabled
+//		ShibbolethUser user = User.UserFactory.getShibInstance((String) safeMap.get(Attributes.EPPN),
+//				(String) safeMap.get(Attributes.MAIL), (String) safeMap.get(Attributes.CN));
+//		user.setAffiliation((String) safeMap.get(Attributes.EDU_AFFIL));
+//		user.setAccountCreation(TimeUtils.getNow().getMillis());
 
+		ShibbolethUser user = null;
+		
 		UserDetails d = new UserDetails();
 		d.read(attributes, true);
 
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 6e75f65..e1759c4 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
@@ -6,7 +6,6 @@
 import javax.persistence.Query;
 import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.CriteriaQuery;
-import javax.persistence.criteria.Predicate;
 import javax.persistence.criteria.Root;
 
 import org.springframework.stereotype.Repository;
@@ -27,8 +26,9 @@
     private EntityManager entityManager;
 
     public void registerClient (String id, String secretHashcode, String name,
-            OAuth2ClientType type, boolean isNative, String url, int urlHashCode,
-            String redirectURI, String registeredBy) throws KustvaktException {
+            OAuth2ClientType type, boolean isNative, String url,
+            int urlHashCode, String redirectURI, String registeredBy,
+            String description) throws KustvaktException {
         ParameterChecker.checkStringValue(id, "client id");
         ParameterChecker.checkStringValue(name, "client name");
         ParameterChecker.checkObjectValue(type, "client type");
@@ -46,6 +46,7 @@
         client.setUrlHashCode(urlHashCode);
         client.setRedirectURI(redirectURI);
         client.setRegisteredBy(registeredBy);
+        client.setDescription(description);
         entityManager.persist(client);
     }
 
@@ -68,7 +69,7 @@
         }
         catch (NoResultException e) {
             throw new KustvaktException(StatusCodes.CLIENT_NOT_FOUND,
-                    "Unknown client with "+clientId+".", "invalid_client");
+                    "Unknown client with " + clientId + ".", "invalid_client");
         }
         catch (Exception e) {
             throw new KustvaktException(StatusCodes.CLIENT_NOT_FOUND,
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 760fc13..9efab11 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
@@ -37,11 +37,13 @@
     private String redirectURI;
     @Column(name = "registered_by")
     private String registeredBy;
+    private String description;
 
     @Override
     public String toString () {
-        return "id=" + id + ", secret=" + secret + ", type=" + type + ", name="
-                + name + ", url=" + url + ", redirectURI=" + redirectURI
-                + ", registeredBy=" + registeredBy;
+        return "id=" + id + ", name=" + name + ", secret=" + secret + ", type="
+                + type + ", isNative=" + isNative + ", url=" + url
+                + ", redirectURI=" + redirectURI + ", registeredBy="
+                + registeredBy + ", description=" + description;
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/handlers/EntityDao.java b/full/src/main/java/de/ids_mannheim/korap/handlers/EntityDao.java
index fd3c1e8..fd7fdc2 100644
--- a/full/src/main/java/de/ids_mannheim/korap/handlers/EntityDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/handlers/EntityDao.java
@@ -1,21 +1,11 @@
 package de.ids_mannheim.korap.handlers;
 
-import de.ids_mannheim.korap.config.ParamFields;
-import de.ids_mannheim.korap.config.URIParam;
-import de.ids_mannheim.korap.exceptions.EmptyResultException;
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
-import de.ids_mannheim.korap.exceptions.DatabaseException;
-import de.ids_mannheim.korap.interfaces.EntityHandlerIface;
-import de.ids_mannheim.korap.interfaces.KustvaktBaseDaoInterface;
-import de.ids_mannheim.korap.interfaces.db.PersistenceClient;
-import de.ids_mannheim.korap.user.KorAPUser;
-import de.ids_mannheim.korap.user.ShibUser;
-import de.ids_mannheim.korap.user.DemoUser;
-import de.ids_mannheim.korap.user.User;
-import de.ids_mannheim.korap.user.User.UserFactory;
-import de.ids_mannheim.korap.utils.BooleanUtils;
-import de.ids_mannheim.korap.utils.TimeUtils;
+import java.sql.Date;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.dao.DataAccessException;
@@ -26,11 +16,19 @@
 import org.springframework.jdbc.support.GeneratedKeyHolder;
 import org.springframework.jdbc.support.KeyHolder;
 
-import java.sql.Date;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import de.ids_mannheim.korap.config.ParamFields;
+import de.ids_mannheim.korap.config.URIParam;
+import de.ids_mannheim.korap.exceptions.DatabaseException;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.EntityHandlerIface;
+import de.ids_mannheim.korap.interfaces.KustvaktBaseDaoInterface;
+import de.ids_mannheim.korap.interfaces.db.PersistenceClient;
+import de.ids_mannheim.korap.user.KorAPUser;
+import de.ids_mannheim.korap.user.ShibbolethUser;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.BooleanUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
 
 /* WKP: In computer software, a data access object (DAO) is an object that provides an abstract interface to some type 
  * of database or other persistence mechanism. By mapping application calls to the persistence layer, the DAO provides 
@@ -154,14 +152,14 @@
                     //                    "uri_expiration=:exp "
                     + "WHERE id=:id";
         }
-        else if (user instanceof ShibUser) {
-            ShibUser s = (ShibUser) user;
+        else if (user instanceof ShibbolethUser) {
+            ShibbolethUser s = (ShibbolethUser) user;
             //todo:
             //            np.addValue("ali", s.getAccountLink());
             np.addValue("ali", null);
             np.addValue("edu", s.getAffiliation());
             np.addValue("id", s.getId());
-            np.addValue("cn", s.getCn());
+            np.addValue("cn", s.getCommonName());
             np.addValue("mail", s.getMail());
 
             query = "UPDATE shibusers SET account_link=:ali"
@@ -225,8 +223,8 @@
 
             //fixme: still applicable?
         }
-        else if (user instanceof ShibUser) {
-            ShibUser s = (ShibUser) user;
+        else if (user instanceof ShibbolethUser) {
+            ShibbolethUser s = (ShibbolethUser) user;
 
             query = "INSERT INTO shibusers (username, type, account_link, account_creation "
                     + "eduPersonScopedAffiliation, cn, mail) "
@@ -237,7 +235,7 @@
             np.addValue("edu", s.getAffiliation());
             np.addValue("mail", s.getMail());
             np.addValue("type", user.getType());
-            np.addValue("cn", s.getCn());
+            np.addValue("cn", s.getCommonName());
             np.addValue("acr", System.currentTimeMillis());
 
             //todo: deprecate
diff --git a/full/src/main/java/de/ids_mannheim/korap/handlers/RowMapperFactory.java b/full/src/main/java/de/ids_mannheim/korap/handlers/RowMapperFactory.java
index d18ea69..39b7efa 100644
--- a/full/src/main/java/de/ids_mannheim/korap/handlers/RowMapperFactory.java
+++ b/full/src/main/java/de/ids_mannheim/korap/handlers/RowMapperFactory.java
@@ -6,7 +6,7 @@
 import de.ids_mannheim.korap.resources.ResourceFactory;
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.user.KorAPUser;
-import de.ids_mannheim.korap.user.ShibUser;
+import de.ids_mannheim.korap.user.ShibbolethUser;
 import de.ids_mannheim.korap.user.User;
 import org.springframework.jdbc.core.RowMapper;
 
@@ -39,9 +39,9 @@
                 case 0:
                     user = getKorAP(rs);
                     break;
-                case 1:
-                    user = getShib(rs);
-                    break;
+//                case 1:
+//                    user = getShib(rs);
+//                    break;
                 default:
                     user = User.UserFactory.getDemoUser();
                     user.setId(rs.getInt("id"));
@@ -71,13 +71,13 @@
         }
 
 
-        private ShibUser getShib (ResultSet rs) throws SQLException {
-            ShibUser user = User.UserFactory.getShibInstance(
-                    rs.getString(Attributes.USERNAME),
-                    rs.getString(Attributes.MAIL), rs.getString(Attributes.CN));
-            user.setId(rs.getInt(Attributes.ID));
-            return user;
-        }
+//        private ShibbolethUser getShib (ResultSet rs) throws SQLException {
+//            ShibbolethUser user = User.UserFactory.getShibInstance(
+//                    rs.getString(Attributes.USERNAME),
+//                    rs.getString(Attributes.MAIL), rs.getString(Attributes.CN));
+//            user.setId(rs.getInt(Attributes.ID));
+//            return user;
+//        }
 
     }
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth/ClientDeregistrationValidator.java b/full/src/main/java/de/ids_mannheim/korap/oauth/ClientDeregistrationValidator.java
new file mode 100644
index 0000000..60525b0
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth/ClientDeregistrationValidator.java
@@ -0,0 +1,23 @@
+package de.ids_mannheim.korap.oauth;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.oltu.oauth2.common.OAuth;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.utils.OAuthUtils;
+import org.apache.oltu.oauth2.common.validators.AbstractValidator;
+
+public class ClientDeregistrationValidator extends AbstractValidator<HttpServletRequest>{
+
+    public ClientDeregistrationValidator () {
+        enforceClientAuthentication = true;
+    }
+    
+    @Override
+    public void validateMethod (HttpServletRequest request)
+            throws OAuthProblemException {
+        if (!request.getMethod().equals(OAuth.HttpMethod.DELETE)) {
+            throw OAuthUtils.handleOAuthProblemException("Method not set to DELETE.");
+        }
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth/OAuthDeregisterClientRequest.java b/full/src/main/java/de/ids_mannheim/korap/oauth/OAuthDeregisterClientRequest.java
new file mode 100644
index 0000000..88ae668
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth/OAuthDeregisterClientRequest.java
@@ -0,0 +1,27 @@
+package de.ids_mannheim.korap.oauth;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.oltu.oauth2.as.request.OAuthRequest;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.utils.OAuthUtils;
+import org.apache.oltu.oauth2.common.validators.OAuthValidator;
+
+public class OAuthDeregisterClientRequest extends OAuthRequest {
+
+    public OAuthDeregisterClientRequest (HttpServletRequest request)
+            throws OAuthSystemException, OAuthProblemException {
+        super(request);
+    }
+
+    @Override
+    protected OAuthValidator<HttpServletRequest> initValidator ()
+            throws OAuthProblemException, OAuthSystemException {
+        validators.put("client_deregistration",
+                ClientDeregistrationValidator.class);
+        final Class<? extends OAuthValidator<HttpServletRequest>> clazz =
+                validators.get("client_deregistration");
+        return OAuthUtils.instantiateClass(clazz);
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2AuthorizationService.java b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2AuthorizationService.java
new file mode 100644
index 0000000..751c7d2
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2AuthorizationService.java
@@ -0,0 +1,118 @@
+package de.ids_mannheim.korap.service;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
+import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
+import org.apache.oltu.oauth2.as.response.OAuthASResponse;
+import org.apache.oltu.oauth2.common.error.OAuthError;
+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.Service;
+
+import com.sun.jersey.api.client.ClientResponse.Status;
+
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.entity.OAuth2Client;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+
+@Service
+public class OAuth2AuthorizationService {
+
+    @Autowired
+    private OAuth2ClientService clientService;
+    @Autowired
+    private OAuth2Service auth2Service;
+    @Autowired
+    private OAuthIssuer oauthIssuer;
+
+    public OAuthResponse requestAuthorizationCode (HttpServletRequest request,
+            OAuthAuthzRequest authzRequest, String authorization)
+            throws KustvaktException, OAuthSystemException {
+
+        String responseType = authzRequest.getResponseType();
+        if (responseType == null || responseType.isEmpty()) {
+            throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
+                    "response_type is missing.",
+                    OAuthError.CodeResponse.INVALID_REQUEST);
+        }
+
+        OAuth2Client client;
+        try {
+            client = clientService.authenticateClient(
+                    authzRequest.getClientId(), authzRequest.getClientSecret());
+        }
+        catch (KustvaktException e) {
+            e.setEntity(OAuthError.CodeResponse.UNAUTHORIZED_CLIENT);
+            throw e;
+        }
+
+        String redirectUri = authzRequest.getRedirectURI();
+        boolean hasRedirectUri = hasRedirectUri(redirectUri);
+        redirectUri = verifyRedirectUri(client, hasRedirectUri, redirectUri);
+
+        auth2Service.authenticateUser(
+                authzRequest.getParam(Attributes.USERNAME),
+                authzRequest.getParam(Attributes.PASSWORD),
+                authzRequest.getScopes());
+
+        return OAuthASResponse
+                .authorizationResponse(request, Status.FOUND.getStatusCode())
+                .setCode(oauthIssuer.authorizationCode()).location(redirectUri)
+                .buildQueryMessage();
+    }
+
+
+    private boolean hasRedirectUri (String redirectURI) {
+        if (redirectURI != null && !redirectURI.isEmpty()) {
+            return true;
+        }
+        return false;
+    }
+
+    /** If the request contains a redirect_uri parameter, the server must confirm 
+     *  it is a valid redirect URI. 
+     *  
+     *  If there is no redirect_uri parameter in the request, and only one URI 
+     *  was registered, the server uses the redirect URL that was previously 
+     *  registered. 
+     *  
+     *  If no redirect URL has been registered, this is an error.
+     *  
+     * @param client an OAuth2Client
+     * @param hasRedirectUri true if request contains redirect_uri, false otherwise
+     * @param redirectUri the redirect_uri value
+     * @return a client's redirect URI
+     * @throws KustvaktException
+     */
+    private String verifyRedirectUri (OAuth2Client client,
+            boolean hasRedirectUri, String redirectUri)
+            throws KustvaktException {
+
+        String registeredUri = client.getRedirectURI();
+        if (hasRedirectUri) {
+            // check if the redirect URI the same as that in DB
+            if (!redirectUri.equals(registeredUri)) {
+                throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+                        redirectUri + " is unknown",
+                        OAuthError.CodeResponse.INVALID_REQUEST);
+            }
+        }
+        else {
+            // check if there is a redirect URI in the DB
+            // This should not happened as it is required in client registration!
+            if (registeredUri != null && !registeredUri.isEmpty()) {
+                redirectUri = registeredUri;
+            }
+            else {
+                throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
+                        "redirect_uri is required",
+                        OAuthError.CodeResponse.INVALID_REQUEST);
+            }
+        }
+
+        return redirectUri;
+    }
+}
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 4072cf7..1089c45 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
@@ -8,14 +8,12 @@
 
 import org.apache.commons.validator.routines.UrlValidator;
 import org.apache.log4j.Logger;
+import org.apache.oltu.oauth2.as.request.OAuthRequest;
 import org.apache.oltu.oauth2.common.error.OAuthError;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import de.ids_mannheim.korap.authentication.http.AuthorizationData;
-import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.FullConfiguration;
-import de.ids_mannheim.korap.constant.AuthenticationScheme;
 import de.ids_mannheim.korap.constant.OAuth2ClientType;
 import de.ids_mannheim.korap.dao.AdminDao;
 import de.ids_mannheim.korap.dao.OAuth2ClientDao;
@@ -57,8 +55,6 @@
     @Autowired
     private EncryptionIface encryption;
     @Autowired
-    private HttpAuthorizationHandler authorizationHandler;
-    @Autowired
     private FullConfiguration config;
 
     public OAuth2ClientDto registerClient (OAuth2ClientJson clientJson,
@@ -101,7 +97,7 @@
             clientDao.registerClient(id, secretHashcode, clientJson.getName(),
                     clientJson.getType(), isNative, clientJson.getUrl(),
                     clientJson.getUrl().hashCode(), clientJson.getRedirectURI(),
-                    registeredBy);
+                    registeredBy, clientJson.getDescription());
         }
         catch (Exception e) {
             Throwable cause = e;
@@ -140,7 +136,8 @@
         }
         catch (URISyntaxException e) {
             throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
-                    "Invalid redirectURI: "+e.getMessage(), OAuthError.TokenResponse.INVALID_REQUEST);
+                    "Invalid redirectURI: " + e.getMessage(),
+                    OAuthError.TokenResponse.INVALID_REQUEST);
         }
         boolean isNative =
                 urlHost.equals(nativeHost) && uriHost.equals(nativeHost);
@@ -152,7 +149,7 @@
     public void deregisterPublicClient (String clientId, String username)
             throws KustvaktException {
 
-        OAuth2Client client = retrieveClientById(clientId);
+        OAuth2Client client = clientDao.retrieveClientById(clientId);
         if (adminDao.isAdmin(username)) {
             clientDao.deregisterClient(client);
         }
@@ -174,98 +171,46 @@
     }
 
 
-    public void deregisterConfidentialClient (String authorization,
-            String clientId) throws KustvaktException {
-        OAuth2Client client =
-                authenticateClientByBasicAuthorization(authorization, clientId);
+    public void deregisterConfidentialClient (OAuthRequest oAuthRequest)
+            throws KustvaktException {
+
+        OAuth2Client client = authenticateClient(oAuthRequest.getClientId(),
+                oAuthRequest.getClientSecret());
         clientDao.deregisterClient(client);
     }
 
-    public OAuth2Client authenticateClient (String authorization,
-            String clientId) throws KustvaktException {
-        OAuth2Client client;
-        if (authorization == null || authorization.isEmpty()) {
-            client = authenticateClientById(clientId);
-            if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
-                throw new KustvaktException(
-                        StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                        "Client authentication using authorization header is required.",
-                        OAuthError.TokenResponse.INVALID_CLIENT);
-            }
-        }
-        else {
-            client = authenticateClientByBasicAuthorization(authorization,
-                    clientId);
-        }
-        return client;
-    }
+    public OAuth2Client authenticateClient (String clientId,
+            String clientSecret) throws KustvaktException {
 
-    public OAuth2Client authenticateClientById (String clientId)
-            throws KustvaktException {
-        if (clientId == null || clientId.equals("null") || clientId.isEmpty()) {
+        if (clientId == null || clientId.isEmpty()) {
             throw new KustvaktException(
                     StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                    "client_id is missing",
-                    OAuthError.TokenResponse.INVALID_REQUEST);
+                    "Missing parameters: client id", "invalid_request");
         }
-        else {
-            return retrieveClientById(clientId);
-        }
-    }
 
-    public OAuth2Client authenticateClientByBasicAuthorization (
-            String authorization, String clientId) throws KustvaktException {
-
-        if (authorization == null || authorization.isEmpty()) {
-            throw new KustvaktException(
-                    StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                    "Authorization header is not found.",
-                    OAuthError.TokenResponse.INVALID_CLIENT);
-        }
-        else {
-            AuthorizationData authData = authorizationHandler
-                    .parseAuthorizationHeaderValue(authorization);
-            if (authData.getAuthenticationScheme()
-                    .equals(AuthenticationScheme.BASIC)) {
-                authorizationHandler.parseBasicToken(authData);
-                return verifyClientCredentials(clientId, authData);
-            }
-            else {
+        OAuth2Client client = clientDao.retrieveClientById(clientId);
+        if (clientSecret == null || clientSecret.isEmpty()) {
+            if (client.getSecret() != null
+                    || client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
                 throw new KustvaktException(
                         StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                        "Client authentication with " + authData
-                                .getAuthenticationScheme().displayName()
-                                + "is not supported",
-                        "invalid_client");
+                        "Missing parameters: client_secret", "invalid_request");
+            }
+            else
+                return client;
+        }
+        else {
+            if (client.getSecret() != null) {
+                if (encryption.checkHash(clientSecret, client.getSecret(),
+                        config.getPasscodeSaltField())) {
+                    return client;
+                }
             }
         }
+
+        throw new KustvaktException(StatusCodes.CLIENT_AUTHENTICATION_FAILED,
+                "Invalid client credentials",
+                OAuthError.TokenResponse.INVALID_CLIENT);
     }
 
-    private OAuth2Client verifyClientCredentials (String clientId,
-            AuthorizationData authData) throws KustvaktException {
-
-        OAuth2Client client = retrieveClientById(authData.getUsername());
-        // EM: not sure if this is necessary
-        if (clientId != null && !clientId.isEmpty()) {
-            if (!client.getId().equals(clientId)) {
-                throw new KustvaktException(
-                        StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                        "Invalid client credentials.",
-                        OAuthError.TokenResponse.INVALID_CLIENT);
-            }
-        }
-        if (!encryption.checkHash(authData.getPassword(), client.getSecret(),
-                config.getPasscodeSaltField())) {
-            throw new KustvaktException(
-                    StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                    "Invalid client credentials.",
-                    OAuthError.TokenResponse.INVALID_CLIENT);
-        }
-        return client;
-    }
-
-    public OAuth2Client retrieveClientById (String clientId)
-            throws KustvaktException {
-        return clientDao.retrieveClientById(clientId);
-    }
 }
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 4ec5dac..9e8092b 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
@@ -4,15 +4,12 @@
 import java.util.Map;
 import java.util.Set;
 
-import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Response.Status;
 
-import org.apache.oltu.oauth2.as.issuer.MD5Generator;
 import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
-import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
-import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
+import org.apache.oltu.oauth2.as.request.AbstractOAuthTokenRequest;
 import org.apache.oltu.oauth2.as.response.OAuthASResponse;
 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 org.apache.oltu.oauth2.common.message.types.GrantType;
@@ -36,12 +33,27 @@
     private FullConfiguration config;
     @Autowired
     private AuthenticationManagerIface authenticationManager;
+    @Autowired
+    private OAuthIssuer oauthIssuer;
 
     /** 
-     *  RFC 6749:
+     * OAuth2 describes various ways for requesting an access token. Kustvakt 
+     * supports:
+     * <ul>
+     * <li> Authorization code grant: obtains authorization from a third party
+     * application.
+     * </li>
+     * <li> Resource owner password grant: strictly for clients that are parts 
+     *   of KorAP. Clients use user credentials, e.g. Kalamar (front-end) with 
+     *   login form. 
+     * </li>
+     * <li> Client credentials grant: strictly for clients that are parts 
+     *   of KorAP. Clients access their own resources, not on behalf of a 
+     *   user.
+     * </li>
+     * </ul>  
      *  
-     *  If the client type is confidential or the client was issued client
-     *  credentials, the client MUST authenticate with the authorization server.
+     *  
      * @param request 
      *  
      * @param oAuthRequest
@@ -50,24 +62,25 @@
      * @throws KustvaktException
      * @throws OAuthSystemException
      */
-    public OAuthResponse requestAccessToken (OAuthTokenRequest oAuthRequest,
-            String authorization)
+    public OAuthResponse requestAccessToken (
+            AbstractOAuthTokenRequest oAuthRequest)
             throws KustvaktException, OAuthSystemException {
 
         String grantType = oAuthRequest.getGrantType();
 
         if (grantType.equals(GrantType.AUTHORIZATION_CODE.toString())) {
-            return requestAccessTokenWithAuthorizationCode(authorization,
+            return requestAccessTokenWithAuthorizationCode(
                     oAuthRequest.getCode(), oAuthRequest.getRedirectURI(),
-                    oAuthRequest.getClientId());
+                    oAuthRequest.getClientId(), oAuthRequest.getClientSecret());
         }
         else if (grantType.equals(GrantType.PASSWORD.toString())) {
-            return requestAccessTokenWithPassword(authorization,
-                    oAuthRequest.getUsername(), oAuthRequest.getPassword(),
-                    oAuthRequest.getScopes(), oAuthRequest.getClientId());
+            return requestAccessTokenWithPassword(oAuthRequest.getUsername(),
+                    oAuthRequest.getPassword(), oAuthRequest.getScopes(),
+                    oAuthRequest.getClientId(), oAuthRequest.getClientSecret());
         }
         else if (grantType.equals(GrantType.CLIENT_CREDENTIALS.toString())) {
-            return requestAccessTokenWithClientCredentials(authorization,
+            return requestAccessTokenWithClientCredentials(
+                    oAuthRequest.getClientId(), oAuthRequest.getClientSecret(),
                     oAuthRequest.getScopes());
         }
         else {
@@ -78,25 +91,31 @@
 
     }
 
-    /** Confidential clients must authenticate
+    /**
+     * RFC 6749: 
+     *  If the client type is confidential or the client was issued client
+     *  credentials, the client MUST authenticate with the authorization server.
      * 
-     * @param authorization
      * @param authorizationCode
-     * @param redirectURI
+     * @param redirectURI required if included in the authorization request
      * @param clientId required if there is no authorization header
+     * @param clientSecret clilent_secret, required if client_secret was issued 
+     *  for the client in client registration.
      * @return
      * @throws OAuthSystemException
      * @throws KustvaktException
-     * @throws OAuthProblemException 
      */
     private OAuthResponse requestAccessTokenWithAuthorizationCode (
-            String authorization, String authorizationCode, String redirectURI,
-            String clientId) throws KustvaktException {
-        OAuth2Client client =
-                clientService.authenticateClient(authorization, clientId);
+            String authorizationCode, String redirectURI, String clientId,
+            String clientSecret)
+            throws KustvaktException, OAuthSystemException {
+
+        clientService.authenticateClient(clientId, clientSecret);
 
         // TODO
-        return null;
+        // check authorization code
+        // check redirectURI
+        return createsAccessTokenResponse();
     }
 
 
@@ -104,30 +123,32 @@
     /**  Third party apps must not be allowed to use password grant.
      * MH: password grant is only allowed for trusted clients (korap frontend)
      *  
-     * A similar rule to that of authorization code grant is additionally 
-     * applied, namely client_id is required when authorization header is not 
-     * available.
+     * According to RFC 6749, client authentication is only required for 
+     * confidential clients and whenever client credentials are provided.
+     * Moreover, client_id is optional for password grant, but without it, 
+     * the authentication server cannot check the client type. 
      * 
-     * According to RFC 6749, client_id is optional for password grant, 
-     * but without it, server would not be able to check the client 
-     * type, thus cannot make sure that confidential clients authenticate. 
+     * To make sure that confidential clients authenticate, client_id is made 
+     * required (similar to authorization code grant).
      * 
-     * @param authorization
-     * @param username
-     * @param password
+     * 
+     * @param username username, required
+     * @param password user password, required
      * @param scopes
-     * @param clientId
+     * @param clientId client_id, required
+     * @param clientSecret clilent_secret, required if client_secret was issued 
+     *  for the client in client registration.
      * @return
      * @throws KustvaktException
-     * @throws OAuthSystemException 
+     * @throws OAuthSystemException
      */
-    private OAuthResponse requestAccessTokenWithPassword (String authorization,
-            String username, String password, Set<String> scopes,
-            String clientId) throws KustvaktException, OAuthSystemException {
+    private OAuthResponse requestAccessTokenWithPassword (String username,
+            String password, Set<String> scopes, String clientId,
+            String clientSecret)
+            throws KustvaktException, OAuthSystemException {
 
         OAuth2Client client =
-                clientService.authenticateClient(authorization, clientId);
-
+                clientService.authenticateClient(clientId, clientSecret);
         if (!client.isNative()) {
             throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
                     "Password grant is not allowed for third party clients",
@@ -138,7 +159,7 @@
         return createsAccessTokenResponse();
     }
 
-    private void authenticateUser (String username, String password,
+    public void authenticateUser (String username, String password,
             Set<String> scopes) throws KustvaktException {
         if (username == null || username.isEmpty()) {
             throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
@@ -162,50 +183,46 @@
 
     /** Clients must authenticate
      * 
-     * @param authorization
+     * @param clientId client_id parameter, required
+     * @param clientSecret client_secret parameter, required
      * @param scopes
-     * @param request 
      * @return
-     * @throws KustvaktException 
-     * @throws OAuthSystemException 
+     * @throws KustvaktException
+     * @throws OAuthSystemException
      */
     private OAuthResponse requestAccessTokenWithClientCredentials (
-            String authorization, Set<String> scopes)
+            String clientId, String clientSecret, Set<String> scopes)
             throws KustvaktException, OAuthSystemException {
 
-        if (authorization == null || authorization.isEmpty()) {
+        if (clientSecret == null || clientSecret.isEmpty()) {
             throw new KustvaktException(
                     StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                    "Client authentication using authorization header is required.",
-                    OAuthError.TokenResponse.INVALID_CLIENT);
+                    "Missing parameters: client_secret", "invalid_request");
         }
-        else {
-            clientService.authenticateClientByBasicAuthorization(authorization,
-                    null);
-            return createsAccessTokenResponse();
-        }
+
+        clientService.authenticateClient(clientId, clientSecret);
+        return createsAccessTokenResponse();
     }
 
 
-    /**
-     * @param request  
-     * @return 
-     * @throws OAuthSystemException 
+
+    /** Creates an OAuthResponse containing an access token and a refresh token 
+     *  with type Bearer.
      * 
+     * @return an OAuthResponse containing an access token
+     * @throws OAuthSystemException
      */
     private OAuthResponse createsAccessTokenResponse ()
             throws OAuthSystemException {
-        OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
-        OAuthResponse r = null;
+        String accessToken = oauthIssuer.accessToken();
+        String refreshToken = oauthIssuer.refreshToken();
 
-        String accessToken = oauthIssuerImpl.accessToken();
-        String refreshToken = oauthIssuerImpl.refreshToken();
-
-        r = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK)
-                .setAccessToken(accessToken)
-                .setTokenType(TokenType.BEARER.toString())
-                .setExpiresIn(String.valueOf(config.getTokenTTL()))
-                .setRefreshToken(refreshToken).buildJSONMessage();
+        OAuthResponse r =
+                OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
+                        .setAccessToken(accessToken)
+                        .setTokenType(TokenType.BEARER.toString())
+                        .setExpiresIn(String.valueOf(config.getTokenTTL()))
+                        .setRefreshToken(refreshToken).buildJSONMessage();
         // scope
         return r;
     }
diff --git a/full/src/main/java/de/ids_mannheim/korap/user/ShibbolethUser.java b/full/src/main/java/de/ids_mannheim/korap/user/ShibbolethUser.java
new file mode 100644
index 0000000..80d01f5
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/user/ShibbolethUser.java
@@ -0,0 +1,90 @@
+package de.ids_mannheim.korap.user;
+
+/**
+ * User: hanl
+ * Date: 10/16/13
+ * Time: 2:02 PM
+ * 
+ * @author margaretha
+ * @last-update 18/04/2018
+ */
+public class ShibbolethUser extends User {
+
+    /**
+     * Auto generated serial Id
+     */
+    private static final long serialVersionUID = -4008236368010397075L;
+    private String mail;
+    private String affiliation;
+    // EM: common name
+    private String commonName;
+
+
+    protected ShibbolethUser () {
+        super(1);
+    }
+
+
+    private ShibbolethUser (String eduPersonID, String mail, String cn,
+                      String affiliation) {
+        this(eduPersonID);
+        this.setUsername(eduPersonID);
+        this.setMail(mail);
+        this.setAffiliation(affiliation);
+        this.setCommonName(cn);
+    }
+
+
+    public ShibbolethUser (String username) {
+        super(username, 1);
+
+    }
+
+
+    @Override
+    public String toString () {
+        final StringBuffer sb = new StringBuffer("ShibbolethUser{");
+        sb.append(", mail='").append(getMail()).append('\'');
+        sb.append(", affiliation='").append(getAffiliation()).append('\'');
+        sb.append(", common-name='").append(getCommonName()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+
+    @Override
+    protected User clone () {
+        return new ShibbolethUser(this.getUsername(), this.getMail(), this.getCommonName(),
+                this.getAffiliation());
+    }
+
+
+    public String getMail () {
+        return mail;
+    }
+
+
+    public void setMail (String mail) {
+        this.mail = mail;
+    }
+
+
+    public String getAffiliation () {
+        return affiliation;
+    }
+
+
+    public void setAffiliation (String affiliation) {
+        this.affiliation = affiliation;
+    }
+
+
+    public String getCommonName () {
+        return commonName;
+    }
+
+
+    public void setCommonName (String commonName) {
+        this.commonName = commonName;
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
index 5df68b2..0d31236 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
@@ -1,5 +1,8 @@
 package de.ids_mannheim.korap.web;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
@@ -92,7 +95,9 @@
         OAuthResponse oAuthResponse = null;
         String errorCode = e.getEntity();
         try {
-            if (errorCode.equals(OAuthError.TokenResponse.INVALID_CLIENT)) {
+            if (errorCode.equals(OAuthError.TokenResponse.INVALID_CLIENT)
+                    || errorCode.equals(
+                            OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)) {
                 oAuthResponse = createOAuthResponse(e,
                         Status.UNAUTHORIZED.getStatusCode());
             }
@@ -100,8 +105,6 @@
                     || errorCode
                             .equals(OAuthError.TokenResponse.INVALID_REQUEST)
                     || errorCode.equals(OAuthError.TokenResponse.INVALID_SCOPE)
-                    || errorCode
-                            .equals(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
                     || errorCode.equals(
                             OAuthError.TokenResponse.UNSUPPORTED_GRANT_TYPE)) {
                 oAuthResponse = createOAuthResponse(e,
@@ -142,4 +145,16 @@
         }
         return builder.build();
     }
+
+    public Response sendRedirect (String locationUri) throws KustvaktException {
+        try {
+            ResponseBuilder builder =
+                    Response.temporaryRedirect(new URI(locationUri));
+            return builder.build();
+        }
+        catch (URISyntaxException e) {
+            throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
+                    e.getMessage(), OAuthError.CodeResponse.INVALID_REQUEST);
+        }
+    }
 }
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 ece03f2..b0699b1 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,6 +2,7 @@
 
 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;
@@ -10,16 +11,20 @@
 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 org.apache.oltu.oauth2.as.request.AbstractOAuthTokenRequest;
+import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
 import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
+import org.apache.oltu.oauth2.as.request.OAuthUnauthenticatedTokenRequest;
 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.apache.oltu.oauth2.common.message.types.GrantType;
 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.OAuth2AuthorizationService;
 import de.ids_mannheim.korap.service.OAuth2Service;
 import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
 import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
@@ -31,7 +36,50 @@
     @Autowired
     private OAuth2ResponseHandler responseHandler;
     @Autowired
-    private OAuth2Service oauth2Service;
+    private OAuth2Service oAuth2Service;
+    @Autowired
+    private OAuth2AuthorizationService authorizationService;
+
+    /** Kustvakt supports authorization only with Kalamar as the authorization 
+     * web-frontend or user interface. Thus authorization code request requires
+     * user credentials in the request body, similar to access token request in
+     * resource owner password grant request. 
+     * 
+     * @param request
+     * @param authorization
+     * @param form
+     * @return
+     */
+    @POST
+    @Path("authorize")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+    public Response requestAuthorizationCode (
+            @Context HttpServletRequest request,
+            @HeaderParam("Authorization") String authorization,
+            MultivaluedMap<String, String> form) {
+
+        try {
+            HttpServletRequest requestWithForm =
+                    new FormRequestWrapper(request, form);
+            OAuthAuthzRequest authzRequest =
+                    new OAuthAuthzRequest(requestWithForm);
+            OAuthResponse authResponse =
+                    authorizationService.requestAuthorizationCode(
+                            requestWithForm, authzRequest, authorization);
+            return responseHandler.sendRedirect(authResponse.getLocationUri());
+        }
+        catch (OAuthSystemException e) {
+            throw responseHandler.throwit(e);
+        }
+        catch (OAuthProblemException e) {
+            throw responseHandler.throwit(e);
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+    }
+
 
     /** Grants a client an access token, namely a string used in authenticated 
      *  requests representing user authorization for the client to access user 
@@ -49,27 +97,32 @@
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
     public Response requestAccessToken (@Context HttpServletRequest request,
-            @HeaderParam("Authorization") String authorization,
+            @FormParam("grant_type") String grantType,
             MultivaluedMap<String, String> form) {
 
         try {
-            OAuthTokenRequest oAuthRequest = null;
-            try {
+            AbstractOAuthTokenRequest oAuthRequest = null;
+            if (grantType != null && !grantType.isEmpty() && grantType
+                    .equals(GrantType.CLIENT_CREDENTIALS.toString())) {
                 oAuthRequest = new OAuthTokenRequest(
                         new FormRequestWrapper(request, form));
             }
-            catch (OAuthProblemException e) {
-                throw responseHandler.throwit(e);
+            else {
+                oAuthRequest = new OAuthUnauthenticatedTokenRequest(
+                        new FormRequestWrapper(request, form));
             }
 
-            OAuthResponse oAuthResponse = oauth2Service
-                    .requestAccessToken(oAuthRequest, authorization);
+            OAuthResponse oAuthResponse =
+                    oAuth2Service.requestAccessToken(oAuthRequest);
 
             return responseHandler.createResponse(oAuthResponse);
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
         }
+        catch (OAuthProblemException 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 9c4834c..39b93b7 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
@@ -1,17 +1,21 @@
 package de.ids_mannheim.korap.web.controller;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 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.SecurityContext;
 
+import org.apache.oltu.oauth2.as.request.OAuthRequest;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
@@ -19,12 +23,14 @@
 
 import de.ids_mannheim.korap.dto.OAuth2ClientDto;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth.OAuthDeregisterClientRequest;
 import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.service.OAuth2ClientService;
 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;
+import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
 
 
 /** Defines controllers for OAuth2 clients, namely applications attempting
@@ -111,14 +117,25 @@
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response deregisterConfidentialClient (
             @Context SecurityContext securityContext,
-            @HeaderParam("Authorization") String authorization,
-            @FormParam("client_id") String clientId) {
+            //            @HeaderParam("Authorization") String authorization,
+            //            @FormParam("client_id") String clientId
+            @Context HttpServletRequest request,
+            MultivaluedMap<String, String> form) {
         try {
-            clientService.deregisterConfidentialClient(authorization, clientId);
+            OAuthRequest oAuthRequest = new OAuthDeregisterClientRequest(
+                    new FormRequestWrapper(request, form));
+
+            clientService.deregisterConfidentialClient(oAuthRequest);
             return Response.ok().build();
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
         }
+        catch (OAuthSystemException e) {
+            throw responseHandler.throwit(e);
+        }
+        catch (OAuthProblemException e) {
+            throw responseHandler.throwit(e);
+        }
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
index e64bcfe..c6a3a97 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
@@ -233,7 +233,7 @@
      * @param pageLength number of results per page
      * @param pageIndex
      * @param startPage
-     * @param cq collection query
+     * @param cq corpus query
      * @return
      */
     // ref query parameter removed!
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java b/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
index 2dc4034..99705e8 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
@@ -20,4 +20,5 @@
     // redirect URI determines where the OAuth 2.0 service will return the user to 
     // after they have authorized a client. It must be https.
     private String redirectURI;
+    private String description;
 }
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
index 3cc2999..f9cd171 100644
--- 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
@@ -2,21 +2,33 @@
 
 -- plain secret value is "secret"
 INSERT INTO oauth2_client(id,name,secret,type,native, url,url_hashcode,
-  redirect_uri,registered_by) 
-VALUES ("fCBbQkAyYzI4NzUxMg==","test confidential client",
+  redirect_uri,registered_by, description) 
+VALUES ("fCBbQkAyYzI4NzUxMg","test confidential client",
   "$2a$08$vi1FbuN3p6GcI1tSxMAoeuIYL8Yw3j6A8wJthaN8ZboVnrQaTwLPq",
   "CONFIDENTIAL", 1, "http://korap.ids-mannheim.de/confidential", 2087150261, 
-  "https://korap.ids-mannheim.de/confidential/redirect", "system");
+  "https://korap.ids-mannheim.de/confidential/redirect", "system",
+  "This is a test native confidential client.");
+  
+-- plain secret value is "secret"
+INSERT INTO oauth2_client(id,name,secret,type,native, url,url_hashcode,
+  redirect_uri,registered_by, description) 
+VALUES ("9aHsGW6QflV13ixNpez","test non native confidential client",
+  "$2a$08$vi1FbuN3p6GcI1tSxMAoeuIYL8Yw3j6A8wJthaN8ZboVnrQaTwLPq",
+  "CONFIDENTIAL", 0, "http://third.party.com/confidential", 1712550103, 
+  "https://third.party.com/confidential/redirect", "system",
+  "This is a test nonnative confidential client.");
   
 INSERT INTO oauth2_client(id,name,secret,type,url,url_hashcode,
-  redirect_uri, registered_by) 
-VALUES ("8bIDtZnH6NvRkW2Fq==","third party client",null,
+  redirect_uri, registered_by, description) 
+VALUES ("8bIDtZnH6NvRkW2Fq","third party client",null,
   "PUBLIC","http://third.party.client.com", -2137275617,
-  "https://third.party.client.com/redirect","system");
+  "https://third.party.client.com/redirect","system",
+  "This is a test nonnative public client.");
   
 INSERT INTO oauth2_client(id,name,secret,type,native,url,url_hashcode,
-  redirect_uri, registered_by) 
+  redirect_uri, registered_by, description) 
 VALUES ("iBr3LsTCxOj7D2o0A5m","test public client",null,
   "PUBLIC", 1, "http://korap.ids-mannheim.de/public", 1360724310,
-  "https://korap.ids-mannheim.de/public/redirect","system"); 
+  "https://korap.ids-mannheim.de/public/redirect","system", 
+  "This is a test native public client."); 
   
\ 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 c55ef43..81b0d92 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
@@ -10,7 +10,8 @@
 	url TEXT NOT NULL,
 	url_hashcode UNIQUE INTEGER NOT NULL,
 	redirect_uri TEXT NOT NULL,
-	registered_by VARCHAR(100) NOT NULL
+	registered_by VARCHAR(100) NOT NULL,
+	description VARCHAR(250) 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 89014f2..c055f4d 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
@@ -10,7 +10,8 @@
 	url TEXT NOT NULL,
 	url_hashcode INTEGER NOT NULL,
 	redirect_uri TEXT NOT NULL,
-	registered_by VARCHAR(100) NOT NULL
+	registered_by VARCHAR(100) NOT NULL,
+	description VARCHAR(250) NOT NULL
 );
 
 CREATE UNIQUE INDEX client_id_index on oauth2_client(id);
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index feef295..57f65ae 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -184,10 +184,17 @@
 	<bean id="kustvaktResponseHandler" class="de.ids_mannheim.korap.web.KustvaktExceptionHandler">
 		<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
 	</bean>
-	<bean id="oauth2_exception" class="de.ids_mannheim.korap.web.OAuth2ExceptionHandler">
+	
+	<!-- OAuth -->
+	<bean id="oauth2ResponseHandler" class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
 		<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
 	</bean>
-
+	
+	<bean id="mdGenerator" class="org.apache.oltu.oauth2.as.issuer.MD5Generator">
+	</bean>
+	<bean id="oauthIssuer" class="org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl">
+		<constructor-arg index="0" ref="mdGenerator" />
+	</bean>
 
 	<bean id="kustvakt_userdb" class="de.ids_mannheim.korap.handlers.EntityDao">
 		<constructor-arg ref="kustvakt_db" />
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 66d610f..b194e08 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
@@ -62,6 +62,7 @@
         json.setType(OAuth2ClientType.CONFIDENTIAL);
         json.setUrl("http://example.client.com");
         json.setRedirectURI("https://example.client.com/redirect");
+        json.setDescription("This is a confidential test client.");
 
         return resource().path("oauth2").path("client").path("register")
                 .header(Attributes.AUTHORIZATION,
@@ -89,8 +90,9 @@
         assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
                 node.at("/error").asText());
 
+        testDeregisterConfidentialClientMissingParameters();
         testDeregisterClientIncorrectCredentials(clientId);
-        testDeregisterConfidentialClient(clientId, clientSecret);
+        testDeregisterConfidentialClient(clientId,clientSecret);
     }
 
     @Test
@@ -101,6 +103,7 @@
         json.setType(OAuth2ClientType.PUBLIC);
         json.setUrl("http://test.public.client.com");
         json.setRedirectURI("https://test.public.client.com/redirect");
+        json.setDescription("This is a public test client.");
 
         ClientResponse response = resource().path("oauth2").path("client")
                 .path("register")
@@ -129,6 +132,7 @@
         json.setType(OAuth2ClientType.PUBLIC);
         json.setUrl("http://korap.ids-mannheim.de/native");
         json.setRedirectURI("https://korap.ids-mannheim.de/native/redirect");
+        json.setDescription("This is a native test client.");
 
         ClientResponse response = resource().path("oauth2").path("client")
                 .path("register")
@@ -140,7 +144,7 @@
                 .entity(json).post(ClientResponse.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        
+
         //EM: need to check native
     }
 
@@ -167,8 +171,6 @@
     private void testDeregisterConfidentialClient (String clientId,
             String clientSecret) throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("client_id", clientId);
 
         ClientResponse response = resource().path("oauth2").path("client")
                 .path("deregister").path("confidential")
@@ -178,16 +180,34 @@
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(HttpHeaders.CONTENT_TYPE,
                         ContentType.APPLICATION_FORM_URLENCODED)
-                .entity(form).delete(ClientResponse.class);
+                .delete(ClientResponse.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
     }
 
+    private void testDeregisterConfidentialClientMissingParameters ()
+            throws KustvaktException {
+
+        ClientResponse response = resource().path("oauth2").path("client")
+                .path("deregister").path("confidential")
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .delete(ClientResponse.class);
+
+        String entity = response.getEntity(String.class);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals("Missing parameters: client_secret client_id",
+                node.at("/error_description").asText());
+    }
+
     private void testDeregisterClientIncorrectCredentials (String clientId)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("client_id", clientId);
 
         ClientResponse response = resource().path("oauth2").path("client")
                 .path("deregister").path("confidential")
@@ -197,7 +217,7 @@
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(HttpHeaders.CONTENT_TYPE,
                         ContentType.APPLICATION_FORM_URLENCODED)
-                .entity(form).delete(ClientResponse.class);
+                .delete(ClientResponse.class);
 
         String entity = response.getEntity(String.class);
         assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
@@ -205,7 +225,7 @@
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(OAuthError.TokenResponse.INVALID_CLIENT,
                 node.at("/error").asText());
-        assertEquals("Invalid client credentials.",
+        assertEquals("Invalid client credentials",
                 node.at("/error_description").asText());
 
         checkWWWAuthenticateHeader(response);
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 956e3ed..3214fda 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
@@ -2,6 +2,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.net.URI;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response.Status;
@@ -14,9 +17,7 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
-import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.UniformInterfaceException;
 import com.sun.jersey.core.util.MultivaluedMapImpl;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
@@ -34,24 +35,99 @@
     @Autowired
     private HttpAuthorizationHandler handler;
 
-    private ClientResponse testRequestTokenConfidentialClient (
-            MultivaluedMap<String, String> form)
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
-        return resource().path("oauth2").path("token")
+    private ClientResponse requestAuthorizationConfidentialClient (
+            MultivaluedMap<String, String> form) throws KustvaktException {
+
+        return resource().path("oauth2").path("authorize")
                 .header(Attributes.AUTHORIZATION,
                         handler.createBasicAuthorizationHeaderValue(
-                                "fCBbQkAyYzI4NzUxMg==", "secret"))
+                                "fCBbQkAyYzI4NzUxMg", "secret"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(HttpHeaders.CONTENT_TYPE,
                         ContentType.APPLICATION_FORM_URLENCODED)
                 .entity(form).post(ClientResponse.class);
     }
 
-    private ClientResponse testRequestTokenPublicClient (
-            MultivaluedMap<String, String> form)
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
+    @Test
+    public void testAuthorizeConfidentialClient () throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("response_type", "code");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("username", "dory");
+        form.add("password", "password");
+        ClientResponse response = requestAuthorizationConfidentialClient(form);
+
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+        URI redirectUri = response.getLocation();
+        assertTrue(redirectUri.getQuery().startsWith("code"));
+    }
+
+    @Test
+    public void testAuthorizeInvalidRedirectUri () throws KustvaktException {
+        String redirectUri = "https://different.uri/redirect";
+
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("response_type", "code");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("username", "dory");
+        form.add("password", "password");
+        form.add("redirect_uri", redirectUri);
+        ClientResponse response = requestAuthorizationConfidentialClient(form);
+
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals(redirectUri + " is unknown",
+                node.at("/error_description").asText());
+    }
+
+    @Test
+    public void testAuthorizeMissingRequiredParameters ()
+            throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        // missing code
+        ClientResponse response = requestAuthorizationConfidentialClient(form);
+
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals("Missing response_type parameter value",
+                node.at("/error_description").asText());
+
+        // missing client_id
+        form.add("response_type", "code");
+        response = requestAuthorizationConfidentialClient(form);
+        entity = response.getEntity(String.class);
+        node = JsonUtils.readTree(entity);
+        assertEquals("Missing parameters: client_id",
+                node.at("/error_description").asText());
+    }
+
+    @Test
+    public void testAuthorizeInvalidResponseType () throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("response_type", "string");
+
+        ClientResponse response = requestAuthorizationConfidentialClient(form);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals("Invalid response_type parameter value",
+                node.at("/error_description").asText());
+    }
+
+    private ClientResponse requestToken (MultivaluedMap<String, String> form)
+            throws KustvaktException {
         return resource().path("oauth2").path("token")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(HttpHeaders.CONTENT_TYPE,
@@ -61,12 +137,15 @@
 
     @Test
     public void testRequestTokenPasswordGrantConfidential ()
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
+            throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "password");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_secret", "secret");
+        form.add("username", "dory");
+        form.add("password", "password");
 
-        ClientResponse response = testRequestTokenConfidentialClient(form);
+        ClientResponse response = requestToken(form);
         String entity = response.getEntity(String.class);
 
         JsonNode node = JsonUtils.readTree(entity);
@@ -78,71 +157,80 @@
     }
 
     @Test
-    public void testRequestTokenConfidentialMissingSecret ()
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
+    public void testRequestTokenPasswordGrantMissingClientSecret ()
+            throws KustvaktException {
 
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "password");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg==");
+        form.add("username", "dory");
+        form.add("password", "password");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
 
-        ClientResponse response = testRequestTokenPublicClient(form);
-        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+        ClientResponse response = requestToken(form);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
         String entity = response.getEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(OAuthError.TokenResponse.INVALID_CLIENT,
+        assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
                 node.at("/error").asText());
-    }
-
-    @Test
-    public void testRequestTokenPasswordGrantPublic ()
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("grant_type", "password");
-        form.add("client_id", "iBr3LsTCxOj7D2o0A5m");
-
-        ClientResponse response = testRequestTokenPublicClient(form);
-        String entity = response.getEntity(String.class);
-
-        JsonNode node = JsonUtils.readTree(entity);
-        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());
+        assertEquals("Missing parameters: client_secret",
+                node.at("/error_description").asText());
     }
 
     @Test
     public void testRequestTokenPasswordGrantMissingClientId ()
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
+            throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "password");
+        form.add("username", "dory");
+        form.add("password", "password");
+        form.add("client_secret", "secret");
 
-        ClientResponse response = testRequestTokenPublicClient(form);
+        ClientResponse response = requestToken(form);
         String entity = response.getEntity(String.class);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
                 node.at("/error").asText());
-        assertEquals("client_id is missing",
+        assertEquals("Missing parameters: client_id",
                 node.at("/error_description").asText());
     }
+    
+    @Test
+    public void testRequestTokenPasswordGrantPublic ()
+            throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("grant_type", "password");
+        form.add("username", "dory");
+        form.add("password", "password");
+        form.add("client_id", "iBr3LsTCxOj7D2o0A5m");
+
+        ClientResponse response = requestToken(form);
+        String entity = response.getEntity(String.class);
+
+        JsonNode node = JsonUtils.readTree(entity);
+        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 testRequestTokenPasswordGrantNonNative ()
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
+            throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "password");
-        form.add("client_id", "8bIDtZnH6NvRkW2Fq==");
+        form.add("username", "dory");
+        form.add("password", "password");
+        // confidential nonnative
+        form.add("client_id", "9aHsGW6QflV13ixNpez");
+        form.add("client_secret", "secret");
 
-        ClientResponse response = testRequestTokenPublicClient(form);
+        ClientResponse response = requestToken(form);
         String entity = response.getEntity(String.class);
-        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
 
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT,
@@ -153,13 +241,13 @@
 
     @Test
     public void testRequestTokenClientCredentialsGrant ()
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
+            throws KustvaktException {
 
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "client_credentials");
-
-        ClientResponse response = testRequestTokenConfidentialClient(form);
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_secret", "secret");
+        ClientResponse response = requestToken(form);
         String entity = response.getEntity(String.class);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
@@ -173,11 +261,9 @@
     }
 
     @Test
-    public void testRequestTokenMissingGrantType ()
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
+    public void testRequestTokenMissingGrantType () throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        ClientResponse response = testRequestTokenConfidentialClient(form);
+        ClientResponse response = requestToken(form);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
         String entity = response.getEntity(String.class);
@@ -187,9 +273,7 @@
     }
 
     @Test
-    public void testRequestTokenUnsupportedGrant ()
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
+    public void testRequestTokenUnsupportedGrant () throws KustvaktException {
 
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "blahblah");
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index 0445d65..c0f7a2e 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -167,10 +167,10 @@
 
 	<!-- URLValidator -->
 	<bean id="urlValidator" class="org.apache.commons.validator.routines.UrlValidator">
-		<constructor-arg value="http,https"/>
+		<constructor-arg value="http,https" />
 	</bean>
 	<bean id="httpsValidator" class="org.apache.commons.validator.routines.UrlValidator">
-		<constructor-arg value="https"/>
+		<constructor-arg value="https" />
 	</bean>
 
 	<bean id="kustvakt_rewrite" class="de.ids_mannheim.korap.rewrite.FullRewriteHandler">
@@ -184,10 +184,18 @@
 	<bean id="kustvaktExceptionHandler" class="de.ids_mannheim.korap.web.KustvaktExceptionHandler">
 		<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
 	</bean>
+
+	<!-- OAuth -->
 	<bean id="oauth2ResponseHandler" class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
 		<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
 	</bean>
 
+	<bean id="mdGenerator" class="org.apache.oltu.oauth2.as.issuer.MD5Generator">
+	</bean>
+	<bean id="oauthIssuer" class="org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl">
+		<constructor-arg index="0" ref="mdGenerator" />
+	</bean>
+
 	<bean id="kustvakt_userdb" class="de.ids_mannheim.korap.handlers.EntityDao">
 		<constructor-arg ref="kustvakt_db" />
 	</bean>
@@ -200,8 +208,7 @@
 		<constructor-arg ref="kustvakt_db" />
 	</bean>
 
-	<bean name="kustvakt_encryption"
-		class="de.ids_mannheim.korap.encryption.KustvaktEncryption">
+	<bean name="kustvakt_encryption" class="de.ids_mannheim.korap.encryption.KustvaktEncryption">
 		<constructor-arg ref="kustvakt_config" />
 	</bean>
 
@@ -267,8 +274,8 @@
 	<!-- specify type for constructor argument -->
 	<bean id="kustvakt_authenticationmanager"
 		class="de.ids_mannheim.korap.authentication.KustvaktAuthenticationManager">
-		<constructor-arg
-			type="de.ids_mannheim.korap.interfaces.EntityHandlerIface" ref="kustvakt_userdb" />
+		<constructor-arg type="de.ids_mannheim.korap.interfaces.EntityHandlerIface"
+			ref="kustvakt_userdb" />
 		<constructor-arg type="de.ids_mannheim.korap.interfaces.EncryptionIface"
 			ref="kustvakt_encryption" />
 		<constructor-arg ref="kustvakt_config" />
@@ -309,8 +316,8 @@
 
 	<!-- mail -->
 	<bean id="authenticator" class="de.ids_mannheim.korap.service.MailAuthenticator">
-		<constructor-arg index="0" value="${mail.username}"/>
-		<constructor-arg index="1" value="${mail.password}"/>
+		<constructor-arg index="0" value="${mail.username}" />
+		<constructor-arg index="1" value="${mail.password}" />
 	</bean>
 	<bean id="smtpSession" class="javax.mail.Session" factory-method="getInstance">
 		<constructor-arg index="0">
@@ -323,7 +330,7 @@
 				<prop key="mail.smtp.connectiontimeout">${mail.connectiontimeout}</prop>
 			</props>
 		</constructor-arg>
-		<constructor-arg index="1" ref="authenticator"/>
+		<constructor-arg index="1" ref="authenticator" />
 	</bean>
 	<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
 		<property name="session" ref="smtpSession" />