Implemented OAuth2 request access token with authorization code grant.

Change-Id: Ia3c427316748876db65373b31ea453bb71f9448b
diff --git a/full/Changes b/full/Changes
index 85c1dd7..9e0be4b 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,5 +1,5 @@
 version 0.60.2
-23/04/2018
+25/04/2018
 	- implemented OAuth2 client registration (margaretha)
 	- implemented OAuth2 client authentication (margaretha)
 	- changed virtual corpus search to retrieval (margaretha)
@@ -7,10 +7,14 @@
 	- 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 response handler (margaretha)
 	- implemented OAuth2 request access token with client credentials grant (margaretha)
 	- implemented OAuth2 request access token with resource owner password grant (margaretha)
 	- implemented OAuth2 authorization code request (margaretha)
+	- added OAuth2 error codes (margaretha)
+	- added OAuth2 authorization, scope and access token tables for SQLite (margaretha)
+	- implemented OAuth2 authorization, scope and access token DAO (margaretha)
+	- implemented OAuth2 request access token with authorization code grant (margaretha)
 	
 version 0.60.1
 28/03/2018
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/BasicAuthenticationManager.java b/full/src/main/java/de/ids_mannheim/korap/authentication/spring/BasicAuthenticationManager.java
deleted file mode 100644
index 808cbf4..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/BasicAuthenticationManager.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package de.ids_mannheim.korap.authentication.spring;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-
-import de.ids_mannheim.korap.config.Attributes;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
-import de.ids_mannheim.korap.config.Scopes;
-import de.ids_mannheim.korap.constant.TokenType;
-import de.ids_mannheim.korap.dao.UserDao;
-import de.ids_mannheim.korap.interfaces.EncryptionIface;
-import de.ids_mannheim.korap.security.context.TokenContext;
-import de.ids_mannheim.korap.user.KorAPUser;
-import de.ids_mannheim.korap.user.User;
-import de.ids_mannheim.korap.utils.TimeUtils;
-
-/** * Basic authentication manager is intended to be used with a database. 
- * It is currently only used for testing using a dummy DAO (@see {@link UserDao}) 
- * without passwords.
- * 
- * @author margaretha
- *
- */
-public class BasicAuthenticationManager implements AuthenticationManager{
-
-    @Autowired
-    private KustvaktConfiguration config;
-    @Autowired
-    private EncryptionIface crypto;
-    @Autowired
-    private UserDao dao;
-    
-    @Override
-    public Authentication authenticate (Authentication authentication)
-            throws AuthenticationException {
-
-        String username = (String) authentication.getPrincipal();
-        String password = (String) authentication.getCredentials();
-        
-        TokenContext c = new TokenContext();
-        User user = dao.getAccount(username);
-        if (user instanceof KorAPUser
-                && ((KorAPUser) user).getPassword() != null) {
-            boolean check = crypto.checkHash(password,
-                    ((KorAPUser) user).getPassword());
-
-            if (!check) return null;
-        }
-        
-        c.setUsername(username);
-        c.setExpirationTime(TimeUtils.plusSeconds(this.config.getTokenTTL())
-                .getMillis());
-        c.setTokenType(TokenType.BASIC);
-        // todo: for production mode, set true
-        c.setSecureRequired(false);
-        // EM: is this secure?
-        c.setToken(authentication.toString());
-        c.addContextParameter(Attributes.SCOPES,
-                Scopes.Scope.search.toString());
-        
-        return authentication;
-    }
-
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktAuthenticationException.java b/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktAuthenticationException.java
deleted file mode 100644
index aeb7ff2..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktAuthenticationException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package de.ids_mannheim.korap.authentication.spring;
-
-import org.springframework.security.core.AuthenticationException;
-
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.web.CoreResponseHandler;
-
-public class KustvaktAuthenticationException extends AuthenticationException {
-
-    /**
-     * Auto-generated serian UID
-     */
-    private static final long serialVersionUID = -357703101436703635L;
-    private String notification;
-    
-    public KustvaktAuthenticationException (String msg) {
-        super(msg);
-    }
-
-    public KustvaktAuthenticationException (KustvaktException e) {
-        super(e.getMessage());
-        notification = CoreResponseHandler.buildNotification(e.getStatusCode(),
-                e.getMessage(), e.getEntity());
-    }
-    
-    public String getNotification () {
-        return notification;
-    }
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktBasicAuthenticationEntryPoint.java b/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktBasicAuthenticationEntryPoint.java
deleted file mode 100644
index de044c0..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/KustvaktBasicAuthenticationEntryPoint.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.ids_mannheim.korap.authentication.spring;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
-
-public class KustvaktBasicAuthenticationEntryPoint
-        extends BasicAuthenticationEntryPoint {
-
-    @Override
-    public void commence (HttpServletRequest request,
-            HttpServletResponse response, AuthenticationException authException)
-            throws IOException, ServletException {
-        super.commence(request, response, authException);
-
-        String notification = ((KustvaktAuthenticationException) authException)
-                .getNotification();
-
-        PrintWriter writer = response.getWriter();
-        writer.write(notification);
-        writer.flush();
-        writer.close();
-    }
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/LDAPAuthenticationManager.java b/full/src/main/java/de/ids_mannheim/korap/authentication/spring/LDAPAuthenticationManager.java
deleted file mode 100644
index a094d9c..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/spring/LDAPAuthenticationManager.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package de.ids_mannheim.korap.authentication.spring;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-
-import com.unboundid.ldap.sdk.LDAPException;
-
-import de.ids_mannheim.korap.authentication.LdapAuth3;
-import de.ids_mannheim.korap.config.FullConfiguration;
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
-
-public class LDAPAuthenticationManager implements AuthenticationManager {
-
-    private static Logger jlog =
-            LoggerFactory.getLogger(LDAPAuthenticationManager.class);
-    @Autowired
-    private FullConfiguration config;
-
-    @Override
-    public Authentication authenticate (Authentication authentication)
-            throws AuthenticationException {
-
-        String username = (String) authentication.getPrincipal();
-        String password = (String) authentication.getCredentials();
-
-        // just to make sure that the plain password does not appear anywhere in
-        // the logs!
-
-        System.out.printf("Debug: authenticateIdM: entering for '%s'...\n",
-                username);
-
-        /**
-         * wozu Apache Validatoren für User/Passwort für IdM/LDAP? siehe
-         * validation.properties. Abgeschaltet 21.04.17/FB try {
-         * validator.validateEntry(username, Attributes.USERNAME); } catch
-         * (KustvaktException e) { throw new WrappedException(e,
-         * StatusCodes.LOGIN_FAILED, username); }
-         */
-        if (username == null || username.isEmpty() || password == null
-                || password.isEmpty()) {
-            throw new KustvaktAuthenticationException(
-                    new KustvaktException(StatusCodes.BAD_CREDENTIALS,
-                            "Missing username or password."));
-        }
-
-        // LDAP Access:
-        try {
-            // todo: unknown = ...
-            int ret =
-                    LdapAuth3.login(username, password, config.getLdapConfig());
-            System.out.printf(
-                    "Debug: autenticationIdM: Ldap.login(%s) returns: %d.\n",
-                    username, ret);
-            if (ret != LdapAuth3.LDAP_AUTH_ROK) {
-                jlog.error("LdapAuth3.login(username='{}') returns '{}'='{}'!",
-                        username, ret, LdapAuth3.getErrMessage(ret));
-
-                // mask exception to disable user guessing in possible attacks
-                /*
-                 * by Hanl throw new WrappedException(new
-                 * KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
-                 * StatusCodes.LOGIN_FAILED, username);
-                 */
-                throw new KustvaktAuthenticationException(new KustvaktException(
-                        username, StatusCodes.LDAP_BASE_ERRCODE + ret,
-                        LdapAuth3.getErrMessage(ret), username));
-            }
-        }
-        catch (LDAPException e) {
-
-            jlog.error("Error: username='{}' -> '{}'!", username, e);
-            // mask exception to disable user guessing in possible attacks
-            /*
-             * by Hanl: throw new WrappedException(new
-             * KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
-             * StatusCodes.LOGIN_FAILED, username);
-             */
-            throw new KustvaktAuthenticationException(new KustvaktException(
-                    username,
-                    StatusCodes.LDAP_BASE_ERRCODE + LdapAuth3.LDAP_AUTH_RINTERR,
-                    LdapAuth3.getErrMessage(LdapAuth3.LDAP_AUTH_RINTERR),
-                    username));
-        }
-
-        jlog.debug("Authentication done: " + username);
-        return authentication;
-    }
-
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth/ClientDeregistrationValidator.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/ClientDeregistrationValidator.java
similarity index 95%
rename from full/src/main/java/de/ids_mannheim/korap/oauth/ClientDeregistrationValidator.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/ClientDeregistrationValidator.java
index 60525b0..422b346 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth/ClientDeregistrationValidator.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/ClientDeregistrationValidator.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.oauth;
+package de.ids_mannheim.korap.oauth2;
 
 import javax.servlet.http.HttpServletRequest;
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth/OAuthDeregisterClientRequest.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2DeregisterClientRequest.java
similarity index 83%
rename from full/src/main/java/de/ids_mannheim/korap/oauth/OAuthDeregisterClientRequest.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2DeregisterClientRequest.java
index 88ae668..ecc2bc0 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth/OAuthDeregisterClientRequest.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2DeregisterClientRequest.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.oauth;
+package de.ids_mannheim.korap.oauth2;
 
 import javax.servlet.http.HttpServletRequest;
 
@@ -8,9 +8,9 @@
 import org.apache.oltu.oauth2.common.utils.OAuthUtils;
 import org.apache.oltu.oauth2.common.validators.OAuthValidator;
 
-public class OAuthDeregisterClientRequest extends OAuthRequest {
+public class OAuth2DeregisterClientRequest extends OAuthRequest {
 
-    public OAuthDeregisterClientRequest (HttpServletRequest request)
+    public OAuth2DeregisterClientRequest (HttpServletRequest request)
             throws OAuthSystemException, OAuthProblemException {
         super(request);
     }
diff --git a/full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2ClientType.java
similarity index 93%
rename from full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2ClientType.java
index 3c4c57d..60209be 100644
--- a/full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2ClientType.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.constant;
+package de.ids_mannheim.korap.oauth2.constant;
 
 public enum OAuth2ClientType {
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Error.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Error.java
new file mode 100644
index 0000000..fbdb5a7
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Error.java
@@ -0,0 +1,103 @@
+package de.ids_mannheim.korap.oauth2.constant;
+
+public class OAuth2Error {
+
+    public static final String ERROR = "error";
+    public static final String DESCRIPTION = "error_description";
+    public static final String URI = "error_uri";
+
+
+    /**
+     * The request is missing a required parameter, includes an
+     * invalid parameter value, includes a parameter more than
+     * once, or is otherwise malformed.
+     */
+    public static final String INVALID_REQUEST = "invalid_request";
+
+    /**
+     * Client authentication failed (e.g., unknown client, no
+     * client authentication included, or unsupported
+     * authentication method). The authorization server MAY
+     * return an HTTP 401 (Unauthorized) status code to indicate
+     * which HTTP authentication schemes are supported. If the
+     * client attempted to authenticate via the "Authorization"
+     * request header field, the authorization server MUST
+     * respond with an HTTP 401 (Unauthorized) status code and
+     * include the "WWW-Authenticate" response header field
+     * matching the authentication scheme used by the client.
+     */
+    public static final String INVALID_CLIENT = "invalid_client";
+
+    /**
+     * The provided authorization grant (e.g., authorization
+     * code, resource owner credentials) or refresh token is
+     * invalid, expired, revoked, does not match the redirection
+     * URI used in the authorization request, or was issued to
+     * another client.
+     * 
+     */
+    public static final String INVALID_GRANT = "invalid_grant";
+
+    /**
+     * The client is not authorized to request an authorization
+     * code using this method.
+     * 
+     */
+    public static final String UNAUTHORIZED_CLIENT = "unauthorized_client";
+
+    /**
+     * The authorization grant type is not supported by the
+     * authorization server.
+     */
+    public static final String UNSUPPORTED_GRANT_TYPE =
+            "unsupported_grant_type";
+
+    /**
+     * The requested scope is invalid, unknown, or malformed.
+     * 
+     */
+    public static final String INVALID_SCOPE = "invalid_scope";
+
+    /**
+     * The resource owner or authorization server denied the
+     * request.
+     * 
+     */
+    public static final String ACCESS_DENIED = "access_denied";
+
+    /**
+     * The authorization server does not support obtaining an
+     * authorization code using this method.
+     */
+    public static final String UNSUPPORTED_RESPONSE_TYPE =
+            "unsupported_response_type";
+
+    /**
+     * The authorization server encountered an unexpected
+     * condition that prevented it from fulfilling the request.
+     * (This error code is needed because a 500 Internal Server
+     * Error HTTP status code cannot be returned to the client
+     * via an HTTP redirect.)
+     * 
+     */
+    public static final String SERVER_ERROR = "server_error";
+
+    /**
+     * The authorization server is currently unable to handle
+     * the request due to a temporary overloading or maintenance
+     * of the server. (This error code is needed because a 503
+     * Service Unavailable HTTP status code cannot be returned
+     * to the client via an HTTP redirect.)
+     */
+    public static final String TEMPORARILY_UNAVAILABLE =
+            "temporarily_unavailable";
+
+
+    // extensions
+    
+    public static final String INSUFFICIENT_SCOPE = "insufficient_scope";
+
+    public static final String INVALID_TOKEN = "invalid_token";
+
+    public static final String EXPIRED_TOKEN = "expired_token";
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessScopeDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessScopeDao.java
new file mode 100644
index 0000000..492a529
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessScopeDao.java
@@ -0,0 +1,33 @@
+package de.ids_mannheim.korap.oauth2.dao;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Root;
+
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+
+@Repository
+@Transactional
+public class AccessScopeDao {
+
+    @PersistenceContext
+    private EntityManager entityManager;
+
+    public List<AccessScope> retrieveAccessScopes () {
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<AccessScope> query =
+                builder.createQuery(AccessScope.class);
+        Root<AccessScope> root = query.from(AccessScope.class);
+        query.select(root);
+        Query q = entityManager.createQuery(query);
+        return q.getResultList();
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
new file mode 100644
index 0000000..1e3866f
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
@@ -0,0 +1,25 @@
+package de.ids_mannheim.korap.oauth2.dao;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import de.ids_mannheim.korap.oauth2.entity.AccessToken;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+
+@Repository
+@Transactional
+public class AccessTokenDao {
+
+    @PersistenceContext
+    private EntityManager entityManager;
+
+    public void storeAccessToken (Authorization authorization, String token) {
+        AccessToken accessToken = new AccessToken();
+        accessToken.setAuthorization(authorization);
+        accessToken.setToken(token);
+        entityManager.persist(accessToken);
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
new file mode 100644
index 0000000..0486420
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
@@ -0,0 +1,66 @@
+package de.ids_mannheim.korap.oauth2.dao;
+
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Expression;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.Authorization_;
+import de.ids_mannheim.korap.utils.ParameterChecker;
+
+@Transactional
+@Repository
+public class AuthorizationDao {
+
+    @PersistenceContext
+    private EntityManager entityManager;
+
+    public void storeAuthorizationCode (String clientId, String userId,
+            String code, Set<AccessScope> scopes, String redirectURI) {
+        Authorization authCode = new Authorization();
+        authCode.setCode(code);
+        authCode.setClientId(clientId);
+        authCode.setUserId(userId);
+        authCode.setScopes(scopes);
+        authCode.setRedirectURI(redirectURI);
+
+        entityManager.persist(authCode);
+        // what if unique fails
+    }
+
+    public Authorization retrieveAuthorizationCode (String code,
+            String clientId) throws KustvaktException {
+        ParameterChecker.checkStringValue(code, "code");
+        ParameterChecker.checkStringValue(clientId, "client_id");
+
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<Authorization> query =
+                builder.createQuery(Authorization.class);
+        Root<Authorization> root = query.from(Authorization.class);
+
+        Predicate restrictions = builder.and(
+                builder.equal(root.get(Authorization_.code), code),
+                builder.equal(root.get(Authorization_.clientId), clientId));
+
+        query.select(root);
+        query.where(restrictions);
+        Query q = entityManager.createQuery(query);
+        return (Authorization) q.getSingleResult();
+    }
+
+    public void updateAuthorization (Authorization authorization) {
+        entityManager.merge(authorization);
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
similarity index 93%
rename from full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
index e1759c4..1bad7fa 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.dao;
+package de.ids_mannheim.korap.oauth2.dao;
 
 import javax.persistence.EntityManager;
 import javax.persistence.NoResultException;
@@ -11,11 +11,11 @@
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
-import de.ids_mannheim.korap.constant.OAuth2ClientType;
-import de.ids_mannheim.korap.entity.OAuth2Client;
-import de.ids_mannheim.korap.entity.OAuth2Client_;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client_;
 import de.ids_mannheim.korap.utils.ParameterChecker;
 
 @Transactional
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
new file mode 100644
index 0000000..cc0e41a
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
@@ -0,0 +1,44 @@
+package de.ids_mannheim.korap.oauth2.entity;
+
+import java.util.List;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "oauth2_access_scope")
+public class AccessScope {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private int id;
+    private String name;
+
+    @ManyToMany(mappedBy = "scopes", fetch = FetchType.LAZY)
+    private List<Authorization> authorizationCodes;
+
+    @Override
+    public String toString () {
+        return "id: " + id + ", name: " + name;
+    }
+
+    @Override
+    public boolean equals (Object obj) {
+        String scope = (String) obj;
+        if (scope.equals(this.name)) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
new file mode 100644
index 0000000..5d36723
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
@@ -0,0 +1,39 @@
+package de.ids_mannheim.korap.oauth2.entity;
+
+import java.time.ZonedDateTime;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "oauth2_access_token")
+public class AccessToken {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private int id;
+    private String token;
+    @Column(name = "created_date")
+    private ZonedDateTime createdDate;
+    @Column(name = "is_revoked")
+    private boolean isRevoked;
+    @Column(name = "total_attempts")
+    private int totalAttempts;
+    
+    @OneToOne(fetch=FetchType.EAGER)
+    @JoinColumn(name="authorization_id")
+    private Authorization authorization;
+    
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/Authorization.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/Authorization.java
new file mode 100644
index 0000000..4113f48
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/Authorization.java
@@ -0,0 +1,60 @@
+package de.ids_mannheim.korap.oauth2.entity;
+
+import java.time.ZonedDateTime;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "oauth2_authorization")
+public class Authorization {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private int id;
+    private String code;
+    @Column(name = "client_id")
+    private String clientId;
+    @Column(name = "user_id")
+    private String userId;
+    @Column(name = "redirect_uri")
+    private String redirectURI;
+    @Column(name = "created_date")
+    private ZonedDateTime createdDate;
+    @Column(name = "is_revoked")
+    private boolean isRevoked;
+    @Column(name = "total_attempts")
+    private int totalAttempts;
+
+    @ManyToMany(fetch = FetchType.EAGER)
+    @JoinTable(name = "oauth2_authorization_scope",
+            joinColumns = @JoinColumn(name = "authorization_id",
+                    referencedColumnName = "id"),
+            inverseJoinColumns = @JoinColumn(name = "scope_id",
+                    referencedColumnName = "id"),
+            uniqueConstraints = @UniqueConstraint(
+                    columnNames = { "authorization_id", "scope_id" }))
+    private Set<AccessScope> scopes;
+    
+    @Override
+    public String toString () {
+        return "code: " + code + ", " + "clientId: " + clientId + ", "
+                + "userId: " + userId + ", " + "createdDate: " + createdDate
+                + ", " + "isRevoked: " + isRevoked + ", " + "scopes: " + scopes
+                + ", " + "totalAttempts: " + totalAttempts;
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
similarity index 91%
rename from full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
index 9efab11..17f0cb4 100644
--- a/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.entity;
+package de.ids_mannheim.korap.oauth2.entity;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -7,7 +7,7 @@
 import javax.persistence.Id;
 import javax.persistence.Table;
 
-import de.ids_mannheim.korap.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
 import lombok.Getter;
 import lombok.Setter;
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java
new file mode 100644
index 0000000..bb014e5
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java
@@ -0,0 +1,220 @@
+package de.ids_mannheim.korap.oauth2.service;
+
+import java.time.ZonedDateTime;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+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.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.OAuthResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.AccessScopeDao;
+import de.ids_mannheim.korap.oauth2.dao.AuthorizationDao;
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+
+@Service
+public class OAuth2AuthorizationService {
+
+    private static Logger jlog =
+            LoggerFactory.getLogger(OAuth2AuthorizationService.class);
+
+    public static int MAX_ATTEMPTS = 3;
+    
+    @Autowired
+    private OAuth2ClientService clientService;
+    @Autowired
+    private OAuth2Service auth2Service;
+    @Autowired
+    private OAuthIssuer oauthIssuer;
+
+    @Autowired
+    private AuthorizationDao authorizationDao;
+    @Autowired
+    private AccessScopeDao accessScopeDao;
+
+    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.", OAuth2Error.INVALID_REQUEST);
+        }
+
+        OAuth2Client client = clientService.authenticateClient(
+                authzRequest.getClientId(), authzRequest.getClientSecret());
+
+        String redirectUri = authzRequest.getRedirectURI();
+        boolean hasRedirectUri = hasRedirectUri(redirectUri);
+        redirectUri = verifyRedirectUri(client, hasRedirectUri, redirectUri);
+
+        String username = authzRequest.getParam(Attributes.USERNAME);
+        auth2Service.authenticateUser(username,
+                authzRequest.getParam(Attributes.PASSWORD),
+                authzRequest.getScopes());
+
+        String code = oauthIssuer.authorizationCode();
+        Set<AccessScope> scopes =
+                convertToAccessScope(authzRequest.getScopes());
+
+        authorizationDao.storeAuthorizationCode(authzRequest.getClientId(),
+                username, code, scopes, authzRequest.getRedirectURI());
+
+        return OAuthASResponse
+                .authorizationResponse(request, Status.FOUND.getStatusCode())
+                .setCode(code).location(redirectUri).buildQueryMessage();
+    }
+
+    private Set<AccessScope> convertToAccessScope (Set<String> scopes)
+            throws KustvaktException {
+
+        if (scopes.isEmpty()) {
+            // return default scopes
+            return null;
+        }
+
+        List<AccessScope> definedScopes = accessScopeDao.retrieveAccessScopes();
+        Set<AccessScope> requestedScopes =
+                new HashSet<AccessScope>(scopes.size());
+        int index;
+        for (String scope : scopes) {
+            index = definedScopes.indexOf(scope);
+            if (index == -1) {
+                throw new KustvaktException(StatusCodes.INVALID_SCOPE,
+                        scope + " is invalid.", OAuth2Error.INVALID_SCOPE);
+            }
+            else {
+                requestedScopes.add(definedScopes.get(index));
+            }
+        }
+        return requestedScopes;
+    }
+
+    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",
+                        OAuth2Error.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",
+                        OAuth2Error.INVALID_REQUEST);
+            }
+        }
+
+        return redirectUri;
+    }
+
+
+    public void verifyAuthorization (String code, String clientId,
+            String redirectURI) throws KustvaktException {
+        Authorization authorization =
+                authorizationDao.retrieveAuthorizationCode(code, clientId);
+
+        // EM: can Kustvakt be specific about the invalid request param?
+        if (authorization.isRevoked()) {
+            addTotalAttempts(authorization);
+            throw new KustvaktException(StatusCodes.INVALID_AUTHORIZATION,
+                    "Invalid authorization", OAuth2Error.INVALID_REQUEST);
+        }
+
+        if (isExpired(authorization.getCreatedDate())) {
+            addTotalAttempts(authorization);
+            throw new KustvaktException(StatusCodes.INVALID_AUTHORIZATION,
+                    "Authorization expired", OAuth2Error.INVALID_REQUEST);
+        }
+
+        String authorizedUri = authorization.getRedirectURI();
+        if (authorizedUri != null && !authorizedUri.isEmpty()
+                && !authorizedUri.equals(redirectURI)) {
+            addTotalAttempts(authorization);
+            throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+                    "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
+        }
+        
+        authorization.setRevoked(true);
+        authorizationDao.updateAuthorization(authorization);
+    }
+
+    public void addTotalAttempts (Authorization authorization) {
+        int totalAttempts = authorization.getTotalAttempts() + 1;
+        if (totalAttempts > MAX_ATTEMPTS){
+            authorization.setRevoked(true);
+        }
+        else{
+            authorization.setTotalAttempts(totalAttempts);
+        }
+        authorizationDao.updateAuthorization(authorization);
+    }
+
+    private boolean isExpired (ZonedDateTime createdDate) {
+        jlog.debug("createdDate: " + createdDate);
+        ZonedDateTime expiration = createdDate.plusMinutes(10);
+        ZonedDateTime now = ZonedDateTime.now();
+        jlog.debug("expiration: " + expiration + ", now: " + now);
+
+        if (expiration.isAfter(now)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
similarity index 86%
rename from full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
index 1089c45..30d22a6 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.service;
+package de.ids_mannheim.korap.oauth2.service;
 
 import java.net.MalformedURLException;
 import java.net.URI;
@@ -9,26 +9,28 @@
 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.config.FullConfiguration;
-import de.ids_mannheim.korap.constant.OAuth2ClientType;
 import de.ids_mannheim.korap.dao.AdminDao;
-import de.ids_mannheim.korap.dao.OAuth2ClientDao;
 import de.ids_mannheim.korap.dto.OAuth2ClientDto;
-import de.ids_mannheim.korap.entity.OAuth2Client;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.interfaces.EncryptionIface;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 
-/** According to RFC 6749, an authorization server MUST: 
+/**
+ * According to RFC 6749, an authorization server MUST:
  * <ul>
  * <li>
  * require client authentication for confidential clients or for any
- * client that was issued client credentials (or with other authentication 
+ * client that was issued client credentials (or with other
+ * authentication
  * requirements),
  * </li>
  * 
@@ -62,13 +64,13 @@
         if (!urlValidator.isValid(clientJson.getUrl())) {
             throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
                     clientJson.getUrl() + " is invalid.",
-                    OAuthError.TokenResponse.INVALID_REQUEST);
+                    OAuth2Error.INVALID_REQUEST);
         }
         if (!httpsValidator.isValid(clientJson.getRedirectURI())) {
             throw new KustvaktException(StatusCodes.HTTPS_REQUIRED,
                     clientJson.getRedirectURI()
                             + " is invalid. RedirectURI requires https.",
-                    OAuthError.TokenResponse.INVALID_REQUEST);
+                    OAuth2Error.INVALID_REQUEST);
         }
 
         boolean isNative = isNativeClient(clientJson.getUrl(),
@@ -107,8 +109,7 @@
                 if (cause instanceof SQLException) {
                     throw new KustvaktException(
                             StatusCodes.CLIENT_REGISTRATION_FAILED,
-                            cause.getMessage(),
-                            OAuthError.TokenResponse.INVALID_REQUEST);
+                            cause.getMessage(), OAuth2Error.INVALID_REQUEST);
                 }
                 lastCause = cause;
             }
@@ -128,7 +129,7 @@
         catch (MalformedURLException e) {
             throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
                     "Invalid url :" + e.getMessage(),
-                    OAuthError.TokenResponse.INVALID_REQUEST);
+                    OAuth2Error.INVALID_REQUEST);
         }
         String uriHost = null;
         try {
@@ -137,7 +138,7 @@
         catch (URISyntaxException e) {
             throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
                     "Invalid redirectURI: " + e.getMessage(),
-                    OAuthError.TokenResponse.INVALID_REQUEST);
+                    OAuth2Error.INVALID_REQUEST);
         }
         boolean isNative =
                 urlHost.equals(nativeHost) && uriHost.equals(nativeHost);
@@ -159,7 +160,7 @@
                     "Service is limited to public clients. To deregister "
                             + "confidential clients, use service at path: "
                             + "oauth2/client/deregister/confidential.",
-                    OAuthError.TokenResponse.INVALID_REQUEST);
+                    OAuth2Error.INVALID_REQUEST);
         }
         else if (client.getRegisteredBy().equals(username)) {
             clientDao.deregisterClient(client);
@@ -185,7 +186,8 @@
         if (clientId == null || clientId.isEmpty()) {
             throw new KustvaktException(
                     StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                    "Missing parameters: client id", "invalid_request");
+                    "Missing parameters: client id",
+                    OAuth2Error.INVALID_REQUEST);
         }
 
         OAuth2Client client = clientDao.retrieveClientById(clientId);
@@ -194,7 +196,8 @@
                     || client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
                 throw new KustvaktException(
                         StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                        "Missing parameters: client_secret", "invalid_request");
+                        "Missing parameters: client_secret",
+                        OAuth2Error.INVALID_REQUEST);
             }
             else
                 return client;
@@ -209,8 +212,7 @@
         }
 
         throw new KustvaktException(StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                "Invalid client credentials",
-                OAuthError.TokenResponse.INVALID_CLIENT);
+                "Invalid client credentials", OAuth2Error.INVALID_CLIENT);
     }
 
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2Service.java
similarity index 67%
rename from full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2Service.java
index 9e8092b..07037ce 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2Service.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.service;
+package de.ids_mannheim.korap.oauth2.service;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -9,7 +9,6 @@
 import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
 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.OAuthSystemException;
 import org.apache.oltu.oauth2.common.message.OAuthResponse;
 import org.apache.oltu.oauth2.common.message.types.GrantType;
@@ -19,10 +18,13 @@
 
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.FullConfiguration;
-import de.ids_mannheim.korap.entity.OAuth2Client;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
 
 @Service
 public class OAuth2Service {
@@ -30,38 +32,17 @@
     @Autowired
     private OAuth2ClientService clientService;
     @Autowired
+    private OAuth2AuthorizationService authorizationService;
+    @Autowired
+    private AccessTokenDao tokenDao;
+
+    @Autowired
     private FullConfiguration config;
     @Autowired
     private AuthenticationManagerIface authenticationManager;
     @Autowired
     private OAuthIssuer oauthIssuer;
 
-    /** 
-     * 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>  
-     *  
-     *  
-     * @param request 
-     *  
-     * @param oAuthRequest
-     * @param authorization
-     * @return
-     * @throws KustvaktException
-     * @throws OAuthSystemException
-     */
     public OAuthResponse requestAccessToken (
             AbstractOAuthTokenRequest oAuthRequest)
             throws KustvaktException, OAuthSystemException {
@@ -86,21 +67,25 @@
         else {
             throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
                     grantType + " is not supported.",
-                    OAuthError.TokenResponse.UNSUPPORTED_GRANT_TYPE);
+                    OAuth2Error.UNSUPPORTED_GRANT_TYPE);
         }
 
     }
 
     /**
-     * RFC 6749: 
-     *  If the client type is confidential or the client was issued client
-     *  credentials, the client MUST authenticate with the authorization server.
+     * RFC 6749:
+     * If the client type is confidential or the client was issued
+     * client credentials, the client MUST authenticate with the
+     * authorization server.
      * 
      * @param authorizationCode
-     * @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.
+     * @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
@@ -111,33 +96,37 @@
             throws KustvaktException, OAuthSystemException {
 
         clientService.authenticateClient(clientId, clientSecret);
+        authorizationService.verifyAuthorization(authorizationCode, clientId,
+                redirectURI);
 
-        // TODO
-        // check authorization code
-        // check redirectURI
         return createsAccessTokenResponse();
     }
 
 
-
-    /**  Third party apps must not be allowed to use password grant.
-     * MH: password grant is only allowed for trusted clients (korap frontend)
-     *  
-     * 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. 
+    /**
+     * Third party apps must not be allowed to use password grant.
+     * MH: password grant is only allowed for trusted clients (korap
+     * frontend)
      * 
-     * To make sure that confidential clients authenticate, client_id is made 
-     * required (similar to authorization code grant).
+     * 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. To make sure that confidential clients
+     * authenticate, client_id is made required (similar to
+     * authorization code grant).
      * 
      * 
-     * @param username username, required
-     * @param password user password, required
+     * @param username
+     *            username, required
+     * @param password
+     *            user password, required
      * @param scopes
-     * @param clientId client_id, required
-     * @param clientSecret clilent_secret, required if client_secret was issued 
-     *  for the client in client registration.
+     * @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
@@ -152,7 +141,7 @@
         if (!client.isNative()) {
             throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
                     "Password grant is not allowed for third party clients",
-                    OAuthError.TokenResponse.UNAUTHORIZED_CLIENT);
+                    OAuth2Error.UNAUTHORIZED_CLIENT);
         }
 
         authenticateUser(username, password, scopes);
@@ -163,13 +152,11 @@
             Set<String> scopes) throws KustvaktException {
         if (username == null || username.isEmpty()) {
             throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
-                    "username is missing.",
-                    OAuthError.TokenResponse.INVALID_REQUEST);
+                    "username is missing.", OAuth2Error.INVALID_REQUEST);
         }
         if (password == null || password.isEmpty()) {
             throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
-                    "password is missing",
-                    OAuthError.TokenResponse.INVALID_REQUEST);
+                    "password is missing", OAuth2Error.INVALID_REQUEST);
         }
 
         Map<String, Object> attributes = new HashMap<>();
@@ -181,10 +168,13 @@
                 attributes);
     }
 
-    /** Clients must authenticate
+    /**
+     * Clients must authenticate.
      * 
-     * @param clientId client_id parameter, required
-     * @param clientSecret client_secret parameter, required
+     * @param clientId
+     *            client_id parameter, required
+     * @param clientSecret
+     *            client_secret parameter, required
      * @param scopes
      * @return
      * @throws KustvaktException
@@ -197,7 +187,8 @@
         if (clientSecret == null || clientSecret.isEmpty()) {
             throw new KustvaktException(
                     StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                    "Missing parameters: client_secret", "invalid_request");
+                    "Missing parameters: client_secret",
+                    OAuth2Error.INVALID_REQUEST);
         }
 
         clientService.authenticateClient(clientId, clientSecret);
@@ -205,18 +196,26 @@
     }
 
 
-
-    /** Creates an OAuthResponse containing an access token and a refresh token 
-     *  with type Bearer.
+    /**
+     * 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 {
+        return createsAccessTokenResponse(null);
+    }
+
+    private OAuthResponse createsAccessTokenResponse (
+            Authorization authorization) throws OAuthSystemException {
         String accessToken = oauthIssuer.accessToken();
         String refreshToken = oauthIssuer.refreshToken();
 
+        tokenDao.storeAccessToken(authorization, accessToken);
+
         OAuthResponse r =
                 OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
                         .setAccessToken(accessToken)
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
deleted file mode 100644
index 751c7d2..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2AuthorizationService.java
+++ /dev/null
@@ -1,118 +0,0 @@
-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/web/OAuth2ResponseHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
index 0d31236..f134c79 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
@@ -17,52 +17,19 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.interfaces.db.AuditingIface;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 
-/** OAuth2ResponseHandler builds {@link Response}s from 
- * {@link OAuthResponse}s and handles exceptions by building 
- * OAuth error responses accordingly. 
+/**
+ * OAuth2ResponseHandler builds {@link Response}s from
+ * {@link OAuthResponse}s and handles exceptions by building
+ * OAuth error responses accordingly.
  * 
  * <br/><br/>
  * 
- * OAuth2 error response consists of error (required), 
+ * OAuth2 error response consists of error (required),
  * error_description (optional) and error_uri (optional).
  * 
- * According to RFC 6749, error indicates error code 
- * categorized into:
- * <ul>
- * <li>invalid_request: The request is missing a required parameter, 
- * includes an unsupported parameter value (other than grant type),
- * repeats a parameter, includes multiple credentials, utilizes 
- * more than one mechanism for authenticating the client, or is 
- * otherwise malformed.</li>
- * 
- * <li>invalid_client: Client authentication failed (e.g., unknown 
- * client, no client authentication included, or unsupported 
- * authentication method).  The authorization sever MAY return 
- * an HTTP 401 (Unauthorized) status code to indicate which 
- * HTTP authentication schemes are supported. If the client 
- * attempted to authenticate via the "Authorization" request 
- * header field, the authorization server MUST respond with 
- * an HTTP 401 (Unauthorized) status code and include 
- * the "WWW-Authenticate" response header field matching 
- * the authentication scheme used by the client</li>
- * 
- * <li>invalid_grant: The provided authorization grant 
- * (e.g., authorization code, resource owner credentials) or 
- * refresh token is invalid, expired, revoked, does not match 
- * the redirection URI used in the authorization request, or 
- * was issued to another client.</li>
- * 
- * <li>unauthorized_client:The authenticated client is not 
- * authorized to use this authorization grant type.</li>
- * 
- * <li>unsupported_grant_type: The authorization grant type 
- * is not supported by the authorization server.</li>
- * 
- * <li>invalid_scope: The requested scope is invalid, unknown, 
- * malformed, or exceeds the scope granted by the resource owner.</li>
- * </ul>
- * 
+ * @see OAuth2Error
  * 
  * @author margaretha
  *
@@ -95,21 +62,32 @@
         OAuthResponse oAuthResponse = null;
         String errorCode = e.getEntity();
         try {
-            if (errorCode.equals(OAuthError.TokenResponse.INVALID_CLIENT)
-                    || errorCode.equals(
-                            OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)) {
+            if (errorCode.equals(OAuth2Error.INVALID_CLIENT)
+                    || errorCode.equals(OAuth2Error.UNAUTHORIZED_CLIENT)
+                    || errorCode.equals(OAuth2Error.INVALID_TOKEN)) {
                 oAuthResponse = createOAuthResponse(e,
                         Status.UNAUTHORIZED.getStatusCode());
             }
-            else if (errorCode.equals(OAuthError.TokenResponse.INVALID_GRANT)
-                    || errorCode
-                            .equals(OAuthError.TokenResponse.INVALID_REQUEST)
-                    || errorCode.equals(OAuthError.TokenResponse.INVALID_SCOPE)
-                    || errorCode.equals(
-                            OAuthError.TokenResponse.UNSUPPORTED_GRANT_TYPE)) {
+            else if (errorCode.equals(OAuth2Error.INVALID_GRANT)
+                    || errorCode.equals(OAuth2Error.INVALID_REQUEST)
+                    || errorCode.equals(OAuth2Error.INVALID_SCOPE)
+                    || errorCode.equals(OAuth2Error.UNSUPPORTED_GRANT_TYPE)
+                    || errorCode.equals(OAuth2Error.UNSUPPORTED_RESPONSE_TYPE)
+                    || errorCode.equals(OAuth2Error.ACCESS_DENIED)) {
                 oAuthResponse = createOAuthResponse(e,
                         Status.BAD_REQUEST.getStatusCode());
-
+            }
+            else if (errorCode.equals(OAuth2Error.INSUFFICIENT_SCOPE)) {
+                oAuthResponse = createOAuthResponse(e,
+                        Status.FORBIDDEN.getStatusCode());
+            }
+            else if (errorCode.equals(OAuth2Error.SERVER_ERROR)) {
+                oAuthResponse = createOAuthResponse(e,
+                        Status.INTERNAL_SERVER_ERROR.getStatusCode());
+            }
+            else if (errorCode.equals(OAuth2Error.TEMPORARILY_UNAVAILABLE)) {
+                oAuthResponse = createOAuthResponse(e,
+                        Status.SERVICE_UNAVAILABLE.getStatusCode());
             }
             else {
                 return super.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 b0699b1..ac219bc 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
@@ -24,8 +24,8 @@
 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.oauth2.service.OAuth2AuthorizationService;
+import de.ids_mannheim.korap.oauth2.service.OAuth2Service;
 import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
 import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
 
@@ -40,15 +40,26 @@
     @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. 
+    /**
+     * Requests an authorization code.
      * 
-     * @param request
-     * @param authorization
-     * @param form
-     * @return
+     * 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.
+     * 
+     * <br /><br />
+     * RFC 6749:
+     * If the client omits the scope parameter when requesting
+     * authorization, the authorization server MUST either process the
+     * request using a pre-defined default value or fail the request
+     * indicating an invalid scope.
+     * 
+     * @param request HttpServletRequest
+     * @param authorization authorization header
+     * @param form form parameters
+     * @return a redirect URL
      */
     @POST
     @Path("authorize")
@@ -81,16 +92,50 @@
     }
 
 
-    /** Grants a client an access token, namely a string used in authenticated 
-     *  requests representing user authorization for the client to access user 
-     *  resources. 
+    /**
+     * Grants a client an access token, namely a string used in
+     * authenticated requests representing user authorization for
+     * the client to access user resources.
      * 
-     * @param request the request
-     * @param authorization authorization header
-     * @param form form parameters in a map
-     * @return a JSON object containing an access token, a refresh token, 
-     *  a token type and token expiry/life time (in seconds) if successful, 
-     *  an error code and an error description otherwise.
+     * <br /><br />
+     * 
+     * OAuth2 describes various ways of requesting an access token.
+     * Kustvakt supports:
+     * <ul>
+     * <li> Authorization code grant: obtains authorization from a
+     * third party application. Required parameters: grant_type,
+     * code, client_id, redirect_uri (if specified in the
+     * authorization request), client_secret (if the client is
+     * confidential or issued a secret).
+     * </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. Required parameters: grant_type,
+     * username, password, client_id, client_secret (if the client is
+     * confidential or issued a secret). Optional parameters: scope.
+     * </li>
+     * <li> Client credentials grant: strictly for clients that are
+     * parts of KorAP. Clients access their own resources, not on
+     * behalf of a user. Required parameters: grant_type, client_id,
+     * client_secret. Optional parameters: scope.
+     * </li>
+     * </ul>
+     * 
+     * <br /><br />
+     * RFC 6749: The value of the scope parameter is expressed as a
+     * list of space-delimited, case-sensitive strings defined by the
+     * authorization server.
+     * 
+     * @param request
+     *            the request
+     * @param authorization
+     *            authorization header
+     * @param form
+     *            form parameters in a map
+     * @return a JSON object containing an access token, a refresh
+     *         token, a token type and the token expiration in seconds
+     *         if successful, an error code and an error description
+     *         otherwise.
      */
     @POST
     @Path("token")
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 39b93b7..05c0bd0 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
@@ -23,9 +23,9 @@
 
 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.oauth2.OAuth2DeregisterClientRequest;
+import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
 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;
@@ -122,7 +122,7 @@
             @Context HttpServletRequest request,
             MultivaluedMap<String, String> form) {
         try {
-            OAuthRequest oAuthRequest = new OAuthDeregisterClientRequest(
+            OAuthRequest oAuthRequest = new OAuth2DeregisterClientRequest(
                     new FormRequestWrapper(request, form));
 
             clientService.deregisterConfidentialClient(oAuthRequest);
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 99705e8..cec1806 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
@@ -1,6 +1,6 @@
 package de.ids_mannheim.korap.web.input;
 
-import de.ids_mannheim.korap.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
 import lombok.Getter;
 import lombok.Setter;
 
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 c055f4d..28a5de2 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
@@ -16,3 +16,55 @@
 
 CREATE UNIQUE INDEX client_id_index on oauth2_client(id);
 CREATE UNIQUE INDEX client_url_index on oauth2_client(url_hashcode);
+
+CREATE TABLE IF NOT EXISTS oauth2_authorization (
+	id INTEGER PRIMARY KEY AUTOINCREMENT,
+	code VARCHAR(250) NOT NULL,
+	client_id VARCHAR(100) NOT NULL,
+	user_id VARCHAR(100) NOT NULL,
+	redirect_uri TEXT DEFAULT NULL,
+	created_date timestamp DEFAULT (datetime('now','localtime')),
+	is_revoked BOOLEAN DEFAULT 0,
+	total_attempts INTEGER DEFAULT 0,
+	FOREIGN KEY (client_id)
+	   REFERENCES oauth2_client(id)
+);
+
+CREATE UNIQUE INDEX authorization_index on oauth2_authorization(code, client_id);
+
+CREATE TABLE IF NOT EXISTS oauth2_access_scope (
+	id INTEGER PRIMARY KEY AUTOINCREMENT,
+	name VARCHAR(200) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS oauth2_authorization_scope (
+	id INTEGER PRIMARY KEY AUTOINCREMENT,
+	authorization_id INTEGER NOT NULL,
+	scope_id INTEGER NOT NULL,
+	FOREIGN KEY (authorization_id)
+	   REFERENCES oauth2_authorization(id),
+	FOREIGN KEY (scope_id)
+	   REFERENCES access_scope(id)
+);
+
+CREATE UNIQUE INDEX authorization_scope_index on 
+	oauth2_authorization_scope(authorization_id, scope_id);
+
+CREATE TRIGGER insert_created_date AFTER INSERT ON oauth2_authorization
+     BEGIN
+      UPDATE oauth2_authorization
+      SET created_date = DATETIME('now', 'localtime')  
+      WHERE rowid = new.rowid;
+     END;
+     
+CREATE TABLE IF NOT EXISTS oauth2_access_token (
+	id INTEGER PRIMARY KEY AUTOINCREMENT,
+	token VARCHAR(300) NOT NULL,
+	authorization_id INTEGER DEFAULT NULL,
+	created_date timestamp DEFAULT (datetime('now','localtime')),
+	is_revoked BOOLEAN DEFAULT 0,
+	total_attempts INTEGER DEFAULT 0,
+	FOREIGN KEY (authorization_id)
+	   REFERENCES oauth2_authorization(id)
+);
+
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index 57f65ae..955d197 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -122,7 +122,12 @@
 		<!-- <property name="dataSource" ref="dataSource" /> -->
 		<property name="dataSource" ref="sqliteDataSource" />
 
-		<property name="packagesToScan" value="de.ids_mannheim.korap.entity" />
+		<property name="packagesToScan">
+			<array>
+				<value>de.ids_mannheim.korap.entity</value>
+				<value>de.ids_mannheim.korap.oauth2.entity</value>
+			</array>
+		</property>
 		<property name="jpaVendorAdapter">
 			<bean id="jpaVendorAdapter"
 				class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
@@ -170,7 +175,7 @@
 		<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,12 +189,12 @@
 	<bean id="kustvaktResponseHandler" 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">
@@ -208,8 +213,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>
 
@@ -218,9 +222,6 @@
 		<constructor-arg type="de.ids_mannheim.korap.config.KustvaktConfiguration" 
 		ref="kustvakt_config" /> </bean> -->
 
-	<bean id="client_auth"
-		class="de.ids_mannheim.korap.authentication.OAuth2ClientAuthentication" />
-
 	<bean id="ldap_auth" class="de.ids_mannheim.korap.authentication.LdapAuth3">
 		<constructor-arg type="de.ids_mannheim.korap.config.KustvaktConfiguration"
 			ref="kustvakt_config" />
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 b194e08..c32ccac 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
@@ -11,7 +11,6 @@
 import javax.ws.rs.core.MultivaluedMap;
 
 import org.apache.http.entity.ContentType;
-import org.apache.oltu.oauth2.common.error.OAuthError;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -27,8 +26,9 @@
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
 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.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 
@@ -87,7 +87,7 @@
         response = testRegisterConfidentialClient();
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
         node = JsonUtils.readTree(response.getEntity(String.class));
-        assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+        assertEquals(OAuth2Error.INVALID_REQUEST,
                 node.at("/error").asText());
 
         testDeregisterConfidentialClientMissingParameters();
@@ -199,7 +199,7 @@
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
         JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+        assertEquals(OAuth2Error.INVALID_REQUEST,
                 node.at("/error").asText());
         assertEquals("Missing parameters: client_secret client_id",
                 node.at("/error_description").asText());
@@ -223,7 +223,7 @@
         assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
 
         JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(OAuthError.TokenResponse.INVALID_CLIENT,
+        assertEquals(OAuth2Error.INVALID_CLIENT,
                 node.at("/error").asText());
         assertEquals("Invalid client credentials",
                 node.at("/error_description").asText());
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 3214fda..835284b 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
@@ -11,6 +11,7 @@
 
 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;
@@ -55,6 +56,7 @@
         form.add("client_id", "fCBbQkAyYzI4NzUxMg");
         form.add("username", "dory");
         form.add("password", "password");
+
         ClientResponse response = requestAuthorizationConfidentialClient(form);
 
         assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
@@ -136,6 +138,36 @@
     }
 
     @Test
+    public void testRequestTokenAuthorizationConfidential ()
+            throws KustvaktException {
+        
+        MultivaluedMap<String, String> authForm = new MultivaluedMapImpl();
+        authForm.add("response_type", "code");
+        authForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        authForm.add("username", "dory");
+        authForm.add("password", "password");
+//        form.add("scope", "read");
+        ClientResponse response = requestAuthorizationConfidentialClient(authForm);
+        URI redirectUri = response.getLocation();
+        String code = redirectUri.getQuery().split("=")[1];
+        
+        MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
+        tokenForm.add("grant_type", "authorization_code");
+        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        tokenForm.add("client_secret", "secret");
+        tokenForm.add("code", code);
+        
+        response = requestToken(tokenForm);
+        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 testRequestTokenPasswordGrantConfidential ()
             throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index c0f7a2e..8f0d54f 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -123,7 +123,12 @@
 		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
 		<!-- <property name="dataSource" ref="dataSource" /> -->
 		<property name="dataSource" ref="sqliteDataSource" />
-		<property name="packagesToScan" value="de.ids_mannheim.korap.entity" />
+		<property name="packagesToScan">
+			<array>
+				<value>de.ids_mannheim.korap.entity</value>
+				<value>de.ids_mannheim.korap.oauth2.entity</value>
+			</array>
+		</property>
 		<property name="jpaVendorAdapter">
 			<bean id="jpaVendorAdapter"
 				class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">