Updated authorization services using Nimbus (#650)

Change-Id: Ia188f533230a850c041b624018383e6d7b7e7a99
diff --git a/full/Changes b/full/Changes
index f9b8f74..8518063 100644
--- a/full/Changes
+++ b/full/Changes
@@ -9,6 +9,7 @@
 - Fixed conflicting jackson-jarxrs
 - Fixed conflicting commons-logging and spring jcl
 - Replaced javax.servlet with jakarta.servlet (#648)
+- Updated authorization services using Nimbus (#650)
 
 # version 0.71
 
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 b7027c2..590fb9f 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
@@ -290,7 +290,7 @@
 		String eppn = (String) attributes.get(Attributes.EPPN);
 
 		if (eppn == null || eppn.isEmpty())
-			throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+			throw new KustvaktException(StatusCodes.INVALID_REQUEST);
 
 		if (!attributes.containsKey(Attributes.EMAIL) && validator.isValid(eppn, Attributes.EMAIL))
 			attributes.put(Attributes.EMAIL, eppn);
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java b/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
index 21f3921..63ec65e 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
@@ -179,7 +179,7 @@
         try {
             jwt = SignedJWT.parse(signedContent);
             if (!jwt.verify(verifier))
-                throw new KustvaktException(StatusCodes.REQUEST_INVALID,
+                throw new KustvaktException(StatusCodes.INVALID_REQUEST,
                         "token invalid", signedContent);
             return jwt.getJWTClaimsSet().getStringClaim("data");
         }
diff --git a/full/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java b/full/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
index efe9338..0915bb5 100644
--- a/full/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
+++ b/full/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
@@ -3,6 +3,8 @@
 import java.net.URI;
 import java.util.Arrays;
 
+import com.nimbusds.oauth2.sdk.ErrorObject;
+
 //import de.ids_mannheim.korap.constant.TokenType;
 import lombok.Getter;
 import lombok.Setter;
@@ -25,6 +27,7 @@
     private boolean isNotification;
 //    private TokenType authType;
     private URI redirectUri;
+    private ErrorObject oauth2Error;
 
     public KustvaktException (int status) {
         this.statusCode = status;
@@ -92,6 +95,13 @@
         this.statusCode = status;
     }
 
+    public KustvaktException (int status, String message,
+            ErrorObject oauth2Error) {
+        super(message);
+        this.statusCode = status;
+        this.oauth2Error = oauth2Error;
+        this.entity = oauth2Error.toString();
+    }
 
     public KustvaktException (Throwable cause, int status) {
         super(cause);
diff --git a/full/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/full/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index c1777a4..8f16809 100644
--- a/full/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/full/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -112,7 +112,7 @@
 
     public static final int STATUS_OK = 1000;
     public static final int NOTHING_CHANGED = 1001;
-    public static final int REQUEST_INVALID = 1002;
+    public static final int INVALID_REQUEST = 1002;
     
 //    public static final int ACCESS_DENIED = 1003;
 
@@ -144,12 +144,13 @@
     
     public static final int UNSUPPORTED_GRANT_TYPE = 1811;
     public static final int UNSUPPORTED_AUTHENTICATION_METHOD = 1812;
+    public static final int UNSUPPORTED_RESPONSE_TYPE = 1813;
     
-    public static final int ID_TOKEN_CLAIM_ERROR = 1813;
-    public static final int ID_TOKEN_SIGNING_FAILED = 1814;
-    public static final int USER_REAUTHENTICATION_REQUIRED = 1815;
+    public static final int ID_TOKEN_CLAIM_ERROR = 1820;
+    public static final int ID_TOKEN_SIGNING_FAILED = 1821;
+    public static final int USER_REAUTHENTICATION_REQUIRED = 1822;
     
-    public static final int INVALID_REFRESH_TOKEN_EXPIRY = 1816;
+    public static final int INVALID_REFRESH_TOKEN_EXPIRY = 1830;
     
     /**
      * 1850 Plugins
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuAuthorizationService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuAuthorizationService.java
index a16b78d..684f5b8 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuAuthorizationService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuAuthorizationService.java
@@ -61,16 +61,7 @@
         OAuth2Client client = clientService.authenticateClientId(clientId);
 
         String redirectUriStr = authzRequest.getRedirectURI();
-        String verifiedRedirectUri = verifyRedirectUri(client, redirectUriStr);
-
-        URI redirectURI;
-        try {
-            redirectURI = new URI(verifiedRedirectUri);
-        }
-        catch (URISyntaxException e) {
-            throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
-                    "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
-        }
+        URI redirectURI = verifyRedirectUri(client, redirectUriStr);
 
         String scope, code;
         try {
@@ -91,7 +82,7 @@
                     .authorizationResponse(request,
                             Status.FOUND.getStatusCode())
                     .setCode(code).setScope(scope)
-                    .location(verifiedRedirectUri)
+                    .location(redirectURI.toString())
                     .buildQueryMessage();
         }
         catch (OAuthSystemException e) {
@@ -137,53 +128,4 @@
         return e;
     }
     
-    public KustvaktException checkRedirectUri (KustvaktException e,
-            String clientId, String redirectUri){
-        int statusCode = e.getStatusCode();
-        if (!clientId.isEmpty()
-                && statusCode != StatusCodes.CLIENT_NOT_FOUND
-                && statusCode != StatusCodes.AUTHORIZATION_FAILED
-                && statusCode != StatusCodes.INVALID_REDIRECT_URI) {
-            String registeredUri = null;
-            try {
-                OAuth2Client client = clientService.retrieveClient(clientId);
-                registeredUri = client.getRedirectURI();
-            }
-            catch (KustvaktException e1) {}
-
-            if (redirectUri != null && !redirectUri.isEmpty()) {
-                if (registeredUri != null && !registeredUri.isEmpty()
-                        && !redirectUri.equals(registeredUri)) {
-                    return new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
-                            "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
-                }
-                else {
-                    try {
-                        e.setRedirectUri(new URI(redirectUri));
-                    }
-                    catch (URISyntaxException e1) {
-                        return new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
-                                "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
-                    }
-                    e.setResponseStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
-                }
-            }
-            else if (registeredUri != null && !registeredUri.isEmpty()) {
-                try {
-                    e.setRedirectUri(new URI(registeredUri));
-                }
-                catch (URISyntaxException e1) {
-                    return new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
-                            "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
-                }
-                e.setResponseStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
-            }
-            else {
-                return new KustvaktException(StatusCodes.MISSING_REDIRECT_URI,
-                        "Missing parameter: redirect URI", OAuth2Error.INVALID_REQUEST);
-            }
-        }
-
-        return e;
-    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdAuthorizationService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdAuthorizationService.java
index 65e83aa..d23b146 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdAuthorizationService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdAuthorizationService.java
@@ -100,15 +100,7 @@
 
         String clientId = authzRequest.getClientID().getValue();
         OAuth2Client client = clientService.authenticateClientId(clientId);
-        String verifiedRedirectUri = verifyRedirectUri(client, redirectUriStr);
-
-        try {
-            redirectUri = new URI(verifiedRedirectUri);
-        }
-        catch (URISyntaxException e) {
-            throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
-                    "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
-        }
+        URI verifiedRedirectUri = verifyRedirectUri(client, redirectUriStr);
 
         try {
             ResponseType responseType = authzRequest.getResponseType();
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
index 8da5caa..220a44a 100644
--- 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
@@ -1,20 +1,33 @@
 package de.ids_mannheim.korap.oauth2.service;
 
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.Set;
 
 import org.apache.commons.validator.routines.UrlValidator;
+import org.apache.http.HttpStatus;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.AuthorizationErrorResponse;
+import com.nimbusds.oauth2.sdk.AuthorizationRequest;
+import com.nimbusds.oauth2.sdk.AuthorizationSuccessResponse;
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.OAuth2Error;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.id.State;
+
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.encryption.RandomCodeGenerator;
 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.AuthorizationDao;
 import de.ids_mannheim.korap.oauth2.entity.AccessScope;
 import de.ids_mannheim.korap.oauth2.entity.Authorization;
@@ -34,6 +47,8 @@
     public static boolean DEBUG = false;
     
     @Autowired
+    private RandomCodeGenerator codeGenerator;
+    @Autowired
     protected OAuth2ClientService clientService;
     @Autowired
     protected OAuth2ScopeServiceImpl scopeService;
@@ -44,22 +59,80 @@
 
     @Autowired
     protected FullConfiguration config;
+    
+    public State createAuthorizationState (String state) {
+        State authState = null;
+        if (state!=null && !state.isEmpty())
+            authState = new State(state);
+        return authState;
+    }
+    
+    public AuthorizationErrorResponse createAuthorizationError (
+            KustvaktException e, String state) {
+        State authState = createAuthorizationState(state);
+        ErrorObject error = e.getOauth2Error();
+        error = error.setDescription(e.getMessage());
+        AuthorizationErrorResponse errorResponse =
+                new AuthorizationErrorResponse(e.getRedirectUri(),
+                        error,authState, null);
+        return errorResponse;
+    }
+    
+    public URI requestAuthorizationCode (URI requestURI,
+            String clientId, String redirectUri, String scope,
+            String state, String username,
+            ZonedDateTime authenticationTime) throws KustvaktException {
+        
+        URI redirectURI = null;
+        String code;
+        try {
+            OAuth2Client client = clientService.authenticateClientId(clientId);
+            redirectURI = verifyRedirectUri(client, redirectUri);
+            //checkResponseType(authzRequest.getResponseType(), redirectURI);
+            code = codeGenerator.createRandomCode();
+            createAuthorization(username, clientId, redirectUri, scope, code.toString(),
+                    authenticationTime, null);
+            return createAuthorizationResponse(requestURI, redirectURI, code, state);
+        }
+        catch (KustvaktException e) {
+            e.setRedirectUri(redirectURI);
+            throw e;
+        }
+    }
 
-    /**
-     * Authorization code request does not require client
-     * authentication, but only checks if the client id exists.
-     * 
-     * @param username
-     * @param clientId
-     * @param redirectUri
-     * @param scopeSet
-     * @param code
-     * @param authenticationTime
-     *            user authentication time
-     * @param nonce
-     * @return
-     * @throws KustvaktException
-     */
+    private URI createAuthorizationResponse (URI requestURI, URI redirectURI,
+            String code, String state) throws KustvaktException {
+        AuthorizationRequest authRequest = null;
+        try {
+            authRequest = AuthorizationRequest.parse(requestURI);
+
+            if (authRequest.getResponseType()
+                    .equals(new ResponseType(ResponseType.Value.CODE))) {
+
+                State authState = createAuthorizationState(state);
+                AuthorizationSuccessResponse response =
+                        new AuthorizationSuccessResponse(redirectURI,
+                                new AuthorizationCode(code), null, authState,
+                                null);
+                return response.toURI();
+            }
+            else {
+                KustvaktException ke = new KustvaktException(
+                        StatusCodes.UNSUPPORTED_RESPONSE_TYPE,
+                        "Unsupported response type. Only code is supported.",
+                        OAuth2Error.UNSUPPORTED_RESPONSE_TYPE);
+                throw ke;
+            }
+        }
+        catch (ParseException e) {
+            KustvaktException ke =
+                    new KustvaktException(StatusCodes.INVALID_REQUEST,
+                            e.getMessage(), OAuth2Error.INVALID_REQUEST_URI);
+            throw ke;
+        }
+
+    }
+    @Deprecated
     public String createAuthorization (String username, String clientId,
             String redirectUri, Set<String> scopeSet, String code,
             ZonedDateTime authenticationTime, String nonce)
@@ -75,6 +148,35 @@
                 scopes, redirectUri, authenticationTime, nonce);
         return String.join(" ", scopeSet);
     }
+    
+    /**
+     * Authorization code request does not require client
+     * authentication, but only checks if the client id exists.
+     * 
+     * @param username
+     * @param clientId
+     * @param redirectUri
+     * @param scopeSet
+     * @param code
+     * @param authenticationTime
+     *            user authentication time
+     * @param nonce
+     * @throws KustvaktException
+     */
+    public void createAuthorization (String username, String clientId,
+            String redirectUri, String scope, String code,
+            ZonedDateTime authenticationTime, String nonce)
+            throws KustvaktException {
+
+        if (scope == null || scope.isEmpty()) {
+            throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
+                    "scope is required", OAuth2Error.INVALID_SCOPE);
+        }
+        Set<AccessScope> accessScopes = scopeService.convertToAccessScope(scope);
+
+        authorizationDao.storeAuthorizationCode(clientId, username, code,
+                accessScopes, redirectUri, authenticationTime, nonce);
+    }
 
     @Deprecated
     protected void checkResponseType (String responseType)
@@ -110,7 +212,7 @@
      * @return a client's redirect URI
      * @throws KustvaktException
      */
-    public String verifyRedirectUri (OAuth2Client client, String redirectUri)
+    public URI verifyRedirectUri (OAuth2Client client, String redirectUri)
             throws KustvaktException {
 
         String registeredUri = client.getRedirectURI();
@@ -133,8 +235,66 @@
                     "Missing parameter: redirect URI",
                     OAuth2Error.INVALID_REQUEST);
         }
+        URI redirectURI;
+        try {
+            redirectURI = new URI(redirectUri);
+        }
+        catch (URISyntaxException e) {
+            throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+                    "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
+        }
+        
+        return redirectURI;
+    }
+    
+    public KustvaktException checkRedirectUri (KustvaktException e,
+            String clientId, String redirectUri){
+        int statusCode = e.getStatusCode();
+        if (clientId!=null && !clientId.isEmpty()
+                && statusCode != StatusCodes.CLIENT_NOT_FOUND
+                && statusCode != StatusCodes.AUTHORIZATION_FAILED
+                && statusCode != StatusCodes.INVALID_REDIRECT_URI) {
+            String registeredUri = null;
+            try {
+                OAuth2Client client = clientService.retrieveClient(clientId);
+                registeredUri = client.getRedirectURI();
+            }
+            catch (KustvaktException e1) {}
 
-        return redirectUri;
+            if (redirectUri != null && !redirectUri.isEmpty()) {
+                if (registeredUri != null && !registeredUri.isEmpty()
+                        && !redirectUri.equals(registeredUri)) {
+                    return new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+                            "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
+                }
+                else {
+                    try {
+                        e.setRedirectUri(new URI(redirectUri));
+                    }
+                    catch (URISyntaxException e1) {
+                        return new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+                                "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
+                    }
+                    e.setResponseStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
+                }
+            }
+            else if (registeredUri != null && !registeredUri.isEmpty()) {
+                try {
+                    e.setRedirectUri(new URI(registeredUri));
+                }
+                catch (URISyntaxException e1) {
+                    return new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+                            "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
+                }
+                e.setResponseStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
+            }
+            else {
+                return new KustvaktException(StatusCodes.MISSING_REDIRECT_URI,
+                        "Missing parameter: redirect URI", OAuth2Error.INVALID_REQUEST);
+            }
+        }
+
+        return e;
     }
 
     public Authorization retrieveAuthorization (String code)
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
index ee7aa91..819a839 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
@@ -13,6 +13,8 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import com.nimbusds.oauth2.sdk.OAuth2Error;
+
 import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.dao.AdminDao;
 import de.ids_mannheim.korap.dto.InstalledPluginDto;
@@ -22,7 +24,6 @@
 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.AccessTokenDao;
 import de.ids_mannheim.korap.oauth2.dao.AuthorizationDao;
 import de.ids_mannheim.korap.oauth2.dao.InstalledPluginDao;
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeServiceImpl.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeServiceImpl.java
index 9102ea3..56f0115 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeServiceImpl.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeServiceImpl.java
@@ -10,13 +10,14 @@
 
 import org.springframework.beans.factory.annotation.Autowired;
 
+import com.nimbusds.oauth2.sdk.OAuth2Error;
+
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.constant.TokenType;
 import de.ids_mannheim.korap.dao.AdminDao;
 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.entity.AccessScope;
 import de.ids_mannheim.korap.security.context.TokenContext;
@@ -41,6 +42,7 @@
      * @return
      * @throws KustvaktException
      */
+    @Deprecated
     public Set<AccessScope> convertToAccessScope (Collection<String> scopes)
             throws KustvaktException {
 
@@ -56,14 +58,47 @@
             }
             catch (IllegalArgumentException e) {
                 throw new KustvaktException(StatusCodes.INVALID_SCOPE,
-                        scope + " is an invalid scope",
+                        "Invalid scope",
                         OAuth2Error.INVALID_SCOPE);
             }
 
             index = definedScopes.indexOf(new AccessScope(oauth2Scope));
             if (index == -1) {
                 throw new KustvaktException(StatusCodes.INVALID_SCOPE,
-                        scope + " is an invalid scope",
+                        "Invalid scope",
+                        OAuth2Error.INVALID_SCOPE);
+            }
+            else {
+                requestedScopes.add(definedScopes.get(index));
+            }
+        }
+        return requestedScopes;
+    }
+    
+    public Set<AccessScope> convertToAccessScope (String scopes)
+            throws KustvaktException {
+
+        String[] scopeArray = scopes.split(" ");
+        List<AccessScope> definedScopes = accessScopeDao.retrieveAccessScopes();
+        Set<AccessScope> requestedScopes =
+                new HashSet<AccessScope>(scopeArray.length);
+        int index;
+        OAuth2Scope oauth2Scope = null;
+        for (String scope : scopeArray) {
+            try {
+                oauth2Scope =
+                        Enum.valueOf(OAuth2Scope.class, scope.toUpperCase());
+            }
+            catch (IllegalArgumentException e) {
+                throw new KustvaktException(StatusCodes.INVALID_SCOPE,
+                        "Invalid scope",
+                        OAuth2Error.INVALID_SCOPE);
+            }
+
+            index = definedScopes.indexOf(new AccessScope(oauth2Scope));
+            if (index == -1) {
+                throw new KustvaktException(StatusCodes.INVALID_SCOPE,
+                        "Invalid scope",
                         OAuth2Error.INVALID_SCOPE);
             }
             else {
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/ClientsHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/ClientsHandler.java
index 2f82719..4964913 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/ClientsHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/ClientsHandler.java
@@ -34,7 +34,7 @@
             return service.path(path).queryParam(key, value).request().get(String.class);
         }
         catch (WebApplicationException e) {
-            throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+            throw new KustvaktException(StatusCodes.INVALID_REQUEST);
         }
     }
 
@@ -52,7 +52,7 @@
             return resource.request().get(String.class);
         }
         catch (WebApplicationException e) {
-            throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+            throw new KustvaktException(StatusCodes.INVALID_REQUEST);
         }
     }
 
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 cd9fb9c..077383f 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
@@ -4,15 +4,15 @@
 import java.net.URISyntaxException;
 
 import org.apache.http.HttpHeaders;
-import org.apache.oltu.oauth2.common.error.OAuthError;
 import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
 import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
 import org.apache.oltu.oauth2.common.message.OAuthResponse;
 import org.apache.oltu.oauth2.common.message.OAuthResponse.OAuthErrorResponseBuilder;
 
+import com.nimbusds.oauth2.sdk.OAuth2Error;
+
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
-import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
@@ -80,6 +80,7 @@
     
     public WebApplicationException throwit (KustvaktException e, String state) {
         String errorCode = e.getEntity();
+                    
         int responseStatus = e.getResponseStatus();
         try {
             if(responseStatus>0) {
@@ -88,30 +89,30 @@
             else if (errorCode == null){
                 return super.throwit(e);
             }
-            else if (errorCode.equals(OAuth2Error.INVALID_CLIENT)
-                    || errorCode.equals(OAuth2Error.UNAUTHORIZED_CLIENT)
-                    || errorCode.equals(OAuth2Error.INVALID_TOKEN)) {
+            else if (errorCode.equals(OAuth2Error.INVALID_CLIENT.getCode())
+                    || errorCode.equals(OAuth2Error.UNAUTHORIZED_CLIENT.getCode())
+                    || errorCode.equals(de.ids_mannheim.korap.oauth2.constant.OAuth2Error.INVALID_TOKEN)) {
                 return throwit(createOAuthProblemException(e,
                         Status.UNAUTHORIZED.getStatusCode(), state));
             }
-            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)) {
+            else if (errorCode.equals(OAuth2Error.INVALID_GRANT.getCode())
+                    || errorCode.equals(OAuth2Error.INVALID_REQUEST.getCode())
+                    || errorCode.equals(OAuth2Error.INVALID_SCOPE.getCode())
+                    || errorCode.equals(OAuth2Error.UNSUPPORTED_GRANT_TYPE.getCode())
+                    || errorCode.equals(OAuth2Error.UNSUPPORTED_RESPONSE_TYPE.getCode())
+                    || errorCode.equals(OAuth2Error.ACCESS_DENIED.getCode())) {
                 return throwit(createOAuthProblemException(e,
                         Status.BAD_REQUEST.getStatusCode(), state));
             }
-            else if (errorCode.equals(OAuth2Error.INSUFFICIENT_SCOPE)) {
+            else if (errorCode.equals(de.ids_mannheim.korap.oauth2.constant.OAuth2Error.INSUFFICIENT_SCOPE)) {
                 return throwit(createOAuthProblemException(e,
                         Status.FORBIDDEN.getStatusCode(), state));
             }
-            else if (errorCode.equals(OAuth2Error.SERVER_ERROR)) {
+            else if (errorCode.equals(OAuth2Error.SERVER_ERROR.getCode())) {
                 return throwit(createOAuthProblemException(e,
                         Status.INTERNAL_SERVER_ERROR.getStatusCode(), state));
             }
-            else if (errorCode.equals(OAuth2Error.TEMPORARILY_UNAVAILABLE)) {
+            else if (errorCode.equals(OAuth2Error.TEMPORARILY_UNAVAILABLE.getCode())) {
                 return throwit(createOAuthProblemException(e,
                         Status.SERVICE_UNAVAILABLE.getStatusCode(), state));
             }
@@ -181,15 +182,8 @@
         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);
-        }
+    public Response sendRedirect (URI locationUri) {
+        ResponseBuilder builder = Response.temporaryRedirect(locationUri);
+        return builder.build();
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java
index c380aab..a777131 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java
@@ -366,7 +366,7 @@
                 authorizationData.getPassword()== null || 
                 authorizationData.getPassword().isEmpty())
             // is actual an invalid request
-            throw kustvaktResponseHandler.throwit(StatusCodes.REQUEST_INVALID);
+            throw kustvaktResponseHandler.throwit(StatusCodes.INVALID_REQUEST);
 
         Map<String, Object> attr = new HashMap<>();
         attr.put(Attributes.HOST, host);
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 ecbe9c8..422aae0 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
@@ -1,49 +1,33 @@
 package de.ids_mannheim.korap.web.controller;
 
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.time.ZonedDateTime;
-import java.util.List;
 
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.oltu.oauth2.as.request.AbstractOAuthTokenRequest;
-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 com.nimbusds.oauth2.sdk.AuthorizationErrorResponse;
+import com.nimbusds.oauth2.sdk.OAuth2Error;
+import com.nimbusds.oauth2.sdk.id.State;
+
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 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.dto.OAuth2TokenDto;
-import de.ids_mannheim.korap.oauth2.oltu.OAuth2AuthorizationRequest;
-import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeAllTokenSuperRequest;
-import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenRequest;
-import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenSuperRequest;
-import de.ids_mannheim.korap.oauth2.oltu.service.OltuAuthorizationService;
-import de.ids_mannheim.korap.oauth2.oltu.service.OltuTokenService;
+import de.ids_mannheim.korap.oauth2.service.OAuth2AuthorizationService;
 import de.ids_mannheim.korap.oauth2.service.OAuth2ScopeService;
 import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
 import de.ids_mannheim.korap.web.filter.APIVersionFilter;
 import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
-import de.ids_mannheim.korap.web.filter.BlockingFilter;
-import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
 import de.ids_mannheim.korap.web.utils.ResourceFilters;
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.FormParam;
+import jakarta.servlet.http.HttpServletRequest;
 import jakarta.ws.rs.GET;
-import jakarta.ws.rs.POST;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.Produces;
 import jakarta.ws.rs.QueryParam;
 import jakarta.ws.rs.core.Context;
 import jakarta.ws.rs.core.MediaType;
-import jakarta.ws.rs.core.MultivaluedMap;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.SecurityContext;
 
@@ -60,16 +44,23 @@
  */
 @Controller
 @Path("{version}/oauth2")
-@ResourceFilters({ APIVersionFilter.class, AuthenticationFilter.class, BlockingFilter.class })
+@ResourceFilters({ APIVersionFilter.class, AuthenticationFilter.class
+    //, BlockingFilter.class 
+    })
 @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
 public class OAuth2Controller {
 
     @Autowired
     private OAuth2ResponseHandler responseHandler;
+//    @Autowired
+//    private OltuTokenService tokenService;
+//    @Deprecated
+//    @Autowired
+//    private OltuAuthorizationService authorizationService;
+    
     @Autowired
-    private OltuTokenService tokenService;
-    @Autowired
-    private OltuAuthorizationService authorizationService;
+    private OAuth2AuthorizationService authorizationService;
+    
     @Autowired
     private OAuth2ScopeService scopeService;
 
@@ -94,81 +85,90 @@
      *            form parameters
      * @return a redirect URL
      */
-    @Deprecated
-    @POST
-    @Path("authorize")
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response requestAuthorizationCode (
-            @Context HttpServletRequest request,
-            @Context SecurityContext context, 
-            @FormParam("state") String state,
-            @FormParam("client_id") String clientId,
-            @FormParam("redirect_uri") String redirectUri,
-            MultivaluedMap<String, String> form) {
-
-        TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
-        String username = tokenContext.getUsername();
-        ZonedDateTime authTime = tokenContext.getAuthenticationTime();
-
-        try {
-            scopeService.verifyScope(tokenContext, OAuth2Scope.AUTHORIZE);
-
-            HttpServletRequest requestWithForm =
-                    new FormRequestWrapper(request, form);
-            OAuth2AuthorizationRequest authzRequest =
-                    new OAuth2AuthorizationRequest(requestWithForm);
-            String uri = authorizationService.requestAuthorizationCode(
-                    requestWithForm, authzRequest, username, authTime);
-            return responseHandler.sendRedirect(uri);
-        }
-        catch (OAuthSystemException e) {
-            throw responseHandler.throwit(e, state);
-        }
-        catch (OAuthProblemException e) {
-            e.state(state);
-            e = authorizationService.checkRedirectUri(e, clientId, redirectUri);
-            throw responseHandler.throwit(e);
-        }
-        catch (KustvaktException e) {
-            e = authorizationService.checkRedirectUri(e, clientId, redirectUri);
-            throw responseHandler.throwit(e, state);
-        }
-    }
+//    @Deprecated
+//    @POST
+//    @Path("authorize")
+//    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+//    public Response requestAuthorizationCode (
+//            @Context HttpServletRequest request,
+//            @Context SecurityContext context, 
+//            @FormParam("state") String state,
+//            @FormParam("client_id") String clientId,
+//            @FormParam("redirect_uri") String redirectUri,
+//            MultivaluedMap<String, String> form) {
+//
+//        TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
+//        String username = tokenContext.getUsername();
+//        ZonedDateTime authTime = tokenContext.getAuthenticationTime();
+//
+//        try {
+//            scopeService.verifyScope(tokenContext, OAuth2Scope.AUTHORIZE);
+//
+//            HttpServletRequest requestWithForm =
+//                    new FormRequestWrapper(request, form);
+//            OAuth2AuthorizationRequest authzRequest =
+//                    new OAuth2AuthorizationRequest(requestWithForm);
+//            String uri = authorizationService.requestAuthorizationCode(
+//                    requestWithForm, authzRequest, username, authTime);
+//            return responseHandler.sendRedirect(uri);
+//        }
+//        catch (OAuthSystemException e) {
+//            throw responseHandler.throwit(e, state);
+//        }
+//        catch (OAuthProblemException e) {
+//            e.state(state);
+//            e = authorizationService.checkRedirectUri(e, clientId, redirectUri);
+//            throw responseHandler.throwit(e);
+//        }
+//        catch (KustvaktException e) {
+//            e = authorizationService.checkRedirectUri(e, clientId, redirectUri);
+//            throw responseHandler.throwit(e, state);
+//        }
+//    }
     
     @GET
     @Path("authorize")
     public Response requestAuthorizationCode (
             @Context HttpServletRequest request,
             @Context SecurityContext context,
+            @QueryParam("response_type") String responseType,
             @QueryParam("client_id") String clientId,
             @QueryParam("redirect_uri") String redirectUri,
-            @QueryParam("state") String state
-            ) {
+            @QueryParam("scope") String scope,
+            @QueryParam("state") String state) {
 
         TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
         String username = tokenContext.getUsername();
         ZonedDateTime authTime = tokenContext.getAuthenticationTime();
 
+        URI requestURI;
+        try {
+            requestURI = new URI(request.getRequestURI()+"?"+request.getQueryString());
+        }
+        catch (URISyntaxException e) {
+            KustvaktException ke = new KustvaktException(
+                    StatusCodes.INVALID_REQUEST, "Failed parsing request URI.",
+                    OAuth2Error.INVALID_REQUEST_URI);
+            throw responseHandler.throwit(ke, state);
+        }
+        
         try {
             scopeService.verifyScope(tokenContext, OAuth2Scope.AUTHORIZE);
-
-            OAuth2AuthorizationRequest authzRequest =
-                    new OAuth2AuthorizationRequest(request);
-            String uri = authorizationService.requestAuthorizationCode(
-                    request, authzRequest, username, authTime);
+            URI uri = authorizationService.requestAuthorizationCode(
+                    requestURI, clientId, redirectUri,
+                    scope, state, username, authTime);
             return responseHandler.sendRedirect(uri);
         }
-        catch (OAuthSystemException e) {
-            throw responseHandler.throwit(e,state);
-        }
-        catch (OAuthProblemException e) {
-            e.state(state);
-            e = authorizationService.checkRedirectUri(e,clientId,redirectUri);
-            throw responseHandler.throwit(e);
-        }
         catch (KustvaktException e) {
             e = authorizationService.checkRedirectUri(e, clientId, redirectUri);
-            throw responseHandler.throwit(e,state);
+            if (e.getRedirectUri() != null) {
+                AuthorizationErrorResponse errorResponse =
+                        authorizationService.createAuthorizationError(e, state);
+                return responseHandler.sendRedirect(errorResponse.toURI());
+            }
+            else {
+                throw responseHandler.throwit(e, state);
+            } 
         }
     }
 
@@ -234,42 +234,42 @@
      *         if successful, an error code and an error description
      *         otherwise.
      */
-    @POST
-    @Path("token")
-    @ResourceFilters({APIVersionFilter.class})
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response requestAccessToken (@Context HttpServletRequest request,
-            @FormParam("grant_type") String grantType,
-            MultivaluedMap<String, String> form) {
-
-        try {
-            boolean grantTypeExist = grantType != null && !grantType.isEmpty();
-            AbstractOAuthTokenRequest oAuthRequest = null;
-            if (grantTypeExist && grantType
-                    .equals(GrantType.CLIENT_CREDENTIALS.toString())) {
-                oAuthRequest = new OAuthTokenRequest(
-                        new FormRequestWrapper(request, form));
-            }
-            else {
-                oAuthRequest = new OAuthUnauthenticatedTokenRequest(
-                        new FormRequestWrapper(request, form));
-            }
-
-            OAuthResponse oAuthResponse =
-                    tokenService.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);
-        }
-    }
+//    @POST
+//    @Path("token")
+//    @ResourceFilters({APIVersionFilter.class})
+//    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+//    public Response requestAccessToken (@Context HttpServletRequest request,
+//            @FormParam("grant_type") String grantType,
+//            MultivaluedMap<String, String> form) {
+//
+//        try {
+//            boolean grantTypeExist = grantType != null && !grantType.isEmpty();
+//            AbstractOAuthTokenRequest oAuthRequest = null;
+//            if (grantTypeExist && grantType
+//                    .equals(GrantType.CLIENT_CREDENTIALS.toString())) {
+//                oAuthRequest = new OAuthTokenRequest(
+//                        new FormRequestWrapper(request, form));
+//            }
+//            else {
+//                oAuthRequest = new OAuthUnauthenticatedTokenRequest(
+//                        new FormRequestWrapper(request, form));
+//            }
+//
+//            OAuthResponse oAuthResponse =
+//                    tokenService.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);
+//        }
+//    }
 
     /**
      * Revokes either an access token or a refresh token. Revoking a
@@ -289,59 +289,59 @@
      * @return 200 if token invalidation is successful or the given
      *         token is invalid
      */
-    @POST
-    @Path("revoke")
-    @ResourceFilters({APIVersionFilter.class})
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response revokeAccessToken (@Context HttpServletRequest request,
-            MultivaluedMap<String, String> form) {
-
-        try {
-            OAuth2RevokeTokenRequest revokeTokenRequest =
-                    new OAuth2RevokeTokenRequest(
-                            new FormRequestWrapper(request, form));
-            tokenService.revokeToken(revokeTokenRequest);
-            return Response.ok("SUCCESS").build();
-        }
-        catch (OAuthProblemException e) {
-            throw responseHandler.throwit(e);
-        }
-        catch (OAuthSystemException e) {
-            throw responseHandler.throwit(e);
-        }
-        catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
-        }
-    }
-
-    @POST
-    @Path("revoke/super")
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response revokeTokenViaSuperClient (@Context SecurityContext context,
-            @Context HttpServletRequest request,
-            MultivaluedMap<String, String> form) {
-
-        TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
-        String username = tokenContext.getUsername();
-
-        try {
-            OAuth2RevokeTokenSuperRequest revokeTokenRequest =
-                    new OAuth2RevokeTokenSuperRequest(
-                            new FormRequestWrapper(request, form));
-            tokenService.revokeTokensViaSuperClient(username,
-                    revokeTokenRequest);
-            return Response.ok("SUCCESS").build();
-        }
-        catch (OAuthSystemException e) {
-            throw responseHandler.throwit(e);
-        }
-        catch (OAuthProblemException e) {
-            throw responseHandler.throwit(e);
-        }
-        catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
-        }
-    }
+//    @POST
+//    @Path("revoke")
+//    @ResourceFilters({APIVersionFilter.class})
+//    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+//    public Response revokeAccessToken (@Context HttpServletRequest request,
+//            MultivaluedMap<String, String> form) {
+//
+//        try {
+//            OAuth2RevokeTokenRequest revokeTokenRequest =
+//                    new OAuth2RevokeTokenRequest(
+//                            new FormRequestWrapper(request, form));
+//            tokenService.revokeToken(revokeTokenRequest);
+//            return Response.ok("SUCCESS").build();
+//        }
+//        catch (OAuthProblemException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//        catch (OAuthSystemException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//        catch (KustvaktException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//    }
+//
+//    @POST
+//    @Path("revoke/super")
+//    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+//    public Response revokeTokenViaSuperClient (@Context SecurityContext context,
+//            @Context HttpServletRequest request,
+//            MultivaluedMap<String, String> form) {
+//
+//        TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
+//        String username = tokenContext.getUsername();
+//
+//        try {
+//            OAuth2RevokeTokenSuperRequest revokeTokenRequest =
+//                    new OAuth2RevokeTokenSuperRequest(
+//                            new FormRequestWrapper(request, form));
+//            tokenService.revokeTokensViaSuperClient(username,
+//                    revokeTokenRequest);
+//            return Response.ok("SUCCESS").build();
+//        }
+//        catch (OAuthSystemException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//        catch (OAuthProblemException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//        catch (KustvaktException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//    }
 
     /**
      * Revokes all tokens of a client for the authenticated user from
@@ -357,67 +357,67 @@
      * @return 200 if token invalidation is successful or the given
      *         token is invalid
      */
-    @POST
-    @Path("revoke/super/all")
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response revokeAllClientTokensViaSuperClient (
-            @Context SecurityContext context,
-            @Context HttpServletRequest request,
-            MultivaluedMap<String, String> form) {
-
-        TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
-        String username = tokenContext.getUsername();
-
-        try {
-            OAuth2RevokeAllTokenSuperRequest revokeTokenRequest =
-                    new OAuth2RevokeAllTokenSuperRequest(
-                            new FormRequestWrapper(request, form));
-            tokenService.revokeAllClientTokensViaSuperClient(username,
-                    revokeTokenRequest);
-            return Response.ok("SUCCESS").build();
-        }
-        catch (OAuthSystemException e) {
-            throw responseHandler.throwit(e);
-        }
-        catch (OAuthProblemException e) {
-            throw responseHandler.throwit(e);
-        }
-        catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
-        }
-    }
-
-    @POST
-    @Path("token/list")
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public List<OAuth2TokenDto> listUserToken (
-            @Context SecurityContext context,
-            @FormParam("super_client_id") String superClientId,
-            @FormParam("super_client_secret") String superClientSecret,
-            @FormParam("client_id") String clientId, // optional
-            @FormParam("token_type") String tokenType) {
-
-        TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
-        String username = tokenContext.getUsername();
-
-        try {
-            if (tokenType.equals("access_token")) {
-                return tokenService.listUserAccessToken(username, superClientId,
-                        superClientSecret, clientId);
-            }
-            else if (tokenType.equals("refresh_token")) {
-                return tokenService.listUserRefreshToken(username,
-                        superClientId, superClientSecret, clientId);
-            }
-            else {
-                throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
-                        "Missing token_type parameter value",
-                        OAuth2Error.INVALID_REQUEST);
-            }
-        }
-        catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
-        }
-
-    }
+//    @POST
+//    @Path("revoke/super/all")
+//    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+//    public Response revokeAllClientTokensViaSuperClient (
+//            @Context SecurityContext context,
+//            @Context HttpServletRequest request,
+//            MultivaluedMap<String, String> form) {
+//
+//        TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
+//        String username = tokenContext.getUsername();
+//
+//        try {
+//            OAuth2RevokeAllTokenSuperRequest revokeTokenRequest =
+//                    new OAuth2RevokeAllTokenSuperRequest(
+//                            new FormRequestWrapper(request, form));
+//            tokenService.revokeAllClientTokensViaSuperClient(username,
+//                    revokeTokenRequest);
+//            return Response.ok("SUCCESS").build();
+//        }
+//        catch (OAuthSystemException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//        catch (OAuthProblemException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//        catch (KustvaktException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//    }
+//
+//    @POST
+//    @Path("token/list")
+//    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+//    public List<OAuth2TokenDto> listUserToken (
+//            @Context SecurityContext context,
+//            @FormParam("super_client_id") String superClientId,
+//            @FormParam("super_client_secret") String superClientSecret,
+//            @FormParam("client_id") String clientId, // optional
+//            @FormParam("token_type") String tokenType) {
+//
+//        TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
+//        String username = tokenContext.getUsername();
+//
+//        try {
+//            if (tokenType.equals("access_token")) {
+//                return tokenService.listUserAccessToken(username, superClientId,
+//                        superClientSecret, clientId);
+//            }
+//            else if (tokenType.equals("refresh_token")) {
+//                return tokenService.listUserRefreshToken(username,
+//                        superClientId, superClientSecret, clientId);
+//            }
+//            else {
+//                throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
+//                        "Missing token_type parameter value",
+//                        OAuth2Error.INVALID_REQUEST);
+//            }
+//        }
+//        catch (KustvaktException e) {
+//            throw responseHandler.throwit(e);
+//        }
+//
+//    }
 }
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationTest.java
new file mode 100644
index 0000000..3019784
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationTest.java
@@ -0,0 +1,287 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.net.URI;
+
+import org.apache.oltu.oauth2.common.error.OAuthError;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+public class OAuth2AuthorizationTest extends OAuth2TestBase {
+    
+    private String userAuthHeader;
+
+    public OAuth2AuthorizationTest () throws KustvaktException {
+        userAuthHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue("dory", "password");
+    }
+    
+    @Test
+    public void testAuthorizeConfidentialClient () throws KustvaktException {
+        // with registered redirect URI
+        Response response =
+                requestAuthorizationCode("code", confidentialClientId, "",
+                        "match_info search client_info", state, userAuthHeader);
+
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+        URI redirectUri = response.getLocation();
+        MultivaluedMap<String, String> params =
+                getQueryParamsFromURI(redirectUri);
+        assertNotNull(params.getFirst("code"));
+        assertEquals(state, params.getFirst("state"));
+    }
+
+    @Test
+    public void testAuthorizePublicClient () throws KustvaktException {
+        // with registered redirect URI
+        String code = requestAuthorizationCode(publicClientId, userAuthHeader);
+        assertNotNull(code);
+    }
+
+    @Test
+    public void testAuthorizeWithRedirectUri () throws KustvaktException {
+        Response response =
+                requestAuthorizationCode("code", publicClientId2,
+                        "https://public.com/redirect", "search match_info", 
+                        "", userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        URI redirectUri = response.getLocation();
+        assertEquals("https", redirectUri.getScheme());
+        assertEquals("public.com", redirectUri.getHost());
+        assertEquals("/redirect", redirectUri.getPath());
+
+        assertTrue(redirectUri.getQuery().startsWith("code="));
+    }
+
+    @Test
+    public void testAuthorizeWithoutScope () throws KustvaktException {
+        Response response = requestAuthorizationCode("code",
+                confidentialClientId, "", "", "", userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        URI redirectUri = response.getLocation();
+        assertEquals(redirectUri.getScheme(), "https");
+        assertEquals(redirectUri.getHost(), "third.party.com");
+        assertEquals(redirectUri.getPath(), "/confidential/redirect");
+
+        String[] queryParts = redirectUri.getQuery().split("&");
+        assertEquals("error_description=scope+is+required",
+                queryParts[1]);
+        assertEquals("error=invalid_scope", queryParts[0]);
+    }
+
+    @Test
+    public void testAuthorizeMissingClientId () throws KustvaktException {
+        Response response = requestAuthorizationCode("code", "", "", "search",
+                "", userAuthHeader);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+        String entity = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals("Missing parameter: client_id",
+                node.at("/error_description").asText());
+    }
+
+    @Test
+    public void testAuthorizeMissingRedirectUri () throws KustvaktException {
+        Response response = requestAuthorizationCode("code",
+                publicClientId2, "", "search", state, userAuthHeader);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        String entity = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals("Missing parameter: redirect URI",
+                node.at("/error_description").asText());
+        assertEquals(state, node.at("/state").asText());
+    }
+
+    @Test
+    public void testAuthorizeMissingResponseType() throws KustvaktException {
+        Response response = requestAuthorizationCode("",
+                confidentialClientId, "", "search", "", userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        assertEquals("https://third.party.com/confidential/redirect?"
+                + "error=invalid_request_uri&"
+                + "error_description=Missing+response_type+parameter"
+                , response.getLocation().toString());
+    }
+    
+    @Test
+    public void testAuthorizeMissingResponseTypeWithoutClientId () throws KustvaktException {
+        Response response = requestAuthorizationCode("",
+                "", "", "search", "", userAuthHeader);
+        
+        assertEquals(Status.BAD_REQUEST.getStatusCode(),
+                response.getStatus());
+        String entity = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        
+        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals("Missing parameter: client_id",
+                node.at("/error_description").asText());
+    }
+
+    @Test
+    public void testAuthorizeInvalidClientId () throws KustvaktException {
+        Response response = requestAuthorizationCode("code",
+                "unknown-client-id", "", "search", "", userAuthHeader);
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+        String entity = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_CLIENT, node.at("/error").asText());
+        assertEquals("Unknown client: unknown-client-id",
+                node.at("/error_description").asText());
+    }
+
+    @Test
+    public void testAuthorizeDifferentRedirectUri () throws KustvaktException {
+        String redirectUri = "https://different.uri/redirect";
+        Response response = requestAuthorizationCode("code",
+                confidentialClientId, redirectUri, "", state, userAuthHeader);
+        
+        testInvalidRedirectUri(response.readEntity(String.class), 
+                response.getHeaderString("Content-Type"),true,
+                response.getStatus());
+    }
+
+    @Test
+    public void testAuthorizeWithRedirectUriLocalhost ()
+            throws KustvaktException {
+        Response response = requestAuthorizationCode("code", publicClientId2,
+                "http://localhost:1410", "search", state, userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        URI redirectUri = response.getLocation();
+        MultivaluedMap<String, String> params =
+                getQueryParamsFromURI(redirectUri);
+        assertNotNull(params.getFirst("code"));
+        assertEquals(state, params.getFirst("state"));
+    }
+
+    @Test
+    public void testAuthorizeWithRedirectUriFragment ()
+            throws KustvaktException {
+        Response response = requestAuthorizationCode("code",
+                publicClientId2, "http://public.com/index.html#redirect", "search",
+                state, userAuthHeader);
+        testInvalidRedirectUri(response.readEntity(String.class), 
+                response.getHeaderString("Content-Type"),true,
+                response.getStatus());
+    }
+
+    @Test
+    public void testAuthorizeInvalidRedirectUri () throws KustvaktException {
+        // host not allowed by Apache URI Validator
+        String redirectUri = "https://public.uri/redirect";
+        Response response = requestAuthorizationCode("code",
+                publicClientId2, redirectUri, "", state, userAuthHeader);
+        testInvalidRedirectUri(response.readEntity(String.class), 
+                response.getHeaderString("Content-Type"),true,
+                response.getStatus());
+    }
+
+    @Test
+    public void testAuthorizeInvalidResponseType () throws KustvaktException {
+        // without redirect URI in the request
+        Response response = requestAuthorizationCode("string",
+                confidentialClientId, "", "search", state, userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        assertEquals("https://third.party.com/confidential/redirect?"
+                + "error=unsupported_response_type"
+                + "&error_description=Unsupported+response+type.+Only+code+is+supported."
+                + "&state=thisIsMyState" ,
+                response.getLocation().toString());
+
+        // with redirect URI, and no registered redirect URI
+        response = requestAuthorizationCode("string", publicClientId2,
+                "https://public.client.com/redirect", "search", state,
+                userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        assertEquals("https://public.client.com/redirect?"
+                + "error=unsupported_response_type"
+                + "&error_description=Unsupported+response+type.+Only+code+is+supported."
+                + "&state=thisIsMyState", 
+                response.getLocation().toString());
+
+        // with different redirect URI
+        String redirectUri = "https://different.uri/redirect";
+        response = requestAuthorizationCode("string", confidentialClientId,
+                redirectUri, "", state, userAuthHeader);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals("Invalid redirect URI",
+                node.at("/error_description").asText());
+        assertEquals(state, node.at("/state").asText());
+
+        // without redirect URI in the request and no registered
+        // redirect URI
+        response = requestAuthorizationCode("string", publicClientId2, "", "",
+                state, userAuthHeader);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        node = JsonUtils.readTree(response.readEntity(String.class));
+        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals("Missing parameter: redirect URI",
+                node.at("/error_description").asText());
+        assertEquals(state, node.at("/state").asText());
+    }
+
+    @Test
+    public void testAuthorizeInvalidScope () throws KustvaktException {
+        String scope = "read_address";
+        Response response = requestAuthorizationCode("code",
+                confidentialClientId, "", scope, state, userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        assertEquals(
+                "https://third.party.com/confidential/redirect?"
+                + "error=invalid_scope&error_description=Invalid+"
+                +"scope&state=thisIsMyState",
+                response.getLocation().toString());
+    }
+
+    @Test
+    public void testAuthorizeUnsupportedTokenResponseType ()
+            throws KustvaktException {
+        Response response = requestAuthorizationCode("token",
+                confidentialClientId, "", "search", state, userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        assertEquals("https://third.party.com/confidential/redirect?"
+                + "error=unsupported_response_type"
+                + "&error_description=Unsupported+response+type.+Only+code+is+supported."
+                + "&state=thisIsMyState", response.getLocation().toString());
+    }
+
+}
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
index e34586a..bb5440c 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
@@ -8,7 +8,6 @@
 import java.net.URI;
 
 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.glassfish.jersey.client.ClientConfig;
 import org.glassfish.jersey.client.ClientProperties;
@@ -19,6 +18,7 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
+import com.nimbusds.oauth2.sdk.OAuth2Error;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
@@ -26,7 +26,6 @@
 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.constant.OAuth2Error;
 import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.utils.TimeUtils;
@@ -35,6 +34,7 @@
 import jakarta.ws.rs.client.Client;
 import jakarta.ws.rs.client.ClientBuilder;
 import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.Invocation.Builder;
 import jakarta.ws.rs.client.WebTarget;
 import jakarta.ws.rs.core.Form;
 import jakarta.ws.rs.core.MultivaluedMap;
@@ -119,8 +119,9 @@
             request = request.queryParam("state", state);
         }
         
-        return request.request().header(Attributes.AUTHORIZATION, authHeader)
-                .get();
+        Builder builder = request.request().header(Attributes.AUTHORIZATION, authHeader);
+        
+        return builder.get();
     }
 
     protected String requestAuthorizationCode (String clientId,
@@ -449,7 +450,7 @@
     protected void testInvalidRedirectUri (String entity, String contentType,
             boolean includeState, int status) throws KustvaktException {
         JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+        assertEquals(OAuth2Error.INVALID_REQUEST.getCode(),
                 node.at("/error").asText());
         assertEquals("Invalid redirect URI",
                 node.at("/error_description").asText());