Implemented OpenID token service for authorization code flow.

Change-Id: Ibfeceb73bff1b53020764f8664d587b0e2415129
diff --git a/full/Changes b/full/Changes
index 4c99586..8a401af 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,10 +1,12 @@
 version 0.60.4
-18/06/2018
+19/06/2018
     - implemented OAuth2 authorization code request with OpenID Authentication (margaretha)
     - enabled OAuth2 authorization without OpenID authentication using Nimbus library (margaretha)
     - implemented response handler for OpenID authentication errors in authorization requests (margaretha)
     - added tests regarding OpenID authentication in authorization requests (margaretha)
     - implemented OAuth2 authorization error response via redirect URI instead of JSON (margaretha)
+    - added state to OAuth2 authorization error response (margaretha)
+    - implemented OpenID token service for authorization code flow (margaretha)
     
 version 0.60.3
 06/06/2018
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/ClientDeregistrationValidator.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/ClientDeregistrationValidator.java
similarity index 94%
rename from full/src/main/java/de/ids_mannheim/korap/oauth2/ClientDeregistrationValidator.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/ClientDeregistrationValidator.java
index 422b346..72be70d 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/ClientDeregistrationValidator.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/ClientDeregistrationValidator.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.oauth2;
+package de.ids_mannheim.korap.oauth2.oltu;
 
 import javax.servlet.http.HttpServletRequest;
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2AuthorizationRequest.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2AuthorizationRequest.java
similarity index 95%
rename from full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2AuthorizationRequest.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2AuthorizationRequest.java
index a5d9add..503a5c8 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2AuthorizationRequest.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2AuthorizationRequest.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.oauth2;
+package de.ids_mannheim.korap.oauth2.oltu;
 
 import javax.servlet.http.HttpServletRequest;
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2DeregisterClientRequest.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2DeregisterClientRequest.java
similarity index 95%
rename from full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2DeregisterClientRequest.java
rename to full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2DeregisterClientRequest.java
index ecc2bc0..3da0c02 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/OAuth2DeregisterClientRequest.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2DeregisterClientRequest.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.oauth2;
+package de.ids_mannheim.korap.oauth2.oltu;
 
 import javax.servlet.http.HttpServletRequest;
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
new file mode 100644
index 0000000..0df9a3c
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
@@ -0,0 +1,116 @@
+package de.ids_mannheim.korap.oauth2.oltu.service;
+
+import java.util.Set;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
+import org.apache.oltu.oauth2.as.request.AbstractOAuthTokenRequest;
+import org.apache.oltu.oauth2.as.response.OAuthASResponse;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.OAuthResponse;
+import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.apache.oltu.oauth2.common.message.types.TokenType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+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.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
+
+@Service
+public class OltuTokenService extends OAuth2TokenService {
+
+    @Autowired
+    private OAuthIssuer oauthIssuer;
+
+    @Autowired
+    private AccessTokenDao tokenDao;
+
+    public OAuthResponse requestAccessToken (
+            AbstractOAuthTokenRequest oAuthRequest)
+            throws KustvaktException, OAuthSystemException {
+
+        String grantType = oAuthRequest.getGrantType();
+
+        if (grantType.equals(GrantType.AUTHORIZATION_CODE.toString())) {
+            Authorization authorization =
+                    requestAccessTokenWithAuthorizationCode(
+                            oAuthRequest.getCode(),
+                            oAuthRequest.getRedirectURI(),
+                            oAuthRequest.getClientId(),
+                            oAuthRequest.getClientSecret());
+            return createsAccessTokenResponse(authorization);
+        }
+        else if (grantType.equals(GrantType.PASSWORD.toString())) {
+            requestAccessTokenWithPassword(oAuthRequest.getUsername(),
+                    oAuthRequest.getPassword(), oAuthRequest.getScopes(),
+                    oAuthRequest.getClientId(), oAuthRequest.getClientSecret());
+            return createsAccessTokenResponse(oAuthRequest.getScopes(),
+                    oAuthRequest.getUsername());
+        }
+        else if (grantType.equals(GrantType.CLIENT_CREDENTIALS.toString())) {
+             Set<String> scopes = requestAccessTokenWithClientCredentials(
+                    oAuthRequest.getClientId(), oAuthRequest.getClientSecret(),
+                    oAuthRequest.getScopes());
+            return createsAccessTokenResponse(scopes, null);
+        }
+        else {
+            throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
+                    grantType + " is not supported.",
+                    OAuth2Error.UNSUPPORTED_GRANT_TYPE);
+        }
+
+    }
+
+    /**
+     * Creates an OAuthResponse containing an access token and a
+     * refresh token with type Bearer.
+     * 
+     * @return an OAuthResponse containing an access token
+     * @throws OAuthSystemException
+     * @throws KustvaktException
+     */
+    private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
+            String userId) throws OAuthSystemException, KustvaktException {
+
+        String accessToken = oauthIssuer.accessToken();
+        // String refreshToken = oauthIssuer.refreshToken();
+
+        Set<AccessScope> accessScopes =
+                scopeService.convertToAccessScope(scopes);
+        tokenDao.storeAccessToken(accessToken, accessScopes, userId);
+
+        return OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
+                .setAccessToken(accessToken)
+                .setTokenType(TokenType.BEARER.toString())
+                .setExpiresIn(String.valueOf(config.getTokenTTL()))
+                // .setRefreshToken(refreshToken)
+                .setScope(String.join(" ", scopes)).buildJSONMessage();
+    }
+
+    private OAuthResponse createsAccessTokenResponse (
+            Authorization authorization)
+            throws OAuthSystemException, KustvaktException {
+        String accessToken = oauthIssuer.accessToken();
+        // String refreshToken = oauthIssuer.refreshToken();
+
+        tokenDao.storeAccessToken(authorization, accessToken);
+
+        String scopes = scopeService
+                .convertAccessScopesToString(authorization.getScopes());
+
+        OAuthResponse r =
+                OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
+                        .setAccessToken(accessToken)
+                        .setTokenType(TokenType.BEARER.toString())
+                        .setExpiresIn(String.valueOf(config.getTokenTTL()))
+                        // .setRefreshToken(refreshToken)
+                        .setScope(scopes).buildJSONMessage();
+        return r;
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/OpenIdHttpRequestWrapper.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/OpenIdHttpRequestWrapper.java
new file mode 100644
index 0000000..8f6d80b
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/OpenIdHttpRequestWrapper.java
@@ -0,0 +1,38 @@
+package de.ids_mannheim.korap.oauth2.openid;
+
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+
+public class OpenIdHttpRequestWrapper extends HTTPRequest {
+
+    private Map<String, String> params;
+
+    public OpenIdHttpRequestWrapper (Method method, URL url) {
+        super(method, url);
+    }
+
+    @Override
+    public Map<String, String> getQueryParameters () {
+        return this.params;
+    }
+
+    public void toHttpRequest (HttpServletRequest servletRequest,
+            Map<String, String> map) throws ParseException {
+
+        this.params = map;
+        this.setClientIPAddress(servletRequest.getRemoteAddr());
+        this.setContentType(servletRequest.getContentType());
+
+        Enumeration<String> headerNames = servletRequest.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+            String name = headerNames.nextElement().toString();
+            this.setHeader(name, servletRequest.getHeader(name));
+        }
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
new file mode 100644
index 0000000..8ce9b16
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
@@ -0,0 +1,125 @@
+package de.ids_mannheim.korap.oauth2.openid.service;
+
+import java.net.URI;
+import java.util.Set;
+
+import org.springframework.stereotype.Service;
+
+import com.nimbusds.oauth2.sdk.AccessTokenResponse;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
+import com.nimbusds.oauth2.sdk.token.AccessToken;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.token.RefreshToken;
+import com.nimbusds.oauth2.sdk.token.Tokens;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
+
+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.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
+
+@Service
+public class OpenIdTokenService extends OAuth2TokenService {
+
+    public AccessTokenResponse requestAccessToken (TokenRequest tokenRequest)
+            throws KustvaktException {
+        AuthorizationGrant grant = tokenRequest.getAuthorizationGrant();
+        GrantType grantType = grant.getType();
+        ClientAuthentication clientAuthentication =
+                tokenRequest.getClientAuthentication();
+        String[] clientCredentials =
+                extractClientCredentials(clientAuthentication);
+
+        if (grantType.equals(GrantType.AUTHORIZATION_CODE)) {
+            AuthorizationCodeGrant codeGrant = (AuthorizationCodeGrant) grant;
+            String authorizationCode =
+                    codeGrant.getAuthorizationCode().getValue();
+            URI redirectionURI = codeGrant.getRedirectionURI();
+            String redirectURI = null;
+            if (redirectionURI != null) {
+                redirectURI = redirectionURI.toString();
+            }
+            Authorization authorization =
+                    requestAccessTokenWithAuthorizationCode(authorizationCode,
+                            redirectURI, clientCredentials[0],
+                            clientCredentials[1]);
+            return createsAccessTokenResponse(authorization);
+        }
+        else if (grantType.equals(GrantType.PASSWORD)) {
+
+        }
+        else if (grantType.equals(GrantType.CLIENT_CREDENTIALS)) {
+
+        }
+        else {
+            throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
+                    grantType + " is not supported.",
+                    OAuth2Error.UNSUPPORTED_GRANT_TYPE);
+        }
+        return null;
+    }
+
+
+    private AccessTokenResponse createsAccessTokenResponse (
+            Authorization authorization) {
+        Set<AccessScope> scopes = authorization.getScopes();
+        String[] scopeArray = scopes.stream().map(scope -> scope.toString())
+                .toArray(String[]::new);
+        Scope scope = new Scope(scopeArray);
+        AccessToken accessToken =
+                new BearerAccessToken(config.getTokenTTL(), scope);
+        RefreshToken refreshToken = new RefreshToken();
+
+        if (scope.contains("openid")) {
+            // id token should be encrypted according to keys and
+            // algorithms the client specified during registration
+            String idToken = "thisIsIdToken";
+            OIDCTokens tokens =
+                    new OIDCTokens(idToken, accessToken, refreshToken);
+            return new OIDCTokenResponse(tokens);
+        }
+        else {
+            Tokens tokens = new Tokens(accessToken, refreshToken);
+            return new AccessTokenResponse(tokens);
+        }
+    }
+
+
+    private String[] extractClientCredentials (
+            ClientAuthentication clientAuthentication)
+            throws KustvaktException {
+
+        ClientAuthenticationMethod method = clientAuthentication.getMethod();
+        String clientSecret;
+        String clientId;
+        if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) {
+            ClientSecretBasic basic = (ClientSecretBasic) clientAuthentication;
+            clientSecret = basic.getClientSecret().getValue();
+            clientId = basic.getClientID().getValue();
+        }
+        else if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) {
+            ClientSecretPost post = (ClientSecretPost) clientAuthentication;
+            clientSecret = post.getClientSecret().getValue();
+            clientId = post.getClientID().getValue();
+        }
+        else {
+            // client authentication method is not supported
+            throw new KustvaktException(
+                    StatusCodes.UNSUPPORTED_AUTHENTICATION_METHOD,
+                    method.getValue() + " is not supported.",
+                    OAuth2Error.INVALID_CLIENT);
+        }
+        return new String[] { clientId, clientSecret };
+    }
+}
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 6bb584f..b103e33 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
@@ -152,8 +152,12 @@
         }
 
         String authorizedUri = authorization.getRedirectURI();
-        if (authorizedUri != null && !authorizedUri.isEmpty()
-                && !authorizedUri.equals(redirectURI)) {
+        if (authorizedUri != null && !authorizedUri.isEmpty()) {
+            if (!authorizedUri.equals(redirectURI))
+                throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+                        "Invalid redirect URI", OAuth2Error.INVALID_GRANT);
+        }
+        else if (redirectURI != null && !redirectURI.isEmpty()) {
             throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
                     "Invalid redirect URI", OAuth2Error.INVALID_GRANT);
         }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeService.java
index 15b51e3..f47fa3c 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeService.java
@@ -50,9 +50,14 @@
     }
 
     public String convertAccessScopesToString (Set<AccessScope> scopes) {
+        Set<String> set = convertAccessScopesToStringSet(scopes);
+        return String.join(" ", set);
+    }
+    
+    public Set<String> convertAccessScopesToStringSet (Set<AccessScope> scopes) {
         Set<String> set = scopes.stream().map(scope -> scope.toString())
                 .collect(Collectors.toSet());
-        return String.join(" ", set);
+        return set;
     }
 
     /**
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java
index e6609d4..c64274d 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java
@@ -4,15 +4,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import javax.ws.rs.core.Response.Status;
-
-import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
-import org.apache.oltu.oauth2.as.request.AbstractOAuthTokenRequest;
-import org.apache.oltu.oauth2.as.response.OAuthASResponse;
-import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
-import org.apache.oltu.oauth2.common.message.OAuthResponse;
-import org.apache.oltu.oauth2.common.message.types.GrantType;
-import org.apache.oltu.oauth2.common.message.types.TokenType;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -22,8 +13,6 @@
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
-import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
-import de.ids_mannheim.korap.oauth2.entity.AccessScope;
 import de.ids_mannheim.korap.oauth2.entity.Authorization;
 import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
 
@@ -39,50 +28,17 @@
 
     @Autowired
     private OAuth2ClientService clientService;
-    
+
     @Autowired
     private OAuth2AuthorizationService authorizationService;
-    
-    @Autowired
-    private OAuth2ScopeService scopeService;
-    @Autowired
-    private AccessTokenDao tokenDao;
 
     @Autowired
-    private FullConfiguration config;
+    protected OAuth2ScopeService scopeService;
+
+    @Autowired
+    protected FullConfiguration config;
     @Autowired
     private AuthenticationManagerIface authenticationManager;
-    @Autowired
-    private OAuthIssuer oauthIssuer;
-
-    public OAuthResponse requestAccessToken (
-            AbstractOAuthTokenRequest oAuthRequest)
-            throws KustvaktException, OAuthSystemException {
-
-        String grantType = oAuthRequest.getGrantType();
-
-        if (grantType.equals(GrantType.AUTHORIZATION_CODE.toString())) {
-            return requestAccessTokenWithAuthorizationCode(
-                    oAuthRequest.getCode(), oAuthRequest.getRedirectURI(),
-                    oAuthRequest.getClientId(), oAuthRequest.getClientSecret());
-        }
-        else if (grantType.equals(GrantType.PASSWORD.toString())) {
-            return requestAccessTokenWithPassword(oAuthRequest.getUsername(),
-                    oAuthRequest.getPassword(), oAuthRequest.getScopes(),
-                    oAuthRequest.getClientId(), oAuthRequest.getClientSecret());
-        }
-        else if (grantType.equals(GrantType.CLIENT_CREDENTIALS.toString())) {
-            return requestAccessTokenWithClientCredentials(
-                    oAuthRequest.getClientId(), oAuthRequest.getClientSecret(),
-                    oAuthRequest.getScopes());
-        }
-        else {
-            throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
-                    grantType + " is not supported.",
-                    OAuth2Error.UNSUPPORTED_GRANT_TYPE);
-        }
-
-    }
 
     /**
      * RFC 6749:
@@ -98,15 +54,13 @@
      * @param clientSecret
      *            clilent_secret, required if client_secret was issued
      *            for the client in client registration.
-     * @return an OAuthResponse containing an access token if
-     *         successful
+     * @return an authorization
      * @throws OAuthSystemException
      * @throws KustvaktException
      */
-    private OAuthResponse requestAccessTokenWithAuthorizationCode (
+    protected Authorization requestAccessTokenWithAuthorizationCode (
             String authorizationCode, String redirectURI, String clientId,
-            String clientSecret)
-            throws KustvaktException, OAuthSystemException {
+            String clientSecret) throws KustvaktException {
 
         Authorization authorization =
                 authorizationService.retrieveAuthorization(authorizationCode);
@@ -119,7 +73,7 @@
             authorizationService.addTotalAttempts(authorization);
             throw e;
         }
-        return createsAccessTokenResponse(authorization);
+        return authorization;
     }
 
 
@@ -152,10 +106,9 @@
      * @throws KustvaktException
      * @throws OAuthSystemException
      */
-    private OAuthResponse requestAccessTokenWithPassword (String username,
+    protected void requestAccessTokenWithPassword (String username,
             String password, Set<String> scopes, String clientId,
-            String clientSecret)
-            throws KustvaktException, OAuthSystemException {
+            String clientSecret) throws KustvaktException {
 
         OAuth2Client client =
                 clientService.authenticateClient(clientId, clientSecret);
@@ -167,7 +120,6 @@
 
         authenticateUser(username, password, scopes);
         // verify or limit scopes ?
-        return createsAccessTokenResponse(scopes, username);
     }
 
     public void authenticateUser (String username, String password,
@@ -204,9 +156,9 @@
      * @throws KustvaktException
      * @throws OAuthSystemException
      */
-    private OAuthResponse requestAccessTokenWithClientCredentials (
+    protected Set<String> requestAccessTokenWithClientCredentials (
             String clientId, String clientSecret, Set<String> scopes)
-            throws KustvaktException, OAuthSystemException {
+            throws KustvaktException {
 
         if (clientSecret == null || clientSecret.isEmpty()) {
             throw new KustvaktException(
@@ -228,54 +180,9 @@
 
         scopes = scopeService.filterScopes(scopes,
                 config.getClientCredentialsScopes());
-        return createsAccessTokenResponse(scopes, null);
+        return scopes;
     }
 
-    /**
-     * Creates an OAuthResponse containing an access token and a
-     * refresh token with type Bearer.
-     * 
-     * @return an OAuthResponse containing an access token
-     * @throws OAuthSystemException
-     * @throws KustvaktException
-     */
 
-    private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
-            String userId) throws OAuthSystemException, KustvaktException {
 
-        String accessToken = oauthIssuer.accessToken();
-        // String refreshToken = oauthIssuer.refreshToken();
-
-        Set<AccessScope> accessScopes =
-                scopeService.convertToAccessScope(scopes);
-        tokenDao.storeAccessToken(accessToken, accessScopes, userId);
-
-        return OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
-                .setAccessToken(accessToken)
-                .setTokenType(TokenType.BEARER.toString())
-                .setExpiresIn(String.valueOf(config.getTokenTTL()))
-                // .setRefreshToken(refreshToken)
-                .setScope(String.join(" ", scopes)).buildJSONMessage();
-    }
-
-    private OAuthResponse createsAccessTokenResponse (
-            Authorization authorization)
-            throws OAuthSystemException, KustvaktException {
-        String accessToken = oauthIssuer.accessToken();
-        // String refreshToken = oauthIssuer.refreshToken();
-
-        tokenDao.storeAccessToken(authorization, accessToken);
-
-        String scopes = scopeService
-                .convertAccessScopesToString(authorization.getScopes());
-
-        OAuthResponse r =
-                OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
-                        .setAccessToken(accessToken)
-                        .setTokenType(TokenType.BEARER.toString())
-                        .setExpiresIn(String.valueOf(config.getTokenTTL()))
-                        // .setRefreshToken(refreshToken)
-                        .setScope(scopes).buildJSONMessage();
-        return r;
-    }
 }
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 d244e90..959803c 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
@@ -46,21 +46,44 @@
         return throwit(StatusCodes.OAUTH2_SYSTEM_ERROR, e.getMessage());
     }
 
+    public WebApplicationException throwit (OAuthSystemException e,
+            String state) {
+        if (state != null && !state.isEmpty()) {
+            return throwit(StatusCodes.OAUTH2_SYSTEM_ERROR, e.getMessage(),
+                    "state=" + state);
+        }
+        return throwit(e);
+    }
+
     public WebApplicationException throwit (OAuthProblemException e) {
+        return throwit(e, null);
+    }
+
+    public WebApplicationException throwit (OAuthProblemException e,
+            String state) {
         OAuthResponse oAuthResponse = null;
         try {
-            oAuthResponse = OAuthResponse.errorResponse(e.getResponseStatus())
-                    .error(e).buildJSONMessage();
+             OAuthErrorResponseBuilder builder = OAuthResponse.errorResponse(e.getResponseStatus())
+                    .error(e);
+                    
+             if (state != null && !state.isEmpty()) {
+                 builder.setState(state);
+             }
+             oAuthResponse = builder.buildJSONMessage();
         }
         catch (OAuthSystemException e1) {
-            throwit(e1);
+            throwit(e1, state);
         }
         Response r = createResponse(oAuthResponse);
         return new WebApplicationException(r);
     }
 
     @Override
-    public WebApplicationException throwit (KustvaktException e) {
+    public WebApplicationException throwit (KustvaktException e){
+        return throwit(e, null);
+    }
+    
+    public WebApplicationException throwit (KustvaktException e, String state) {
         OAuthResponse oAuthResponse = null;
         String errorCode = e.getEntity();
         try {
@@ -68,7 +91,7 @@
                     || errorCode.equals(OAuth2Error.UNAUTHORIZED_CLIENT)
                     || errorCode.equals(OAuth2Error.INVALID_TOKEN)) {
                 oAuthResponse = createOAuthResponse(e,
-                        Status.UNAUTHORIZED.getStatusCode());
+                        Status.UNAUTHORIZED.getStatusCode(), state);
             }
             else if (errorCode.equals(OAuth2Error.INVALID_GRANT)
                     || errorCode.equals(OAuth2Error.INVALID_REQUEST)
@@ -77,26 +100,26 @@
                     || errorCode.equals(OAuth2Error.UNSUPPORTED_RESPONSE_TYPE)
                     || errorCode.equals(OAuth2Error.ACCESS_DENIED)) {
                 oAuthResponse = createOAuthResponse(e,
-                        Status.BAD_REQUEST.getStatusCode());
+                        Status.BAD_REQUEST.getStatusCode(), state);
             }
             else if (errorCode.equals(OAuth2Error.INSUFFICIENT_SCOPE)) {
                 oAuthResponse = createOAuthResponse(e,
-                        Status.FORBIDDEN.getStatusCode());
+                        Status.FORBIDDEN.getStatusCode(), state);
             }
             else if (errorCode.equals(OAuth2Error.SERVER_ERROR)) {
                 oAuthResponse = createOAuthResponse(e,
-                        Status.INTERNAL_SERVER_ERROR.getStatusCode());
+                        Status.INTERNAL_SERVER_ERROR.getStatusCode(), state);
             }
             else if (errorCode.equals(OAuth2Error.TEMPORARILY_UNAVAILABLE)) {
                 oAuthResponse = createOAuthResponse(e,
-                        Status.SERVICE_UNAVAILABLE.getStatusCode());
+                        Status.SERVICE_UNAVAILABLE.getStatusCode(), state);
             }
             else {
                 return super.throwit(e);
             }
         }
         catch (OAuthSystemException e1) {
-            return throwit(e1);
+            return throwit(e1, state);
         }
 
         Response r = createResponse(oAuthResponse);
@@ -104,12 +127,16 @@
     }
 
     private OAuthResponse createOAuthResponse (KustvaktException e,
-            int statusCode) throws OAuthSystemException {
+            int statusCode, String state) throws OAuthSystemException {
         OAuthProblemException oAuthProblemException = OAuthProblemException
-                .error(e.getEntity()).description(e.getMessage());
+                .error(e.getEntity()).state(state).description(e.getMessage());
 
         OAuthErrorResponseBuilder responseBuilder = OAuthResponse
                 .errorResponse(statusCode).error(oAuthProblemException);
+        if (state!=null && !state.isEmpty()){
+            responseBuilder.setState(state);
+        }
+            
         URI redirectUri = e.getRedirectUri();
         if (redirectUri != null) {
             responseBuilder.location(redirectUri.toString());
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/OpenIdResponseHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/OpenIdResponseHandler.java
index 21cecdb..eb6a19e 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/OpenIdResponseHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/OpenIdResponseHandler.java
@@ -7,13 +7,17 @@
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
 
+import org.apache.http.HttpHeaders;
 import org.springframework.stereotype.Service;
 
+import com.nimbusds.oauth2.sdk.AccessTokenResponse;
 import com.nimbusds.oauth2.sdk.AuthorizationErrorResponse;
 import com.nimbusds.oauth2.sdk.ErrorObject;
 import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.TokenErrorResponse;
 import com.nimbusds.oauth2.sdk.id.State;
 import com.nimbusds.oauth2.sdk.token.BearerTokenError;
 import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
@@ -52,10 +56,12 @@
 
     final static Map<String, ErrorObject> tokenErrorObjectMap = new HashMap<>();
     {
-        errorObjectMap.put(OAuth2Error.INSUFFICIENT_SCOPE,
+        tokenErrorObjectMap.put(OAuth2Error.INSUFFICIENT_SCOPE,
                 BearerTokenError.INSUFFICIENT_SCOPE);
-        errorObjectMap.put(OAuth2Error.INVALID_TOKEN,
+        tokenErrorObjectMap.put(OAuth2Error.INVALID_TOKEN,
                 BearerTokenError.INVALID_TOKEN);
+        tokenErrorObjectMap.put(OAuth2Error.INVALID_REQUEST,
+                BearerTokenError.INVALID_REQUEST);
     }
 
     public OpenIdResponseHandler (AuditingIface iface) {
@@ -140,4 +146,71 @@
 
     }
 
+    public void createTokenErrorResponse (KustvaktException e) {
+
+        String errorCode = e.getEntity();
+        ErrorObject errorObject = tokenErrorObjectMap.get(errorCode);
+        if (errorObject == null) {
+            errorObject = errorObjectMap.get(errorCode);
+            if (errorObject == null) {
+                errorObject = new ErrorObject(e.getEntity(), e.getMessage());
+            }
+        }
+
+        TokenErrorResponse errorResponse = new TokenErrorResponse(errorObject);
+        Status status = determineErrorStatus(errorCode);
+        createResponse(errorResponse, status);
+    }
+
+    public Response createResponse (AccessTokenResponse tokenResponse,
+            Status status) {
+        String jsonString = tokenResponse.toJSONObject().toJSONString();
+        return createResponse(status, jsonString);
+    }
+    
+    public Response createResponse (TokenErrorResponse tokenResponse,
+            Status status) {
+        String jsonString = tokenResponse.toJSONObject().toJSONString();
+        return createResponse(status, jsonString);
+    }
+    
+    private Response createResponse(Status status, Object entity){
+        ResponseBuilder builder = Response.status(status);
+        builder.entity(entity);
+        builder.header(HttpHeaders.CACHE_CONTROL, "no-store");
+        builder.header(HttpHeaders.PRAGMA, "no-store");
+
+        if (status == Status.UNAUTHORIZED) {
+            builder.header(HttpHeaders.WWW_AUTHENTICATE,
+                    "Basic realm=\"Kustvakt\"");
+        }
+        return builder.build();
+    }
+
+    private Status determineErrorStatus (String errorCode) {
+        Status status = Status.BAD_REQUEST;
+        if (errorCode.equals(OAuth2Error.INVALID_CLIENT)
+                || errorCode.equals(OAuth2Error.UNAUTHORIZED_CLIENT)
+                || errorCode.equals(OAuth2Error.INVALID_TOKEN)) {
+            status = Status.UNAUTHORIZED;
+        }
+        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)) {
+            status = Status.BAD_REQUEST;
+        }
+        else if (errorCode.equals(OAuth2Error.INSUFFICIENT_SCOPE)) {
+            status = Status.FORBIDDEN;
+        }
+        else if (errorCode.equals(OAuth2Error.SERVER_ERROR)) {
+            status = Status.INTERNAL_SERVER_ERROR;
+        }
+        else if (errorCode.equals(OAuth2Error.TEMPORARILY_UNAVAILABLE)) {
+            status = Status.SERVICE_UNAVAILABLE;
+        }
+        return status;
+    }
 }
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 6614f11..3726f17 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
@@ -25,9 +25,9 @@
 import com.sun.jersey.spi.container.ResourceFilters;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.oauth2.OAuth2AuthorizationRequest;
+import de.ids_mannheim.korap.oauth2.oltu.OAuth2AuthorizationRequest;
 import de.ids_mannheim.korap.oauth2.oltu.service.OltuAuthorizationService;
-import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
+import de.ids_mannheim.korap.oauth2.oltu.service.OltuTokenService;
 import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
 import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
@@ -41,7 +41,7 @@
     @Autowired
     private OAuth2ResponseHandler responseHandler;
     @Autowired
-    private OAuth2TokenService oAuth2Service;
+    private OltuTokenService tokenService;
     @Autowired
     private OltuAuthorizationService authorizationService;
 
@@ -74,6 +74,7 @@
     public Response requestAuthorizationCode (
             @Context HttpServletRequest request,
             @Context SecurityContext context,
+            @FormParam("state") String state,
             MultivaluedMap<String, String> form) {
 
         TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
@@ -89,13 +90,13 @@
             return responseHandler.sendRedirect(uri);
         }
         catch (OAuthSystemException e) {
-            throw responseHandler.throwit(e);
+            throw responseHandler.throwit(e, state);
         }
         catch (OAuthProblemException e) {
-            throw responseHandler.throwit(e);
+            throw responseHandler.throwit(e, state);
         }
         catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
+            throw responseHandler.throwit(e, state);
         }
     }
 
@@ -167,7 +168,7 @@
             }
 
             OAuthResponse oAuthResponse =
-                    oAuth2Service.requestAccessToken(oAuthRequest);
+                    tokenService.requestAccessToken(oAuthRequest);
 
             return responseHandler.createResponse(oAuthResponse);
         }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
index 82affd6..d0e69d7 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
@@ -1,6 +1,8 @@
 package de.ids_mannheim.korap.web.controller;
 
+import java.net.MalformedURLException;
 import java.net.URI;
+import java.net.URL;
 import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
@@ -13,18 +15,24 @@
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.SecurityContext;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
+import com.nimbusds.oauth2.sdk.AccessTokenResponse;
 import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest.Method;
 import com.nimbusds.oauth2.sdk.id.State;
 import com.sun.jersey.spi.container.ResourceFilters;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.openid.OpenIdHttpRequestWrapper;
 import de.ids_mannheim.korap.oauth2.openid.service.OpenIdAuthorizationService;
+import de.ids_mannheim.korap.oauth2.openid.service.OpenIdTokenService;
 import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.web.OpenIdResponseHandler;
 import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
@@ -38,6 +46,8 @@
     @Autowired
     private OpenIdAuthorizationService authzService;
     @Autowired
+    private OpenIdTokenService tokenService;
+    @Autowired
     private OpenIdResponseHandler openIdResponseHandler;
 
     /**
@@ -132,4 +142,46 @@
         return builder.build();
     }
 
+
+    @POST
+    @Path("token")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+    public Response requestAccessToken (
+            @Context HttpServletRequest servletRequest,
+            MultivaluedMap<String, String> form) {
+
+        Map<String, String> map = MapUtils.toMap(form);
+        Method method = Method.valueOf(servletRequest.getMethod());
+        URL url = null;
+        try {
+            url = new URL(servletRequest.getRequestURL().toString());
+        }
+        catch (MalformedURLException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        try {
+            OpenIdHttpRequestWrapper httpRequest =
+                    new OpenIdHttpRequestWrapper(method, url);
+            httpRequest.toHttpRequest(servletRequest, map);
+
+            TokenRequest tokenRequest = TokenRequest.parse(httpRequest);
+            AccessTokenResponse tokenResponse =
+                    tokenService.requestAccessToken(tokenRequest);
+            return openIdResponseHandler.createResponse(tokenResponse,
+                    Status.OK);
+        }
+        catch (ParseException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        catch (KustvaktException e) {
+            openIdResponseHandler.createTokenErrorResponse(e);
+        }
+
+        return null;
+
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
index cb00c12..2c36ad6 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -23,7 +23,7 @@
 
 import de.ids_mannheim.korap.dto.OAuth2ClientDto;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.oauth2.OAuth2DeregisterClientRequest;
+import de.ids_mannheim.korap.oauth2.oltu.OAuth2DeregisterClientRequest;
 import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
 import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
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 f7aa89a..97efe8d 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
@@ -77,6 +77,7 @@
         form.add("response_type", "code");
         form.add("client_id", "fCBbQkAyYzI4NzUxMg");
         form.add("redirect_uri", redirectUri);
+        form.add("state", "thisIsMyState");
         ClientResponse response = requestAuthorizationConfidentialClient(form);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
@@ -87,13 +88,15 @@
                 node.at("/error").asText());
         assertEquals("Invalid redirect URI",
                 node.at("/error_description").asText());
+        assertEquals("thisIsMyState", node.at("/state").asText());
     }
 
     @Test
     public void testAuthorizeMissingRequiredParameters ()
             throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        // missing code
+        form.add("state", "thisIsMyState");
+        // missing response_type
         ClientResponse response = requestAuthorizationConfidentialClient(form);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
@@ -104,6 +107,7 @@
                 node.at("/error").asText());
         assertEquals("Missing response_type parameter value",
                 node.at("/error_description").asText());
+        assertEquals("thisIsMyState", node.at("/state").asText());
 
         // missing client_id
         form.add("response_type", "code");
@@ -118,7 +122,8 @@
     public void testAuthorizeInvalidResponseType () throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("response_type", "string");
-
+        form.add("state", "thisIsMyState");
+        
         ClientResponse response = requestAuthorizationConfidentialClient(form);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
@@ -128,6 +133,7 @@
                 node.at("/error").asText());
         assertEquals("Invalid response_type parameter value",
                 node.at("/error_description").asText());
+        assertEquals("thisIsMyState", node.at("/state").asText());
     }
 
     @Test
@@ -136,7 +142,8 @@
         form.add("response_type", "code");
         form.add("client_id", "fCBbQkAyYzI4NzUxMg");
         form.add("scope", "read_address");
-
+        form.add("state", "thisIsMyState");
+        
         ClientResponse response = requestAuthorizationConfidentialClient(form);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
@@ -145,6 +152,7 @@
                 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"));
     }
 
     private ClientResponse requestToken (MultivaluedMap<String, String> form)
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
index d4a337d..c4156d6 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
@@ -9,6 +9,7 @@
 import javax.ws.rs.core.MultivaluedMap;
 
 import org.apache.http.entity.ContentType;
+import org.apache.oltu.oauth2.common.message.types.TokenType;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.MultiValueMap;
@@ -47,6 +48,15 @@
                         ContentType.APPLICATION_FORM_URLENCODED)
                 .entity(form).post(ClientResponse.class);
     }
+    
+    private ClientResponse sendTokenRequest (MultivaluedMap<String, String> form)
+            throws KustvaktException {
+        return resource().path("oauth2").path("openid").path("token")
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+    }
 
     @Test
     public void testRequestAuthorizationCode ()
@@ -176,4 +186,40 @@
         assertEquals("unsupported+response_type%3A+id_token",
                 params.getFirst("error_description"));
     }
+    
+    @Test
+    public void testRequestAccessToken () throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("response_type", "code");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("redirect_uri", redirectUri);
+        form.add("scope", "openid");
+        form.add("state", "thisIsMyState");
+
+        ClientResponse response = sendAuthorizationRequest(form);
+        URI location = response.getLocation();
+        MultiValueMap<String, String> params =
+                UriComponentsBuilder.fromUri(location).build().getQueryParams();
+        String code = params.getFirst("code");
+        
+        MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
+        tokenForm.add("grant_type", "authorization_code");
+        tokenForm.add("redirect_uri", redirectUri);
+        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        tokenForm.add("client_secret", "secret");
+        tokenForm.add("code", code);
+        
+        ClientResponse tokenResponse = sendTokenRequest(tokenForm);
+        String entity = tokenResponse.getEntity(String.class);
+//        System.out.println(entity);
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertNotNull(node.at("/access_token").asText());
+        assertNotNull(node.at("/refresh_token").asText());
+        assertEquals(TokenType.BEARER.toString(),
+                node.at("/token_type").asText());
+        assertNotNull(node.at("/expires_in").asText());
+        assertNotNull(node.at("/id_token").asText());
+
+    }
 }