Updated RefreshToken implementations with separate DB tables. Allows
multiple access tokens per refresh token.

Change-Id: I5227191ed38a2f5260e1249d548f893a49108dce
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
index f701777..b0785cd 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
@@ -11,7 +11,6 @@
 import javax.persistence.Query;
 import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.CriteriaQuery;
-import javax.persistence.criteria.Predicate;
 import javax.persistence.criteria.Root;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -27,7 +26,7 @@
 import de.ids_mannheim.korap.oauth2.entity.AccessScope;
 import de.ids_mannheim.korap.oauth2.entity.AccessToken;
 import de.ids_mannheim.korap.oauth2.entity.AccessToken_;
-import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
 import de.ids_mannheim.korap.utils.ParameterChecker;
 
 @Repository
@@ -43,23 +42,7 @@
         super("access_token", "key:access_token");
     }
 
-    @Deprecated
-    public void storeAccessToken (Authorization authorization, String token)
-            throws KustvaktException {
-        ParameterChecker.checkObjectValue(authorization, "Authorization");
-        ParameterChecker.checkStringValue(token, "accessToken");
-
-        AccessToken accessToken = new AccessToken();
-        // accessToken.setAuthorization(authorization);
-        accessToken.setUserId(authorization.getUserId());
-        accessToken.setToken(token);
-        accessToken.setScopes(authorization.getScopes());
-        accessToken.setUserAuthenticationTime(
-                authorization.getUserAuthenticationTime());
-        entityManager.persist(accessToken);
-    }
-
-    public void storeAccessToken (String token, String refreshToken,
+    public void storeAccessToken (String token, RefreshToken refreshToken,
             Set<AccessScope> scopes, String userId, String clientId,
             ZonedDateTime authenticationTime) throws KustvaktException {
         ParameterChecker.checkStringValue(token, "access token");
@@ -77,8 +60,6 @@
         accessToken.setCreatedDate(now);
         accessToken
                 .setExpiryDate(now.plusSeconds(config.getAccessTokenExpiry()));
-        accessToken.setRefreshTokenExpiryDate(
-                now.plusSeconds(config.getRefreshTokenExpiry()));
         accessToken.setToken(token);
         accessToken.setRefreshToken(refreshToken);
         accessToken.setScopes(scopes);
@@ -128,57 +109,6 @@
     }
 
     @SuppressWarnings("unchecked")
-    public List<AccessToken> retrieveAccessTokenByRefreshToken (
-            String refreshToken) throws KustvaktException {
-
-        ParameterChecker.checkStringValue(refreshToken, "refresh_token");
-        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
-        CriteriaQuery<AccessToken> query =
-                builder.createQuery(AccessToken.class);
-        Root<AccessToken> root = query.from(AccessToken.class);
-        Predicate condition = builder.equal(root.get(AccessToken_.refreshToken),
-                refreshToken);
-
-        query.select(root);
-        query.where(condition);
-        query.orderBy(builder.desc(root.get(AccessToken_.createdDate)));
-
-        Query q = entityManager.createQuery(query);
-        return q.getResultList();
-    }
-
-    public AccessToken retrieveAccessTokenByAnynomousToken (String token)
-            throws KustvaktException {
-        ParameterChecker.checkObjectValue(token, "token");
-        AccessToken accessToken = (AccessToken) this.getCacheValue(token);
-        if (accessToken != null) {
-            return accessToken;
-        }
-
-        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
-        CriteriaQuery<AccessToken> query =
-                builder.createQuery(AccessToken.class);
-
-        Root<AccessToken> root = query.from(AccessToken.class);
-        Predicate condition = builder.or(
-                builder.equal(root.get(AccessToken_.token), token),
-                builder.equal(root.get(AccessToken_.refreshToken), token));
-
-        query.select(root);
-        query.where(condition);
-        Query q = entityManager.createQuery(query);
-
-        try {
-            accessToken = (AccessToken) q.getSingleResult();
-            return accessToken;
-        }
-        catch (NoResultException e) {
-            throw new KustvaktException(StatusCodes.INVALID_ACCESS_TOKEN,
-                    "Access token is not found", OAuth2Error.INVALID_TOKEN);
-        }
-    }
-
-    @SuppressWarnings("unchecked")
     public List<AccessToken> retrieveAccessTokenByClientId (String clientId) {
         CriteriaBuilder builder = entityManager.getCriteriaBuilder();
         CriteriaQuery<AccessToken> query =
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java
new file mode 100644
index 0000000..e98c627
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java
@@ -0,0 +1,94 @@
+package de.ids_mannheim.korap.oauth2.dao;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Root;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
+import de.ids_mannheim.korap.oauth2.entity.RefreshToken_;
+import de.ids_mannheim.korap.utils.ParameterChecker;
+
+@Repository
+@Transactional
+public class RefreshTokenDao {
+
+    @PersistenceContext
+    private EntityManager entityManager;
+    @Autowired
+    private FullConfiguration config;
+
+    public RefreshToken storeRefreshToken (String refreshToken, String userId,
+            ZonedDateTime userAuthenticationTime, String clientId,
+            Set<AccessScope> scopes) throws KustvaktException {
+        ParameterChecker.checkStringValue(refreshToken, "refresh token");
+        // ParameterChecker.checkStringValue(userId, "username");
+        ParameterChecker.checkStringValue(clientId, "client_id");
+        ParameterChecker.checkObjectValue(scopes, "scopes");
+
+        ZonedDateTime now =
+                ZonedDateTime.now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE));
+
+        RefreshToken token = new RefreshToken();
+        token.setToken(refreshToken);
+        token.setUserId(userId);
+        token.setUserAuthenticationTime(userAuthenticationTime);
+        token.setClientId(clientId);
+        token.setCreatedDate(now);
+        token.setExpiryDate(now.plusSeconds(config.getRefreshTokenExpiry()));
+        token.setScopes(scopes);
+
+        entityManager.persist(token);
+        return token;
+    }
+
+    public RefreshToken retrieveRefreshToken (String token)
+            throws KustvaktException {
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<RefreshToken> query =
+                builder.createQuery(RefreshToken.class);
+        Root<RefreshToken> root = query.from(RefreshToken.class);
+        query.select(root);
+        query.where(builder.equal(root.get(RefreshToken_.token), token));
+        Query q = entityManager.createQuery(query);
+        return (RefreshToken) q.getSingleResult();
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<RefreshToken> retrieveRefreshTokenByClientId (String clientId)
+            throws KustvaktException {
+        ParameterChecker.checkStringValue(clientId, "client_id");
+
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<RefreshToken> query =
+                builder.createQuery(RefreshToken.class);
+        Root<RefreshToken> root = query.from(RefreshToken.class);
+        query.select(root);
+        query.where(builder.equal(root.get(RefreshToken_.clientId), clientId));
+        Query q = entityManager.createQuery(query);
+        return q.getResultList();
+    }
+
+    public RefreshToken updateRefreshToken (RefreshToken token)
+            throws KustvaktException {
+        ParameterChecker.checkObjectValue(token, "refresh_token");
+
+        token = entityManager.merge(token);
+        return token;
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
index b26a90a..8fb2e0d 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
@@ -38,6 +38,9 @@
     
     @ManyToMany(mappedBy = "scopes", fetch = FetchType.LAZY)
     private List<AccessToken> accessTokens;
+    
+    @ManyToMany(mappedBy = "scopes", fetch = FetchType.LAZY)
+    private List<RefreshToken> refreshTokens;
 
     @Override
     public String toString () {
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
index e7509d0..ba381de 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
@@ -13,6 +13,7 @@
 import javax.persistence.JoinColumn;
 import javax.persistence.JoinTable;
 import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
 import javax.persistence.Table;
 import javax.persistence.UniqueConstraint;
 
@@ -43,12 +44,6 @@
     private boolean isRevoked;
     @Column(name = "user_auth_time", updatable = false)
     private ZonedDateTime userAuthenticationTime;
-    @Column(name = "refresh_token", updatable = false)
-    private String refreshToken;
-    @Column(name = "refresh_expiry_date", updatable = false)
-    private ZonedDateTime refreshTokenExpiryDate;
-    @Column(name = "is_refresh_revoked")
-    private boolean isRefreshTokenRevoked;
 
     // @OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.REMOVE)
     // @JoinColumn(name="authorization_id")
@@ -64,4 +59,7 @@
                     columnNames = { "token_id", "scope_id" }))
     private Set<AccessScope> scopes;
 
+    @ManyToOne(fetch=FetchType.LAZY)
+    @JoinColumn(name="refresh_token")
+    private RefreshToken refreshToken;
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/RefreshToken.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/RefreshToken.java
new file mode 100644
index 0000000..a43a493
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/RefreshToken.java
@@ -0,0 +1,126 @@
+package de.ids_mannheim.korap.oauth2.entity;
+
+import java.time.ZonedDateTime;
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+@Entity
+@Table(name = "oauth2_refresh_token")
+public class RefreshToken {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private int id;
+    private String token;
+    @Column(name = "created_date", updatable = false)
+    private ZonedDateTime createdDate;
+    @Column(name = "expiry_date", updatable = false)
+    private ZonedDateTime expiryDate;
+    @Column(name = "user_id")
+    private String userId;
+    @Column(name = "client_id")
+    private String clientId;
+    @Column(name = "user_auth_time", updatable = false)
+    private ZonedDateTime userAuthenticationTime;
+    @Column(name = "is_revoked")
+    private boolean isRevoked;
+
+    @OneToMany(fetch = FetchType.EAGER, mappedBy = "refreshToken")
+    private Set<AccessToken> accessTokens;
+
+    @ManyToMany(fetch = FetchType.EAGER)
+    @JoinTable(name = "oauth2_refresh_token_scope",
+            joinColumns = @JoinColumn(name = "token_id",
+                    referencedColumnName = "id"),
+            inverseJoinColumns = @JoinColumn(name = "scope_id",
+                    referencedColumnName = "id"),
+            uniqueConstraints = @UniqueConstraint(
+                    columnNames = { "token_id", "scope_id" }))
+    private Set<AccessScope> scopes;
+
+    public String getToken () {
+        return token;
+    }
+
+    public void setToken (String token) {
+        this.token = token;
+    }
+
+    public ZonedDateTime getCreatedDate () {
+        return createdDate;
+    }
+
+    public void setCreatedDate (ZonedDateTime createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public ZonedDateTime getExpiryDate () {
+        return expiryDate;
+    }
+
+    public void setExpiryDate (ZonedDateTime expiryDate) {
+        this.expiryDate = expiryDate;
+    }
+
+    public String getUserId () {
+        return userId;
+    }
+
+    public void setUserId (String userId) {
+        this.userId = userId;
+    }
+
+    public String getClientId () {
+        return clientId;
+    }
+
+    public void setClientId (String clientId) {
+        this.clientId = clientId;
+    }
+
+    public ZonedDateTime getUserAuthenticationTime () {
+        return userAuthenticationTime;
+    }
+
+    public void setUserAuthenticationTime (
+            ZonedDateTime userAuthenticationTime) {
+        this.userAuthenticationTime = userAuthenticationTime;
+    }
+
+    public boolean isRevoked () {
+        return isRevoked;
+    }
+
+    public void setRevoked (boolean isRevoked) {
+        this.isRevoked = isRevoked;
+    }
+
+    public Set<AccessToken> getAccessTokens () {
+        return accessTokens;
+    }
+
+    public void setAccessTokens (Set<AccessToken> accessTokens) {
+        this.accessTokens = accessTokens;
+    }
+
+    public Set<AccessScope> getScopes () {
+        return scopes;
+    }
+
+    public void setScopes (Set<AccessScope> scopes) {
+        this.scopes = scopes;
+    }
+
+}
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
index 7730b1e..fb7b541 100644
--- 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
@@ -3,9 +3,9 @@
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
+import javax.persistence.NoResultException;
 import javax.ws.rs.core.Response.Status;
 
 import org.apache.oltu.oauth2.as.request.AbstractOAuthTokenRequest;
@@ -23,10 +23,12 @@
 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.dao.RefreshTokenDao;
 import de.ids_mannheim.korap.oauth2.entity.AccessScope;
 import de.ids_mannheim.korap.oauth2.entity.AccessToken;
 import de.ids_mannheim.korap.oauth2.entity.Authorization;
 import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenRequest;
 import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
 
@@ -38,6 +40,8 @@
 
     @Autowired
     private AccessTokenDao tokenDao;
+    @Autowired
+    private RefreshTokenDao refreshDao;
 
     public OAuthResponse requestAccessToken (
             AbstractOAuthTokenRequest oAuthRequest)
@@ -82,7 +86,7 @@
      * Client authentication is done using the given client
      * credentials.
      * 
-     * @param refreshToken
+     * @param refreshTokenStr
      * @param scopes
      * @param clientId
      * @param clientSecret
@@ -91,58 +95,52 @@
      * @throws OAuthSystemException
      */
     private OAuthResponse requestAccessTokenWithRefreshToken (
-            String refreshToken, Set<String> scopes, String clientId,
+            String refreshTokenStr, Set<String> scopes, String clientId,
             String clientSecret)
             throws KustvaktException, OAuthSystemException {
 
-        if (refreshToken == null || refreshToken.isEmpty()) {
+        if (refreshTokenStr == null || refreshTokenStr.isEmpty()) {
             throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
                     "Missing parameters: refresh_token",
                     OAuth2Error.INVALID_REQUEST);
         }
 
         clientService.authenticateClient(clientId, clientSecret);
-        List<AccessToken> accessTokenList =
-                tokenDao.retrieveAccessTokenByRefreshToken(refreshToken);
-        if (accessTokenList.isEmpty()) {
+
+        RefreshToken refreshToken;
+        try {
+            refreshToken = refreshDao.retrieveRefreshToken(refreshTokenStr);
+        }
+        catch (NoResultException e) {
             throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
                     "Refresh token is not found", OAuth2Error.INVALID_GRANT);
         }
 
-        AccessToken latestAccessToken = accessTokenList.get(0);
-        AccessToken origin = accessTokenList.get(accessTokenList.size() - 1);
-
-        if (!clientId.equals(latestAccessToken.getClientId())) {
+        if (!clientId.equals(refreshToken.getClientId())) {
             throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
                     "Client " + clientId + "is not authorized",
                     OAuth2Error.INVALID_CLIENT);
         }
-
-        if (latestAccessToken.isRefreshTokenRevoked()) {
+        else if (refreshToken.isRevoked()) {
             throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
                     "Refresh token has been revoked.",
                     OAuth2Error.INVALID_GRANT);
         }
         else if (ZonedDateTime.now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE))
-                .isAfter(origin.getRefreshTokenExpiryDate())) {
+                .isAfter(refreshToken.getExpiryDate())) {
             throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
                     "Refresh token is expired.", OAuth2Error.INVALID_GRANT);
         }
 
-        Set<AccessScope> requestedScopes = latestAccessToken.getScopes();
+        Set<AccessScope> requestedScopes = refreshToken.getScopes();
         if (scopes != null && !scopes.isEmpty()) {
-            requestedScopes = scopeService.verifyRefreshScope(scopes,
-                    latestAccessToken.getScopes());
-        }
-
-        if (!latestAccessToken.isRevoked()) {
-            latestAccessToken.setRevoked(true);
-            tokenDao.updateAccessToken(latestAccessToken);
+            requestedScopes =
+                    scopeService.verifyRefreshScope(scopes, requestedScopes);
         }
 
         return createsAccessTokenResponse(scopes, requestedScopes, clientId,
-                latestAccessToken.getUserId(),
-                latestAccessToken.getUserAuthenticationTime(), refreshToken);
+                refreshToken.getUserId(),
+                refreshToken.getUserAuthenticationTime(), refreshToken);
     }
 
     /**
@@ -310,17 +308,19 @@
             ZonedDateTime authenticationTime)
             throws OAuthSystemException, KustvaktException {
 
-        String refreshToken = randomGenerator.createRandomCode();
+        String random = randomGenerator.createRandomCode();
+        RefreshToken refreshToken = refreshDao.storeRefreshToken(random, userId,
+                authenticationTime, clientId, accessScopes);
         return createsAccessTokenResponse(scopes, accessScopes, clientId,
                 userId, authenticationTime, refreshToken);
     }
 
     private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
             Set<AccessScope> accessScopes, String clientId, String userId,
-            ZonedDateTime authenticationTime, String refreshToken)
+            ZonedDateTime authenticationTime, RefreshToken refreshToken)
             throws OAuthSystemException, KustvaktException {
-        String accessToken = randomGenerator.createRandomCode();
 
+        String accessToken = randomGenerator.createRandomCode();
         tokenDao.storeAccessToken(accessToken, refreshToken, accessScopes,
                 userId, clientId, authenticationTime);
 
@@ -328,7 +328,7 @@
                 .setAccessToken(accessToken)
                 .setTokenType(TokenType.BEARER.toString())
                 .setExpiresIn(String.valueOf(config.getAccessTokenExpiry()))
-                .setRefreshToken(refreshToken)
+                .setRefreshToken(refreshToken.getToken())
                 .setScope(String.join(" ", scopes)).buildJSONMessage();
     }
 
@@ -368,17 +368,23 @@
     }
 
     private boolean revokeRefreshToken (String token) throws KustvaktException {
-        List<AccessToken> accessTokenList =
-                tokenDao.retrieveAccessTokenByRefreshToken(token);
-        if (accessTokenList.isEmpty()) {
+        RefreshToken refreshToken = null;
+        try {
+            refreshToken = refreshDao.retrieveRefreshToken(token);
+        }
+        catch (NoResultException e) {
             return false;
         }
 
+        refreshToken.setRevoked(true);
+        refreshDao.updateRefreshToken(refreshToken);
+
+        Set<AccessToken> accessTokenList = refreshToken.getAccessTokens();
         for (AccessToken accessToken : accessTokenList) {
             accessToken.setRevoked(true);
-            accessToken.setRefreshTokenRevoked(true);
             tokenDao.updateAccessToken(accessToken);
         }
+
         return true;
     }
 }
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
index a17b4c8..15570e1 100644
--- 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
@@ -47,6 +47,7 @@
 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.dao.RefreshTokenDao;
 import de.ids_mannheim.korap.oauth2.entity.AccessScope;
 import de.ids_mannheim.korap.oauth2.entity.Authorization;
 import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
@@ -73,6 +74,8 @@
 
     @Autowired
     private AccessTokenDao tokenDao;
+    @Autowired
+    private RefreshTokenDao refreshDao;
 
     public AccessTokenResponse requestAccessToken (TokenRequest tokenRequest)
             throws KustvaktException {
@@ -177,10 +180,11 @@
                 new BearerAccessToken(config.getAccessTokenExpiry(), scope);
 
         RefreshToken refreshToken = new RefreshToken();
-
-        tokenDao.storeAccessToken(accessToken.getValue(),
-                refreshToken.getValue(),
-                scopeService.convertToAccessScope(scopeSet), username,
+        Set<AccessScope> scopes = scopeService.convertToAccessScope(scopeSet);
+        de.ids_mannheim.korap.oauth2.entity.RefreshToken rt =
+                refreshDao.storeRefreshToken(refreshToken.getValue(), username,
+                        authenticationTime, clientId.getValue(), scopes);
+        tokenDao.storeAccessToken(accessToken.getValue(), rt, scopes, username,
                 clientIdStr, authenticationTime);
 
         return createsAccessTokenResponse(accessToken, refreshToken, scope,
@@ -230,10 +234,14 @@
         AccessToken accessToken =
                 new BearerAccessToken(config.getAccessTokenExpiry(), scope);
         RefreshToken refreshToken = new RefreshToken();
+        de.ids_mannheim.korap.oauth2.entity.RefreshToken rt =
+                refreshDao.storeRefreshToken(refreshToken.getValue(),
+                        authorization.getUserId(),
+                        authorization.getUserAuthenticationTime(),
+                        authorization.getClientId(), scopes);
 
-        tokenDao.storeAccessToken(accessToken.getValue(),
-                refreshToken.getValue(), scopes, authorization.getUserId(),
-                authorization.getClientId(),
+        tokenDao.storeAccessToken(accessToken.getValue(), rt, scopes,
+                authorization.getUserId(), authorization.getClientId(),
                 authorization.getUserAuthenticationTime());
 
         return createsAccessTokenResponse(accessToken, refreshToken, scope,
@@ -265,7 +273,6 @@
         }
     }
 
-
     private String[] extractClientCredentials (
             ClientAuthentication clientAuthentication)
             throws KustvaktException {
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 6e8803a..f077e33 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
@@ -21,11 +21,13 @@
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
 import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
 import de.ids_mannheim.korap.oauth2.dto.OAuth2ClientDto;
 import de.ids_mannheim.korap.oauth2.dto.OAuth2ClientInfoDto;
 import de.ids_mannheim.korap.oauth2.entity.AccessToken;
 import de.ids_mannheim.korap.oauth2.entity.Authorization;
 import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
 import de.ids_mannheim.korap.oauth2.interfaces.AuthorizationDaoInterface;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 
@@ -54,6 +56,8 @@
     @Autowired
     private AccessTokenDao tokenDao;
     @Autowired
+    private RefreshTokenDao refreshDao;
+    @Autowired
     private AuthorizationDaoInterface authorizationDao;
     @Autowired
     private AdminDao adminDao;
@@ -207,9 +211,15 @@
                 tokenDao.retrieveAccessTokenByClientId(clientId);
         for (AccessToken token : tokens) {
             token.setRevoked(true);
-            token.setRefreshTokenRevoked(true);
             tokenDao.updateAccessToken(token);
         }
+
+        List<RefreshToken> refreshTokens =
+                refreshDao.retrieveRefreshTokenByClientId(clientId);
+        for (RefreshToken token : refreshTokens) {
+            token.setRevoked(true);
+            refreshDao.updateRefreshToken(token);
+        }
     }
 
     public OAuth2ClientDto resetSecret (String clientId, String clientSecret,
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 ac082a9..99e7f68 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
@@ -85,10 +85,10 @@
         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 =
@@ -108,7 +108,6 @@
         }
     }
 
-
     /**
      * Grants a client an access token, namely a string used in
      * authenticated requests representing user authorization for
@@ -117,9 +116,8 @@
      * 
      * <br /><br />
      * 
-     * Clients may refreshing access token using this endpoint. This
-     * request will revoke all access token associated with the given
-     * refresh token, and grants a new access token. The refresh token
+     * Clients may request refresh access token using this endpoint.
+     * This request will grants a new access token. The refresh token
      * is not changed and can be used until it expires.
      * 
      * <br /><br />
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
deleted file mode 100644
index 2c43acc..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
+++ /dev/null
@@ -1,631 +0,0 @@
-package de.ids_mannheim.korap.web.controller;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.HeaderParam;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.SecurityContext;
-
-import org.apache.oltu.oauth2.as.issuer.MD5Generator;
-import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
-import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
-import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
-import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
-import org.apache.oltu.oauth2.as.response.OAuthASResponse;
-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.OAuthResponse;
-import org.apache.oltu.oauth2.common.message.types.GrantType;
-import org.apache.oltu.oauth2.common.message.types.ResponseType;
-import org.apache.oltu.oauth2.common.utils.OAuthUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import com.sun.jersey.spi.container.ContainerRequest;
-import com.sun.jersey.spi.container.ResourceFilters;
-
-import de.ids_mannheim.korap.config.Attributes;
-import de.ids_mannheim.korap.config.AuthCodeInfo;
-import de.ids_mannheim.korap.config.ClientInfo;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
-import de.ids_mannheim.korap.config.Scopes;
-import de.ids_mannheim.korap.constant.AuthenticationMethod;
-import de.ids_mannheim.korap.constant.TokenType;
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.handlers.OAuth2Handler;
-import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
-import de.ids_mannheim.korap.security.context.TokenContext;
-import de.ids_mannheim.korap.user.User;
-import de.ids_mannheim.korap.user.UserDetails;
-import de.ids_mannheim.korap.user.Userdata;
-import de.ids_mannheim.korap.utils.JsonUtils;
-import de.ids_mannheim.korap.utils.StringUtils;
-import de.ids_mannheim.korap.web.CoreResponseHandler;
-import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
-import de.ids_mannheim.korap.web.filter.BlockingFilter;
-import de.ids_mannheim.korap.web.filter.DemoUserFilter;
-import de.ids_mannheim.korap.web.filter.PiwikFilter;
-import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
-
-/**
- * @author hanl
- * @date 07/06/2014
- */
-//todo: only allow oauth2 access_token requests GET methods?
-//todo: allow refresh tokens
-//@Path("/oauth2")
-@Deprecated
-public class OAuthController {
-
-    @Autowired
-    private CoreResponseHandler kustvaktResponseHandler;
-    
-    private OAuth2Handler handler;
-    @Autowired
-    private AuthenticationManagerIface controller;
-    
-//    private EncryptionIface crypto;
-    @Autowired
-    private KustvaktConfiguration config;
-
-
-    public OAuthController () {
-//        this.handler = new OAuth2Handler(BeansFactory.getKustvaktContext()
-//                .getPersistenceClient());
-//        this.controller = BeansFactory.getKustvaktContext()
-//                .getAuthenticationManager();
-//        this.crypto = BeansFactory.getKustvaktContext().getEncryption();
-    }
-
-
-//    @POST
-//    @Path("unregister")
-//    @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
-//    public Response unregisterClient (@Context SecurityContext context,
-//            @HeaderParam("Host") String host,
-//            @QueryParam("client_secret") String secret,
-//            @QueryParam("client_id") String client_id) {
-//        ClientInfo info = new ClientInfo(client_id, secret);
-//        info.setUrl(host);
-//        TokenContext ctx = (TokenContext) context.getUserPrincipal();
-//        try {
-//            this.handler.getPersistenceHandler().removeClient(info,
-//                    this.controller.getUser(ctx.getUsername()));
-//        }
-//        catch (KustvaktException e) {
-//            throw kustvaktResponseHandler.throwit(e);
-//        }
-//        return Response.ok().build();
-//    }
-
-
-//    @POST
-//    @Path("register")
-//    @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
-//    public Response registerClient (@Context SecurityContext context,
-//            @HeaderParam("Host") String host,
-//            @QueryParam("redirect_url") String rurl) {
-//        ClientInfo info = new ClientInfo(crypto.createRandomNumber(),
-//                crypto.createToken());
-//        info.setUrl(host);
-//        if (rurl == null)
-//            throw kustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT,
-//                    "Missing parameter!", "redirect_url");
-//        info.setRedirect_uri(rurl);
-//        TokenContext ctx = (TokenContext) context.getUserPrincipal();
-//        String json = "";
-//        try {
-//            User user = this.controller.getUser(ctx.getUsername());
-//            this.handler.getPersistenceHandler().registerClient(info, user);
-//            json = info.toJSON();
-//        }
-//        catch (KustvaktException e) {
-//            throw kustvaktResponseHandler.throwit(e);
-//        }
-//        return Response.ok(json).build();
-//    }
-
-
-    @GET
-    @Path("info")
-    @ResourceFilters({ AuthenticationFilter.class, DemoUserFilter.class, PiwikFilter.class })
-    public Response getStatus (@Context SecurityContext context,
-            @QueryParam("scope") String scopes) {
-        TokenContext ctx = (TokenContext) context.getUserPrincipal();
-        Userdata data;
-        String json= "";
-        try {
-            User user = this.controller.getUser(ctx.getUsername());
-            data = this.controller.getUserData(user, UserDetails.class);
-            Set<String> base_scope = StringUtils.toSet(scopes, " ");
-            base_scope.retainAll(StringUtils.toSet(scopes));
-            scopes = StringUtils.toString(base_scope);
-            json = JsonUtils.toJSON(Scopes.mapScopes(scopes, data));
-        }
-        catch (KustvaktException e) {
-            throw kustvaktResponseHandler.throwit(e);
-        }
-        // json format with scope callback parameter
-        // todo: add other scopes as well!
-        return Response.ok(json).build();
-    }
-
-
-    @GET
-    @Path("authorizations")
-    @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
-    public Response getAuthorizations (@Context SecurityContext context,
-            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
-            @HeaderParam(ContainerRequest.HOST) String host) {
-        // works on all tokens, but access to native apps cannot be revoked!
-        // check secret and id and retrieve access tokens
-        TokenContext ctx = (TokenContext) context.getUserPrincipal();
-        try {
-            User user = this.controller.getUser(ctx.getUsername());
-            Collection auths = this.handler.getPersistenceHandler().getAuthorizedClients(user.getId());
-            if (auths.isEmpty())
-                return Response.noContent().build();
-            return Response.ok(JsonUtils.toJSON(auths)).build();
-        }
-        catch (KustvaktException e) {
-            throw kustvaktResponseHandler.throwit(e);
-        }
-    }
-
-
-    // todo: scopes for access_token are defined here
-    // todo: if user already has an access token registered for client and application, then redirect to token endpoint to retrieve that token
-    // todo: demo account should be disabled for this function --> if authentication failed, client must redirect to login url (little login window)
-    @POST
-    @Path("authorize")
-    @Consumes("application/x-www-form-urlencoded")
-    @Produces("application/json")
-    @ResourceFilters({ BlockingFilter.class })
-    public Response authorize (@Context HttpServletRequest request,
-            @Context SecurityContext context,
-            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
-            @HeaderParam(ContainerRequest.HOST) String host,
-            MultivaluedMap<String, String> form) throws OAuthSystemException,
-            URISyntaxException {
-        // user needs to be authenticated to this service!
-        TokenContext c = (TokenContext) context.getUserPrincipal();
-
-        try {
-            OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(
-                    new FormRequestWrapper(request, form));
-            OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(
-                    new MD5Generator());
-            User user;
-
-            Map<String, Object> attr = new HashMap<>();
-            attr.put(Attributes.HOST, host);
-            attr.put(Attributes.USER_AGENT, agent);
-            attr.put(Attributes.USERNAME, c.getUsername());
-            // also extractable via authorization header
-            attr.put(Attributes.CLIENT_ID, oauthRequest.getClientId());
-            attr.put(Attributes.CLIENT_SECRET, oauthRequest.getClientSecret());
-            StringBuilder scopes = new StringBuilder();
-            for (String scope : oauthRequest.getScopes())
-                scopes.append(scope + " ");
-            attr.put(Attributes.SCOPE, scopes.toString());
-
-            try {
-                user = controller.getUser(c.getUsername());
-                // EM: not in the new DB
-//                Userdata data = controller.getUserData(user, UserDetails.class);
-//                user.addUserData(data);
-            }
-            catch (KustvaktException e) {
-                throw kustvaktResponseHandler.throwit(e);
-            }
-
-            // register response according to response_type
-            String responseType = oauthRequest
-                    .getParam(OAuth.OAUTH_RESPONSE_TYPE);
-
-            final String authorizationCode = oauthIssuerImpl
-                    .authorizationCode();
-            ClientInfo info = this.handler.getPersistenceHandler()
-                    .getClient(oauthRequest.getClientId());
-
-            if (info == null
-                    || !info.getClient_secret().equals(
-                            oauthRequest.getClientSecret())) {
-                OAuthResponse res = OAuthASResponse
-                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
-                        .setError(OAuthError.CodeResponse.UNAUTHORIZED_CLIENT)
-                        .setErrorDescription("Unauthorized client!\n")
-                        .buildJSONMessage();
-                return Response.status(res.getResponseStatus())
-                        .entity(res.getBody()).build();
-            }
-
-            if (!info.getRedirect_uri().contains(oauthRequest.getRedirectURI())) {
-                OAuthResponse res = OAuthASResponse
-                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
-                        .setError(OAuthError.CodeResponse.INVALID_REQUEST)
-                        .setErrorDescription("Unauthorized redirect!\n")
-                        .buildJSONMessage();
-                return Response.status(res.getResponseStatus())
-                        .entity(res.getBody()).build();
-            }
-
-            String accessToken = this.handler.getPersistenceHandler().getToken(
-                    oauthRequest.getClientId(), user.getId());
-
-            //todo: test correct redirect and parameters
-            if (accessToken != null) {
-                // fixme: correct status code?
-                OAuthASResponse.OAuthResponseBuilder builder = OAuthASResponse
-                        .status(HttpServletResponse.SC_FOUND);
-                final OAuthResponse response = builder
-                        .location("/oauth2/token")
-                        .setParam(OAuth.OAUTH_CLIENT_ID,
-                                oauthRequest.getClientId())
-                        .setParam(OAuth.OAUTH_CLIENT_SECRET,
-                                oauthRequest.getClientSecret())
-                        .buildQueryMessage();
-                return Response.status(response.getResponseStatus())
-                        .location(new URI(response.getLocationUri())).build();
-            }
-
-            final OAuthResponse response;
-            String redirectURI = oauthRequest.getRedirectURI();
-            if (OAuthUtils.isEmpty(redirectURI)) {
-                throw new WebApplicationException(
-                        Response.status(HttpServletResponse.SC_BAD_REQUEST)
-                                .entity("OAuth callback url needs to be provided by client!!!\n")
-                                .build());
-            }
-
-            if (responseType.equals(ResponseType.CODE.toString())) {
-                OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse
-                        .authorizationResponse(request,
-                                HttpServletResponse.SC_FOUND);
-                builder.location(redirectURI);
-
-                try {
-                    AuthCodeInfo codeInfo = new AuthCodeInfo(
-                            info.getClient_id(), authorizationCode);
-                    codeInfo.setScopes(StringUtils.toString(
-                            oauthRequest.getScopes(), " "));
-                    this.handler.authorize(codeInfo, user);
-                }
-                catch (KustvaktException e) {
-                    throw kustvaktResponseHandler.throwit(e);
-                }
-                builder.setParam(OAuth.OAUTH_RESPONSE_TYPE,
-                        ResponseType.CODE.toString());
-                builder.setCode(authorizationCode);
-                response = builder.buildBodyMessage();
-
-            }
-            else if (responseType.contains(ResponseType.TOKEN.toString())) {
-                OAuthASResponse.OAuthTokenResponseBuilder builder = OAuthASResponse
-                        .tokenResponse(HttpServletResponse.SC_OK);
-                builder.setParam(OAuth.OAUTH_RESPONSE_TYPE,
-                        ResponseType.TOKEN.toString());
-                builder.location(redirectURI);
-
-                String token = oauthIssuerImpl.accessToken();
-                String refresh = oauthIssuerImpl.refreshToken();
-
-                this.handler.getPersistenceHandler().addToken(token, refresh, user.getId(),
-                        oauthRequest.getClientId(),
-                        StringUtils.toString(oauthRequest.getScopes(), " "),
-                        config.getLongTokenTTL());
-                builder.setAccessToken(token);
-                builder.setRefreshToken(refresh);
-                builder.setExpiresIn(String.valueOf(config.getLongTokenTTL()));
-
-                // skips authorization code type and returns id_token and access token directly
-                if (oauthRequest.getScopes().contains("openid")) {
-                    try {
-                        // EM: MH uses APIAuthentication to create api token
-                        TokenContext new_context = this.controller
-                                .createTokenContext(user, attr, null);
-                        builder.setParam(new_context.getTokenType().displayName(),
-                                new_context.getToken());
-                    }
-                    catch (KustvaktException e) {
-                        throw kustvaktResponseHandler.throwit(e);
-                    }
-                }
-                response = builder.buildBodyMessage();
-            }
-            else {
-                OAuthResponse res = OAuthASResponse
-                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
-                        .setError(
-                                OAuthError.CodeResponse.UNSUPPORTED_RESPONSE_TYPE)
-                        .setErrorDescription("Unsupported Response type!\n")
-                        .buildJSONMessage();
-                return Response.status(res.getResponseStatus())
-                        .entity(res.getBody()).build();
-            }
-            //
-            //            String redirectURI = oauthRequest.getRedirectURI();
-            //
-            //            // enables state parameter to disable cross-site scripting attacks
-            //            final OAuthResponse response = builder.location(redirectURI)
-            //                    .buildQueryMessage();
-            //            if (OAuthUtils.isEmpty(redirectURI)) {
-            //                throw new WebApplicationException(
-            //                        Response.status(HttpServletResponse.SC_BAD_REQUEST)
-            //                                .entity("OAuth callback url needs to be provided by client!!!\n")
-            //                                .build());
-            //            }
-
-            return Response.status(response.getResponseStatus())
-                    .location(new URI(response.getLocationUri())).build();
-        }
-        catch (OAuthProblemException e) {
-            final Response.ResponseBuilder responseBuilder = Response
-                    .status(HttpServletResponse.SC_BAD_REQUEST);
-            String redirectUri = e.getRedirectUri();
-
-            if (OAuthUtils.isEmpty(redirectUri))
-                throw new WebApplicationException(
-                        responseBuilder
-                                .entity("OAuth callback url needs to be provided by client!!!\n")
-                                .build());
-
-            final OAuthResponse response = OAuthASResponse
-                    .errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e)
-                    .location(redirectUri).buildQueryMessage();
-            final URI location = new URI(response.getLocationUri());
-            return responseBuilder.location(location).build();
-        }
-        catch (OAuthSystemException | URISyntaxException | KustvaktException e) {
-            e.printStackTrace();
-        }
-        return Response.noContent().build();
-    }
-
-
-    @POST
-    @Path("revoke")
-    public Response revokeToken (@Context HttpServletRequest request,
-            @Context SecurityContext context,
-            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
-            @HeaderParam(ContainerRequest.HOST) String host)
-            throws OAuthSystemException, URISyntaxException {
-        TokenContext ctx = (TokenContext) context.getUserPrincipal();
-        try {
-
-            if (!this.handler.getPersistenceHandler().revokeToken(ctx.getToken())) {
-                OAuthResponse res = OAuthASResponse
-                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
-                        .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
-                        .setErrorDescription("Invalid access token!\n")
-                        .buildJSONMessage();
-                return Response.status(res.getResponseStatus())
-                        .entity(res.getBody()).build();
-            }
-
-        }
-        catch (KustvaktException e) {
-            e.printStackTrace();
-            // fixme: do something
-            /**
-             * final Response.ResponseBuilder responseBuilder =
-             * Response
-             * .status(HttpServletResponse.SC_FOUND);
-             * String redirectUri = e.getRedirectUri();
-             * 
-             * final OAuthResponse response = OAuthASResponse
-             * .errorResponse(HttpServletResponse.SC_FOUND).error(e)
-             * .location(redirectUri).buildQueryMessage();
-             * final URI location = new
-             * URI(response.getLocationUri());
-             * return responseBuilder.location(location).build();
-             */
-        }
-
-        return Response.ok().build();
-    }
-
-
-    @POST
-    @Consumes("application/x-www-form-urlencoded")
-    @Produces("application/json")
-    @Path("token")
-    public Response requestToken (@Context HttpServletRequest request,
-            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
-            @HeaderParam(ContainerRequest.HOST) String host, MultivaluedMap form)
-            throws OAuthSystemException {
-        boolean openid_valid = false;
-        User user = null;
-        OAuthTokenRequest oauthRequest;
-        OAuthASResponse.OAuthTokenResponseBuilder builder = OAuthASResponse
-                .tokenResponse(HttpServletResponse.SC_OK);
-
-        OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
-        ClientInfo info;
-        try {
-            oauthRequest = new OAuthTokenRequest(new FormRequestWrapper(
-                    request, form));
-
-            if ((info = this.handler.getPersistenceHandler().getClient(oauthRequest.getClientId())) == null) {
-                OAuthResponse res = OAuthASResponse
-                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
-                        .setError(OAuthError.TokenResponse.INVALID_CLIENT)
-                        .setErrorDescription("Invalid client id!\n")
-                        .buildJSONMessage();
-                return Response.status(res.getResponseStatus())
-                        .entity(res.getBody()).build();
-            }
-            else if (!info.getClient_secret().equals(
-                    oauthRequest.getClientSecret())) {
-                OAuthResponse res = OAuthASResponse
-                        .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
-                        .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
-                        .setErrorDescription("Invalid client secret!\n")
-                        .buildJSONMessage();
-                return Response.status(res.getResponseStatus())
-                        .entity(res.getBody()).build();
-            }
-
-            Map<String, Object> attr = new HashMap<>();
-            attr.put(Attributes.HOST, host);
-            attr.put(Attributes.USER_AGENT, agent);
-            attr.put(Attributes.SCOPE,
-                    StringUtils.toString(oauthRequest.getScopes(), " "));
-
-            // support code (for external clients only) and password grant type
-            // password grant at this point is only allowed with trusted clients (korap frontend)
-            if (oauthRequest.getGrantType().equalsIgnoreCase(
-                    GrantType.AUTHORIZATION_CODE.toString())) {
-                // validate auth code
-                AuthCodeInfo codeInfo;
-                try {
-                    //can this be joined with the simple retrieval of access tokens?
-                    // partially yes: auth code can be valid, even though no access token exists
-                    // --> zero result set
-                    codeInfo = this.handler.getAuthorization(oauthRequest
-                            .getCode());
-                    if (codeInfo == null) {
-                        OAuthResponse res = OAuthASResponse
-                                .errorResponse(
-                                        HttpServletResponse.SC_UNAUTHORIZED)
-                                .setError(
-                                        OAuthError.TokenResponse.INVALID_REQUEST)
-                                .setErrorDescription(
-                                        "Invalid authorization code\n")
-                                .buildJSONMessage();
-                        return Response.status(res.getResponseStatus())
-                                .entity(res.getBody()).build();
-                    }
-                    else {
-                        openid_valid = codeInfo.getScopes().contains("openid");
-                        String accessToken = oauthIssuerImpl.accessToken();
-                        String refreshToken = oauthIssuerImpl.refreshToken();
-                        // auth code posesses the user reference. native apps access_tokens are directly associated with the user
-                        this.handler
-                                .addToken(oauthRequest.getCode(), accessToken,
-                                        refreshToken, config.getTokenTTL());
-
-                        builder.setTokenType(TokenType.BEARER.displayName());
-                        builder.setExpiresIn(String.valueOf(config
-                                .getLongTokenTTL()));
-                        builder.setAccessToken(accessToken);
-                        builder.setRefreshToken(refreshToken);
-                    }
-                }
-                catch (KustvaktException e) {
-                    throw kustvaktResponseHandler.throwit(e);
-                }
-                // todo: errors for invalid scopes or different scopes then during authorization request?
-                //todo ??
-                attr.put(Attributes.SCOPE, codeInfo.getScopes());
-
-            }
-            else if (oauthRequest.getGrantType().equalsIgnoreCase(
-                    GrantType.PASSWORD.toString())) {
-                //fixme: via https; as basic auth header and only if client is native!
-                if (!info.isConfidential()) {
-                    OAuthResponse res = OAuthASResponse
-                            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
-                            .setError(
-                                    OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
-                            .setErrorDescription(
-                                    "Grant type not supported for client!\n")
-                            .buildJSONMessage();
-                    return Response.status(res.getResponseStatus())
-                            .entity(res.getBody()).build();
-                }
-
-                openid_valid = true;
-                try {
-                    // EM: MH uses database
-                    user = controller.authenticate(AuthenticationMethod.DATABASE,
-                            oauthRequest.getUsername(),
-                            oauthRequest.getPassword(), attr);
-                }
-                catch (KustvaktException e) {
-                    throw kustvaktResponseHandler.throwit(e);
-                }
-
-                try {
-                    String accessToken = this.handler.getPersistenceHandler().getToken(
-                            oauthRequest.getClientId(), user.getId());
-                    if (accessToken == null) {
-                        String refresh = oauthIssuerImpl.refreshToken();
-                        accessToken = oauthIssuerImpl.accessToken();
-                        this.handler.getPersistenceHandler().addToken(accessToken, refresh, user
-                                .getId(), oauthRequest.getClientId(),
-                                StringUtils.toString(oauthRequest.getScopes(),
-                                        " "), config.getLongTokenTTL());
-                        builder.setRefreshToken(refresh);
-                    }
-                    builder.setTokenType(TokenType.BEARER.displayName());
-                    builder.setExpiresIn(String.valueOf(config
-                            .getLongTokenTTL()));
-                    builder.setAccessToken(accessToken);
-
-                }
-                catch (KustvaktException e) {
-                    throw kustvaktResponseHandler.throwit(e);
-                }
-            }
-
-            if (openid_valid
-                    && oauthRequest.getScopes().contains(
-                            Scopes.Scope.openid.toString())) {
-                try {
-                    if (user == null)
-                        // EM: MH uses database
-                        user = controller.authenticate(AuthenticationMethod.DATABASE,
-                                oauthRequest.getUsername(),
-                                oauthRequest.getPassword(), attr);
-                    Userdata data = controller.getUserData(user,
-                            UserDetails.class);
-                    user.addUserData(data);
-
-                    attr.put(Attributes.CLIENT_SECRET,
-                            oauthRequest.getClientSecret());
-                    TokenContext c = controller.createTokenContext(user, attr,TokenType.ID_TOKEN);
-                            //Attributes.OPENID_AUTHENTICATION);
-                    
-                    builder.setParam(c.getTokenType().displayName(), c.getToken());
-                }
-                catch (KustvaktException e) {
-                    throw kustvaktResponseHandler.throwit(e);
-                }
-            }
-
-            OAuthResponse r = builder.buildJSONMessage();
-            return Response.status(r.getResponseStatus()).entity(r.getBody())
-                    .build();
-        }
-        catch (OAuthProblemException ex) {
-            OAuthResponse r = OAuthResponse.errorResponse(401).error(ex)
-                    .buildJSONMessage();
-            return Response.status(r.getResponseStatus()).entity(r.getBody())
-                    .build();
-        }
-        catch (OAuthSystemException e) {
-            e.printStackTrace();
-            // todo: throw error
-        }
-        return Response.noContent().build();
-    }
-
-}
diff --git a/full/src/main/resources/db/insert/V3.5__insert_oauth2_clients.sql b/full/src/main/resources/db/insert/V3.5__insert_oauth2_clients.sql
index 0d518a3..01e5099 100644
--- a/full/src/main/resources/db/insert/V3.5__insert_oauth2_clients.sql
+++ b/full/src/main/resources/db/insert/V3.5__insert_oauth2_clients.sql
@@ -48,6 +48,6 @@
 --  "This is a test super public client."); 
 
 INSERT INTO oauth2_access_token(token,user_id,created_date, 
-expiry_date, refresh_expiry_date, user_auth_time)
+expiry_date, user_auth_time)
 VALUES("fia0123ikBWn931470H8s5gRqx7Moc4p","marlin","2018-05-30 16:25:50", 
-"2018-05-31 16:25:50", "2018-08-30 16:25:50", "2018-05-30 16:23:10");
+"2018-05-31 16:25:50", "2018-05-30 16:23:10");
diff --git a/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql b/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
index 10e069d..d5688b8 100644
--- a/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
+++ b/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
@@ -20,37 +20,39 @@
 	   REFERENCES oauth2_client_url(url_hashcode)
 );
 
-CREATE TABLE IF NOT EXISTS oauth2_authorization (
-	id INTEGER PRIMARY KEY AUTO_INCREMENT,
-	code VARCHAR(255) NOT NULL,
-	client_id VARCHAR(100) NOT NULL,
-	user_id VARCHAR(100) NOT NULL,
-	redirect_uri TEXT DEFAULT NULL,
-	created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-	expiry_date TIMESTAMP NULL,
-	is_revoked BOOLEAN DEFAULT 0,
-	total_attempts INTEGER DEFAULT 0,
-	user_auth_time TIMESTAMP NULL,
-	nonce TEXT DEFAULT NULL,
-	FOREIGN KEY (client_id)
-	   REFERENCES oauth2_client(id),
-	UNIQUE INDEX authorization_index(code, client_id)
-);
-
 CREATE TABLE IF NOT EXISTS oauth2_access_scope (
 	id VARCHAR(255) PRIMARY KEY NOT NULL
 );
 
-CREATE TABLE IF NOT EXISTS oauth2_authorization_scope (
-	id INTEGER PRIMARY KEY AUTO_INCREMENT,
-	authorization_id INTEGER NOT NULL,
-	scope_id VARCHAR(100) NOT NULL,
-	FOREIGN KEY (authorization_id)
-	   REFERENCES oauth2_authorization(id),
-	FOREIGN KEY (scope_id)
-	   REFERENCES oauth2_access_scope(id),
-	UNIQUE INDEX authorization_scope_index(authorization_id, scope_id)
-);
+-- authorization tables are not needed if using cache 
+
+--CREATE TABLE IF NOT EXISTS oauth2_authorization (
+--	id INTEGER PRIMARY KEY AUTO_INCREMENT,
+--	code VARCHAR(255) NOT NULL,
+--	client_id VARCHAR(100) NOT NULL,
+--	user_id VARCHAR(100) NOT NULL,
+--	redirect_uri TEXT DEFAULT NULL,
+--	created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+--	expiry_date TIMESTAMP NULL,
+--	is_revoked BOOLEAN DEFAULT 0,
+--	total_attempts INTEGER DEFAULT 0,
+--	user_auth_time TIMESTAMP NULL,
+--	nonce TEXT DEFAULT NULL,
+--	FOREIGN KEY (client_id)
+--	   REFERENCES oauth2_client(id),
+--	UNIQUE INDEX authorization_index(code, client_id)
+--);
+--
+--CREATE TABLE IF NOT EXISTS oauth2_authorization_scope (
+--	id INTEGER PRIMARY KEY AUTO_INCREMENT,
+--	authorization_id INTEGER NOT NULL,
+--	scope_id VARCHAR(100) NOT NULL,
+--	FOREIGN KEY (authorization_id)
+--	   REFERENCES oauth2_authorization(id),
+--	FOREIGN KEY (scope_id)
+--	   REFERENCES oauth2_access_scope(id),
+--	UNIQUE INDEX authorization_scope_index(authorization_id, scope_id)
+--);
 
 CREATE TABLE IF NOT EXISTS oauth2_access_token (
 	id INTEGER PRIMARY KEY AUTO_INCREMENT,
@@ -62,10 +64,10 @@
 	is_revoked BOOLEAN DEFAULT 0,
 	user_auth_time TIMESTAMP NULL,
     refresh_token VARCHAR(255) DEFAULT NULL,
-    refresh_expiry_date TIMESTAMP NULL,
-	is_refresh_revoked BOOLEAN DEFAULT 0,
 	FOREIGN KEY (client_id)
-	   REFERENCES oauth2_client(id)
+	   REFERENCES oauth2_client(id),
+	FOREIGN KEY (refresh_token)
+	   REFERENCES oauth2_refresh_token(id)
 );
 
 CREATE TABLE oauth2_access_token_scope (
@@ -74,40 +76,20 @@
 	CONSTRAINT primary_key PRIMARY KEY (token_id, scope_id)
 );
 
---
----- status 1 = valid, 0 = revoked, -1 = disabled
---create table if not exists oauth2_access_token (
---id INTEGER PRIMARY KEY AUTO_INCREMENT,
---access_token VARCHAR(300),
---auth_code VARCHAR(250),
---client_id VARCHAR(100),
---user_id INTEGER,
----- make boolean --
---status INTEGER DEFAULT 1,
----- in case of code authorization, should match auth code scopes!
----- use scopes for levelaccess descriptor level[rw],level[r]
---scopes VARCHAR(350),
---expiration TIMESTAMP,
---FOREIGN KEY (user_id)
---REFERENCES korap_users(id)
---ON DELETE CASCADE,
---FOREIGN KEY (client_id)
---REFERENCES oauth2_client(client_id)
---ON DELETE CASCADE
---);
---
---
----- also scopes?
---create table if not exists oauth2_refresh_token (
---id INTEGER PRIMARY KEY AUTO_INCREMENT,
---client_id VARCHAR(100),
---user_id INTEGER,
---expiration TIMESTAMP,
---scopes VARCHAR(350),
---FOREIGN KEY (user_id)
---REFERENCES korap_users(id)
---ON DELETE CASCADE,
---FOREIGN KEY (client_id)
---REFERENCES oauth2_client(client_id)
---ON DELETE CASCADE
---);
\ No newline at end of file
+CREATE TABLE IF NOT EXISTS oauth2_refresh_token (
+	id INTEGER PRIMARY KEY AUTO_INCREMENT,
+	token VARCHAR(255) NOT NULL,
+	user_id VARCHAR(100) DEFAULT NULL,
+	client_id VARCHAR(100) DEFAULT NULL,
+	created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+	expiry_date TIMESTAMP NULL,
+	is_revoked BOOLEAN DEFAULT 0,
+	FOREIGN KEY (client_id)
+	   REFERENCES oauth2_client(id)
+);
+
+CREATE TABLE oauth2_refresh_token_scope (
+	token_id INTEGER NOT NULL, 
+	scope_id VARCHAR(100) NOT NULL, 
+	CONSTRAINT primary_key PRIMARY KEY (token_id, scope_id)
+);
diff --git a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
index 45fa1f4..f11cac8 100644
--- a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
+++ b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
@@ -22,40 +22,42 @@
 
 CREATE UNIQUE INDEX client_id_index on oauth2_client(id);
 
-CREATE TABLE IF NOT EXISTS oauth2_authorization (
-	id INTEGER PRIMARY KEY AUTOINCREMENT,
-	code VARCHAR(255) NOT NULL,
-	client_id VARCHAR(100) NOT NULL,
-	user_id VARCHAR(100) NOT NULL,
-	redirect_uri TEXT DEFAULT NULL,
-	created_date TIMESTAMP NOT NULL,
-	expiry_date TIMESTAMP NOT NULL,
-	is_revoked BOOLEAN DEFAULT 0,
-	total_attempts INTEGER DEFAULT 0,
-	user_auth_time TIMESTAMP NOT NULL,
-	nonce TEXT DEFAULT NULL,
-	FOREIGN KEY (client_id)
-	   REFERENCES oauth2_client(id)
-);
-
-CREATE UNIQUE INDEX authorization_index on oauth2_authorization(code, client_id);
-
 CREATE TABLE IF NOT EXISTS oauth2_access_scope (
 	id VARCHAR(255) PRIMARY KEY NOT NULL
 );
 
-CREATE TABLE IF NOT EXISTS oauth2_authorization_scope (
-	id INTEGER PRIMARY KEY AUTOINCREMENT,
-	authorization_id INTEGER NOT NULL,
-	scope_id VARCHAR(100) NOT NULL,
-	FOREIGN KEY (authorization_id)
-	   REFERENCES oauth2_authorization(id),
-	FOREIGN KEY (scope_id)
-	   REFERENCES oauth2_access_scope(id)
-);
+-- authorization tables are not needed if using cache
 
-CREATE UNIQUE INDEX authorization_scope_index on 
-	oauth2_authorization_scope(authorization_id, scope_id);
+--CREATE TABLE IF NOT EXISTS oauth2_authorization (
+--	id INTEGER PRIMARY KEY AUTOINCREMENT,
+--	code VARCHAR(255) NOT NULL,
+--	client_id VARCHAR(100) NOT NULL,
+--	user_id VARCHAR(100) NOT NULL,
+--	redirect_uri TEXT DEFAULT NULL,
+--	created_date TIMESTAMP NOT NULL,
+--	expiry_date TIMESTAMP NOT NULL,
+--	is_revoked BOOLEAN DEFAULT 0,
+--	total_attempts INTEGER DEFAULT 0,
+--	user_auth_time TIMESTAMP NOT NULL,
+--	nonce TEXT DEFAULT NULL,
+--	FOREIGN KEY (client_id)
+--	   REFERENCES oauth2_client(id)
+--);
+--
+--CREATE UNIQUE INDEX authorization_index on oauth2_authorization(code, client_id);
+--
+--CREATE TABLE IF NOT EXISTS oauth2_authorization_scope (
+--	id INTEGER PRIMARY KEY AUTOINCREMENT,
+--	authorization_id INTEGER NOT NULL,
+--	scope_id VARCHAR(100) NOT NULL,
+--	FOREIGN KEY (authorization_id)
+--	   REFERENCES oauth2_authorization(id),
+--	FOREIGN KEY (scope_id)
+--	   REFERENCES oauth2_access_scope(id)
+--);
+--
+--CREATE UNIQUE INDEX authorization_scope_index on 
+--	oauth2_authorization_scope(authorization_id, scope_id);
 
 CREATE TABLE IF NOT EXISTS oauth2_access_token (
 	id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -67,10 +69,10 @@
 	is_revoked BOOLEAN DEFAULT 0,
 	user_auth_time TIMESTAMP NOT NULL,
 	refresh_token VARCHAR(255) DEFAULT NULL,
-	refresh_expiry_date TIMESTAMP NOT NULL,
-	is_refresh_revoked BOOLEAN DEFAULT 0,
 	FOREIGN KEY (client_id)
 	   REFERENCES oauth2_client(id)
+	FOREIGN KEY (refresh_token)
+	   REFERENCES oauth2_refresh_token(id)
 );
 
 CREATE TABLE oauth2_access_token_scope (
@@ -78,3 +80,22 @@
 	scope_id VARCHAR(100) NOT NULL, 
 	primary key (token_id, scope_id)
 );
+
+CREATE TABLE IF NOT EXISTS oauth2_refresh_token (
+	id INTEGER PRIMARY KEY AUTOINCREMENT,
+	token VARCHAR(255) NOT NULL,
+	user_id VARCHAR(100) DEFAULT NULL,
+	user_auth_time TIMESTAMP NOT NULL,
+	client_id VARCHAR(100) DEFAULT NULL,
+	created_date TIMESTAMP NOT NULL,
+	expiry_date TIMESTAMP NULL,
+	is_revoked BOOLEAN DEFAULT 0,
+	FOREIGN KEY (client_id)
+	   REFERENCES oauth2_client(id)
+);
+
+CREATE TABLE oauth2_refresh_token_scope (
+	token_id INTEGER NOT NULL, 
+	scope_id VARCHAR(100) NOT NULL, 
+	CONSTRAINT primary_key PRIMARY KEY (token_id, scope_id)
+);
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index f9ce666..ac243e3 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -141,6 +141,10 @@
 				<prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory}</prop>
 				<prop key="hibernate.jdbc.time_zone">${hibernate.jdbc.time_zone}</prop>
 				<!-- <prop key="net.sf.ehcache.configurationResourceName">classpath:ehcache.xml</prop> -->
+				
+				<prop key="connection.autoReconnect">true</prop>
+				<prop key="connection.autoReconnectForPools">true</prop>
+				<prop key="connection.is-connection-validation-required">true</prop>
 			</props>
 		</property>
 	</bean>