Updated authorization error response.

Included error and error description in the client redirect URI except
for missing or invalid client id or redirect URI.

Change-Id: Ic35c5dcaf056f6ba761d80246c9ca64a7cf2016a
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
index 9123a5b..6ccbbce 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
@@ -18,9 +18,12 @@
 @Getter
 public class KustvaktException extends Exception {
 
+    private static final long serialVersionUID = -1955783565699446322L;
     protected List<AuditRecord> records = new ArrayList<>();
     private String userid;
     private Integer statusCode;
+    private int responseStatus;
+    
     private String entity;
     private String notification;
     private boolean isNotification;
diff --git a/full/Changes b/full/Changes
index cad48b8..3bf608d 100644
--- a/full/Changes
+++ b/full/Changes
@@ -12,6 +12,11 @@
 2022-04-13
  - Updated OAuth2Client list API (added redirect_uri, registration_date, 
    permitted, source to OAuth2UserClientDto).
+2022-04-20
+ - Updated authorization error response. (Included error and error 
+   description in the client redirect URI except for missing or 
+   invalid client id or redirect URI.
+ 
 
 # version 0.65.2
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2AuthorizationRequest.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2AuthorizationRequest.java
index 503a5c8..1819c11 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2AuthorizationRequest.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2AuthorizationRequest.java
@@ -3,15 +3,30 @@
 import javax.servlet.http.HttpServletRequest;
 
 import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
+import org.apache.oltu.oauth2.as.validator.CodeValidator;
 import org.apache.oltu.oauth2.common.OAuth;
+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.types.ResponseType;
+import org.apache.oltu.oauth2.common.utils.OAuthUtils;
+import org.apache.oltu.oauth2.common.validators.OAuthValidator;
 
 /**
  * Customization of {@link OAuthAuthzRequest} from Apache Oltu.
- * Limit extraction of client id from request's parameters since
+ * <ul>
+ * <li>Limit extraction of client id from request's parameters since
  * Kustvakt requires user authentication via Basic authentication for
- * authorization code requests.
+ * authorization code requests. </li>
+ * 
+ * <li>Exclude TokenValidator since it is not supported in
+ * Kustvakt.</li>
+ * 
+ * <li>Minimize {{@link #validate()} to include missing response type
+ * response in client redirect URI when the client id and redirect URI 
+ * are valid. </li>
+ * 
+ * </ul>
  * 
  * @author margaretha
  *
@@ -27,4 +42,39 @@
     public String getClientId () {
         return getParam(OAuth.OAUTH_CLIENT_ID);
     }
+
+    @Override
+    protected OAuthValidator<HttpServletRequest> initValidator ()
+            throws OAuthProblemException, OAuthSystemException {
+        validators.put(ResponseType.CODE.toString(), CodeValidator.class);
+        // validators.put(ResponseType.TOKEN.toString(),
+        // TokenValidator.class);
+        final String requestTypeValue = getParam(OAuth.OAUTH_RESPONSE_TYPE);
+        if (!requestTypeValue.isEmpty()) {
+            if (requestTypeValue.equals(ResponseType.CODE.toString())) {
+                
+            }
+            else if (requestTypeValue.equals(ResponseType.TOKEN.toString())) {
+                throw OAuthProblemException.error(
+                        OAuthError.CodeResponse.UNSUPPORTED_RESPONSE_TYPE)
+                        .description("response_type token is not supported");
+            }
+            else {
+                throw OAuthUtils.handleOAuthProblemException(
+                        "Invalid response_type parameter value");
+            }
+        }
+        
+        return OAuthUtils.instantiateClass(validators.get("code"));
+    }
+
+    @Override
+    protected void validate ()
+            throws OAuthSystemException, OAuthProblemException {
+        validator = initValidator();
+        validator.validateMethod(request);
+        validator.validateContentType(request);
+        validator.validateRequiredParameters(request);
+        validator.validateClientAuthenticationCredentials(request);
+    }
 }
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 cbd638d..a3b9b01 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
@@ -6,8 +6,10 @@
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.http.HttpStatus;
 import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
 import org.apache.oltu.oauth2.as.response.OAuthASResponse;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
 import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
 import org.apache.oltu.oauth2.common.message.OAuthResponse;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -21,6 +23,7 @@
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
 import de.ids_mannheim.korap.oauth2.service.OAuth2AuthorizationService;
+import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
 
 /**
  * OAuth2 authorization service using Apache Oltu
@@ -33,8 +36,10 @@
 
     @Autowired
     private RandomCodeGenerator codeGenerator;
+    @Autowired
+    private OAuth2ClientService clientService;
 
-    /**
+    /**e.description("Redirect URI is required");
      * Authorization code request does not require client
      * authentication, but only checks if the client id exists.
      * 
@@ -70,7 +75,7 @@
 
         String scope, code;
         try {
-            checkResponseType(authzRequest.getResponseType());
+            //checkResponseType(authzRequest.getResponseType(), redirectURI);
             code = codeGenerator.createRandomCode();
             scope = createAuthorization(username, authzRequest.getClientId(),
                     redirectUriStr, authzRequest.getScopes(), code,
@@ -86,7 +91,8 @@
             oAuthResponse = OAuthASResponse
                     .authorizationResponse(request,
                             Status.FOUND.getStatusCode())
-                    .setCode(code).setScope(scope).location(verifiedRedirectUri)
+                    .setCode(code).setScope(scope)
+                    .location(verifiedRedirectUri)
                     .buildQueryMessage();
         }
         catch (OAuthSystemException e) {
@@ -99,4 +105,85 @@
         }
         return oAuthResponse.getLocationUri();
     }
+
+    public OAuthProblemException checkRedirectUri (OAuthProblemException e,
+            String clientId, String redirectUri) {
+        if (!clientId.isEmpty()) {
+            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)) {
+                    e.description("Invalid redirect URI");
+                }
+                else {
+                    e.setRedirectUri(redirectUri);
+                    e.responseStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
+                }
+            }
+            else if (registeredUri != null && !registeredUri.isEmpty()) {
+                e.setRedirectUri(registeredUri);
+                e.responseStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
+            }
+            else {
+                e.description("Redirect URI is required");
+            }
+        }
+
+        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) {
+            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,
+                        "Redirect URI is required", OAuth2Error.INVALID_REQUEST);
+            }
+        }
+
+        return e;
+    }
 }
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 4a50c8a..69713f8 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
@@ -72,6 +72,7 @@
         return String.join(" ", scopeSet);
     }
 
+    @Deprecated
     protected void checkResponseType (String responseType)
             throws KustvaktException {
         if (responseType == null || responseType.isEmpty()) {
@@ -124,7 +125,7 @@
             }
             else {
                 throw new KustvaktException(StatusCodes.MISSING_REDIRECT_URI,
-                        "redirect_uri is required",
+                        "Redirect URI is required",
                         OAuth2Error.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 0b21a14..3c86fd3 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
@@ -10,6 +10,7 @@
 import javax.ws.rs.core.Response.Status;
 
 import org.apache.http.HttpHeaders;
+import org.apache.http.HttpStatus;
 import org.apache.oltu.oauth2.common.error.OAuthError;
 import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
 import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
@@ -56,20 +57,21 @@
     }
 
     public WebApplicationException throwit (OAuthProblemException e) {
-        return throwit(e, null);
-    }
-
-    public WebApplicationException throwit (OAuthProblemException e,
-            String state) {
         OAuthResponse oAuthResponse = null;
+        String state = e.getState();
         try {
-             OAuthErrorResponseBuilder builder = OAuthResponse.errorResponse(e.getResponseStatus())
-                    .error(e);
-                    
+            OAuthErrorResponseBuilder builder =
+                    OAuthResponse.errorResponse(e.getResponseStatus()).error(e);
              if (state != null && !state.isEmpty()) {
                  builder.setState(state);
              }
-             oAuthResponse = builder.buildJSONMessage();
+             if (e.getRedirectUri()!= null && !e.getRedirectUri().isEmpty()) {
+                 builder.location(e.getRedirectUri());
+                 oAuthResponse = builder.buildQueryMessage();
+             }
+             else {
+                 oAuthResponse = builder.buildJSONMessage();
+             }
         }
         catch (OAuthSystemException e1) {
             throwit(e1, state);
@@ -84,17 +86,20 @@
     }
     
     public WebApplicationException throwit (KustvaktException e, String state) {
-        OAuthResponse oAuthResponse = null;
         String errorCode = e.getEntity();
+        int responseStatus = e.getResponseStatus();
         try {
-            if (errorCode == null){
+            if(responseStatus>0) {
+                return throwit(createOAuthProblemException(e, responseStatus, state));
+            }
+            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)) {
-                oAuthResponse = createOAuthResponse(e,
-                        Status.UNAUTHORIZED.getStatusCode(), state);
+                return throwit(createOAuthProblemException(e,
+                        Status.UNAUTHORIZED.getStatusCode(), state));
             }
             else if (errorCode.equals(OAuth2Error.INVALID_GRANT)
                     || errorCode.equals(OAuth2Error.INVALID_REQUEST)
@@ -102,20 +107,20 @@
                     || errorCode.equals(OAuth2Error.UNSUPPORTED_GRANT_TYPE)
                     || errorCode.equals(OAuth2Error.UNSUPPORTED_RESPONSE_TYPE)
                     || errorCode.equals(OAuth2Error.ACCESS_DENIED)) {
-                oAuthResponse = createOAuthResponse(e,
-                        Status.BAD_REQUEST.getStatusCode(), state);
+                return throwit(createOAuthProblemException(e,
+                        Status.BAD_REQUEST.getStatusCode(), state));
             }
             else if (errorCode.equals(OAuth2Error.INSUFFICIENT_SCOPE)) {
-                oAuthResponse = createOAuthResponse(e,
-                        Status.FORBIDDEN.getStatusCode(), state);
+                return throwit(createOAuthProblemException(e,
+                        Status.FORBIDDEN.getStatusCode(), state));
             }
             else if (errorCode.equals(OAuth2Error.SERVER_ERROR)) {
-                oAuthResponse = createOAuthResponse(e,
-                        Status.INTERNAL_SERVER_ERROR.getStatusCode(), state);
+                return throwit(createOAuthProblemException(e,
+                        Status.INTERNAL_SERVER_ERROR.getStatusCode(), state));
             }
             else if (errorCode.equals(OAuth2Error.TEMPORARILY_UNAVAILABLE)) {
-                oAuthResponse = createOAuthResponse(e,
-                        Status.SERVICE_UNAVAILABLE.getStatusCode(), state);
+                return throwit(createOAuthProblemException(e,
+                        Status.SERVICE_UNAVAILABLE.getStatusCode(), state));
             }
             else {
                 return super.throwit(e);
@@ -124,29 +129,18 @@
         catch (OAuthSystemException e1) {
             return throwit(e1, state);
         }
-
-        Response r = createResponse(oAuthResponse);
-        return new WebApplicationException(r);
     }
 
-    private OAuthResponse createOAuthResponse (KustvaktException e,
-            int statusCode, String state) throws OAuthSystemException {
-        OAuthProblemException oAuthProblemException = OAuthProblemException
-                .error(e.getEntity()).state(state).description(e.getMessage());
-
-        OAuthErrorResponseBuilder responseBuilder = OAuthResponse
-                .errorResponse(statusCode).error(oAuthProblemException);
-        if (state!=null && !state.isEmpty()){
-            responseBuilder.setState(state);
+    private OAuthProblemException createOAuthProblemException (
+            KustvaktException e, int statusCode, String state)
+            throws OAuthSystemException {
+        OAuthProblemException ex = OAuthProblemException.error(e.getEntity())
+                .responseStatus(statusCode).state(state)
+                .description(e.getMessage());
+        if (e.getRedirectUri()!= null) {
+            ex.setRedirectUri(e.getRedirectUri().toString());
         }
-            
-        URI redirectUri = e.getRedirectUri();
-        if (redirectUri != null) {
-            responseBuilder.location(redirectUri.toString());
-            return responseBuilder.buildQueryMessage();
-        }
-
-        return responseBuilder.buildJSONMessage();
+        return ex;
     }
 
     /**
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 e29ad15..8ea0f37 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
@@ -100,7 +100,10 @@
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response requestAuthorizationCode (
             @Context HttpServletRequest request,
-            @Context SecurityContext context, @FormParam("state") String state,
+            @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();
@@ -122,9 +125,12 @@
             throw responseHandler.throwit(e, state);
         }
         catch (OAuthProblemException e) {
-            throw responseHandler.throwit(e, state);
+            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);
         }
     }
@@ -134,6 +140,8 @@
     public Response requestAuthorizationCode (
             @Context HttpServletRequest request,
             @Context SecurityContext context,
+            @QueryParam("client_id") String clientId,
+            @QueryParam("redirect_uri") String redirectUri,
             @QueryParam("state") String state
             ) {
 
@@ -151,13 +159,16 @@
             return responseHandler.sendRedirect(uri);
         }
         catch (OAuthSystemException e) {
-            throw responseHandler.throwit(e, state);
+            throw responseHandler.throwit(e,state);
         }
         catch (OAuthProblemException e) {
-            throw responseHandler.throwit(e, state);
+            e.state(state);
+            e = authorizationService.checkRedirectUri(e,clientId,redirectUri);
+            throw responseHandler.throwit(e);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e, state);
+            e = authorizationService.checkRedirectUri(e, clientId, redirectUri);
+            throw responseHandler.throwit(e,state);
         }
     }
 
diff --git a/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql b/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
index b2d6949..159c713 100644
--- a/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
+++ b/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
@@ -43,13 +43,12 @@
 
   
 INSERT INTO oauth2_client(id,name,secret,type,super,
-  redirect_uri, registered_by, description, url, registration_date, 
+  registered_by, description, url, registration_date, 
   is_permitted) 
 VALUES ("nW5qM63Rb2a7KdT9L","test public client",null,
-  "PUBLIC", 0, 
-  "https://korap.ids-mannheim.de/public/redirect","system", 
-  "This is a test public client.",
-  "http://korap.ids-mannheim.de/public", CURRENT_TIMESTAMP, 1);
+  "PUBLIC", 0, "https://korap.ids-mannheim.de/public/redirect",
+  "system", "http://korap.ids-mannheim.de/public", 
+  CURRENT_TIMESTAMP, 1);
   
 
 INSERT INTO oauth2_access_token(token,user_id,created_date, 
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 b7911b3..a7d108c 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
@@ -15,13 +15,10 @@
 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.util.MultiValueMap;
-import org.springframework.web.util.UriComponentsBuilder;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
 import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.uri.UriComponent;
 import com.sun.jersey.core.util.MultivaluedMapImpl;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
@@ -47,25 +44,114 @@
 
     @Test
     public void testAuthorizeConfidentialClient () throws KustvaktException {
-        ClientResponse response = requestAuthorizationCode("code",
-                confidentialClientId, "", "", state, userAuthHeader);
+        // with registered redirect URI
+        ClientResponse response =
+                requestAuthorizationCode("code", confidentialClientId, "",
+                        "match_info search client_info", state, userAuthHeader);
 
         assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
                 response.getStatus());
         URI redirectUri = response.getLocation();
-        MultiValueMap<String, String> params = UriComponentsBuilder
-                .fromUri(redirectUri).build().getQueryParams();
+        MultivaluedMap<String, String> params =
+                getQueryParamsFromURI(redirectUri);
         assertNotNull(params.getFirst("code"));
         assertEquals("thisIsMyState", params.getFirst("state"));
+        assertEquals("match_info search client_info", params.getFirst("scope"));
     }
 
     @Test
     public void testAuthorizePublicClient () throws KustvaktException {
+        // with registered redirect URI
         String code = requestAuthorizationCode(publicClientId, userAuthHeader);
         assertNotNull(code);
     }
 
     @Test
+    public void testAuthorizePublicClientWithRedirectUri () throws KustvaktException {
+        ClientResponse response =
+                requestAuthorizationCode("code", publicClientId2,
+                        "https://public.com/redirect", "", "", userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+        
+        URI redirectUri = response.getLocation();
+        assertEquals(redirectUri.getScheme(), "https");
+        assertEquals(redirectUri.getHost(), "public.com");
+        assertEquals(redirectUri.getPath(), "/redirect");
+
+        String[] queryParts = redirectUri.getQuery().split("&");
+        assertTrue(queryParts[0].startsWith("code="));
+        assertEquals(queryParts[1], "scope=match_info+search");
+    }
+    
+    @Test
+    public void testAuthorizeWithoutScope () throws KustvaktException {
+        ClientResponse 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("&");
+        assertTrue(queryParts[0].startsWith("code="));
+        assertEquals(queryParts[1], "scope=match_info+search");
+    }
+
+    @Test
+    public void testAuthorizeMissingClientId () throws KustvaktException {
+        ClientResponse response = requestAuthorizationCode("code", "", "", "",
+                "", userAuthHeader);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals("Missing parameters: client_id",
+                node.at("/error_description").asText());
+    }
+
+    @Test
+    public void testAuthorizeMissingRedirectUri () throws KustvaktException {
+        ClientResponse response = requestAuthorizationCode("code",
+                publicClientId2, "", "", state, userAuthHeader);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals("Redirect URI is required",
+                node.at("/error_description").asText());
+        assertEquals("thisIsMyState", node.at("/state").asText());
+    }
+
+    @Test
+    public void testAuthorizeMissingResponseType () throws KustvaktException {
+        ClientResponse response = requestAuthorizationCode("",
+                confidentialClientId, "", "", "", userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        assertEquals("https://third.party.com/confidential/redirect?"
+                + "error_description=Missing+parameters%3A+response_type&"
+                + "error=invalid_request", response.getLocation().toString());
+    }
+
+    @Test
+    public void testAuthorizeInvalidClientId () throws KustvaktException {
+        ClientResponse response = requestAuthorizationCode("code",
+                "unknown-client-id", "", "", "", userAuthHeader);
+//        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+        String entity = response.getEntity(String.class);
+        System.out.println(entity);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals("Unknown client with unknown-client-id.",
+                node.at("/error_description").asText());
+    }
+
+    @Test
     public void testAuthorizeInvalidRedirectUri () throws KustvaktException {
         String redirectUri = "https://different.uri/redirect";
         ClientResponse response = requestAuthorizationCode("code",
@@ -81,57 +167,54 @@
                 node.at("/error_description").asText());
         assertEquals("thisIsMyState", node.at("/state").asText());
     }
-    
-//    @Test
-//    public void testAuthorizeRedirectUriLocalhost () throws KustvaktException {
-//        String redirectUri = "http://localhost:1410/";
-//        ClientResponse response =
-//                requestAuthorizationCode("code", confidentialClientId2,
-//                        redirectUri, null, "myState", userAuthHeader);
-//        System.out.println(response.getStatus());
-//        System.out.println(response.getEntity(String.class));
-//    }
-
-    @Test
-    public void testAuthorizeMissingRequiredParameters ()
-            throws KustvaktException {
-        // missing response_type
-        ClientResponse response = requestAuthorizationCode("",
-                confidentialClientId, "", "", state, userAuthHeader);
-
-        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-
-        String entity = response.getEntity(String.class);
-        JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
-                node.at("/error").asText());
-        assertEquals("Missing response_type parameter value",
-                node.at("/error_description").asText());
-        assertEquals("thisIsMyState", node.at("/state").asText());
-
-        // missing client_id
-        response = requestAuthorizationCode("code","", "", "", "", userAuthHeader);
-        entity = response.getEntity(String.class);
-        node = JsonUtils.readTree(entity);
-        assertEquals("Missing parameters: client_id",
-                node.at("/error_description").asText());
-    }
 
     @Test
     public void testAuthorizeInvalidResponseType () throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("response_type", "string");
-        form.add("state", "thisIsMyState");
-
+        // without redirect URI in the request
         ClientResponse response = requestAuthorizationCode("string",
                 confidentialClientId, "", "", state, userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        assertEquals("https://third.party.com/confidential/redirect?"
+                + "error_description=Invalid+response_type+parameter+"
+                + "value&state=thisIsMyState&" + "error=invalid_request",
+                response.getLocation().toString());
+
+        // with redirect URI, and no registered redirect URI
+        response = requestAuthorizationCode("string", publicClientId2,
+                "https://public.client.com/redirect", "", state,
+                userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        assertEquals("https://public.client.com/redirect?error_description="
+                + "Invalid+response_type+parameter+value&state=thisIsMyState&"
+                + "error=invalid_request", 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());
 
-        String entity = response.getEntity(String.class);
-        JsonNode node = JsonUtils.readTree(entity);
+        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
         assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
                 node.at("/error").asText());
-        assertEquals("Invalid response_type parameter value",
+        assertEquals("Invalid redirect URI",
+                node.at("/error_description").asText());
+        assertEquals("thisIsMyState", 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.getEntity(String.class));
+        assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+        assertEquals("Redirect URI is required",
                 node.at("/error_description").asText());
         assertEquals("thisIsMyState", node.at("/state").asText());
     }
@@ -139,18 +222,30 @@
     @Test
     public void testAuthorizeInvalidScope () throws KustvaktException {
         String scope = "read_address";
-
         ClientResponse response = requestAuthorizationCode("code",
                 confidentialClientId, "", scope, state, userAuthHeader);
-        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
 
-        URI location = response.getLocation();
-        MultiValueMap<String, String> params =
-                UriComponentsBuilder.fromUri(location).build().getQueryParams();
-        assertEquals(OAuth2Error.INVALID_SCOPE, params.getFirst("error"));
-        assertEquals("read_address+is+an+invalid+scope",
-                params.getFirst("error_description"));
-        assertEquals("thisIsMyState", params.getFirst("state"));
+        assertEquals(
+                "https://third.party.com/confidential/redirect?"
+                + "error_description=read_address+is+an+invalid+scope&"
+                + "state=thisIsMyState&error=invalid_scope",
+                response.getLocation().toString());
+    }
+
+    @Test
+    public void testAuthorizeUnsupportedTokenResponseType ()
+            throws KustvaktException {
+        ClientResponse response = requestAuthorizationCode("token",
+                confidentialClientId, "", "", state, userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        assertEquals("https://third.party.com/confidential/redirect?"
+                + "error_description=response_type+token+is+not+"
+                + "supported&state=thisIsMyState&error=unsupported_"
+                + "response_type", response.getLocation().toString());
     }
 
     @Test
@@ -182,9 +277,8 @@
         String scope = "search";
         ClientResponse response = requestAuthorizationCode("code",
                 confidentialClientId, "", scope, state, userAuthHeader);
-        URI redirectUri = response.getLocation();
         MultivaluedMap<String, String> params =
-                UriComponent.decodeQuery(redirectUri, true);
+                getQueryParamsFromURI(response.getLocation());
         String code = params.get("code").get(0);
         String scopes = params.get("scope").get(0);
 
@@ -249,9 +343,8 @@
         ClientResponse response =
                 requestAuthorizationCode("code", confidentialClientId,
                         redirect_uri, scope, state, userAuthHeader);
-        URI redirectUri = response.getLocation();
         MultivaluedMap<String, String> params =
-                UriComponent.decodeQuery(redirectUri, true);
+                getQueryParamsFromURI(response.getLocation());
         String code = params.get("code").get(0);
 
         testRequestTokenAuthorizationInvalidClient(code);
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 bcdcee7..1a043d7 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
@@ -20,6 +20,7 @@
 import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.api.uri.UriComponent;
 import com.sun.jersey.core.util.MultivaluedMapImpl;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
@@ -45,6 +46,8 @@
     protected RefreshTokenDao refreshTokenDao;
     
     protected String publicClientId = "8bIDtZnH6NvRkW2Fq";
+    // without registered redirect URI
+    protected String publicClientId2 = "nW5qM63Rb2a7KdT9L";
     protected String confidentialClientId = "9aHsGW6QflV13ixNpez";
     protected String confidentialClientId2 = "52atrL0ajex_3_5imd9Mgw";
     protected String superClientId = "fCBbQkAyYzI4NzUxMg";
@@ -56,6 +59,10 @@
     
     protected String clientURL = "http://example.client.com";
     protected String clientRedirectUri = "https://example.client.com/redirect";
+    
+    protected MultivaluedMap<String, String> getQueryParamsFromURI (URI uri) {
+        return UriComponent.decodeQuery(uri, true);
+    };
 
     protected ClientResponse requestAuthorizationCode (String responseType,
             String clientId, String redirectUri, String scope, String state,