Implemented signed OpenID token with default algorithm RSA256.

Change-Id: I5bd5bed5c556e550244e06299bc7e54f53226401
diff --git a/core/Changes b/core/Changes
index 3c77c97..687adb1 100644
--- a/core/Changes
+++ b/core/Changes
@@ -1,3 +1,8 @@
+version 0.60.4
+25/06/2018
+	- added the redirect URI property in KustvaktException (margaretha)
+	- added openid related status codes (margaretha)
+	
 version 0.60.3
 30/05/2018
 	- added parameter checker for collection (margaretha)
diff --git a/core/pom.xml b/core/pom.xml
index 91bb414..de109cc 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>de.ids_mannheim.korap</groupId>
 	<artifactId>Kustvakt-core</artifactId>
-	<version>0.60.3</version>
+	<version>0.60.4</version>
 
 	<properties>
 		<java.version>1.8</java.version>
diff --git a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
index 5c98520..1d1c1ec 100644
--- a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
+++ b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
@@ -4,7 +4,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.URL;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -40,7 +40,6 @@
     private List<String> queryLanguages;
 
     private String serverHost;
-    private URL issuer;
 
     private int maxhits;
     private int returnhits;
@@ -83,7 +82,7 @@
     // deprec?!
     private final BACKENDS DEFAULT_ENGINE = BACKENDS.LUCENE;
 
-	public KustvaktConfiguration (Properties properties) throws IOException {
+	public KustvaktConfiguration (Properties properties) throws IOException, URISyntaxException {
         load(properties);
     }
 	
@@ -93,10 +92,11 @@
      * @param properties
      * @return
      * @throws IOException 
+     * @throws URISyntaxException 
      * @throws KustvaktException 
      */
     protected void load (Properties properties)
-            throws IOException {
+            throws IOException, URISyntaxException {
         baseURL = properties.getProperty("kustvakt.base.url", "/api/*");
         maxhits = new Integer(properties.getProperty("maxhits", "50000"));
         returnhits = new Integer(properties.getProperty("returnhits", "50000"));
@@ -110,11 +110,6 @@
         queryLanguages = new ArrayList<>();
         for (String querylang : qls)
             queryLanguages.add(querylang.trim().toUpperCase());
-        String is = properties.getProperty("security.jwt.issuer", "");
-
-        if (!is.startsWith("http"))
-            is = "http://" + is;
-        issuer = new URL(is);
 
         default_const = properties
                 .getProperty("default.layer.constituent", "mate");
@@ -173,8 +168,9 @@
      * properties can be overloaded after spring init
      * 
      * @param stream
+     * @throws URISyntaxException 
      */
-    public void setPropertiesAsStream (InputStream stream) {
+    public void setPropertiesAsStream (InputStream stream) throws URISyntaxException {
         try {
 
             Properties p = new Properties();
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index ed1a11f..655a98a 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -131,6 +131,9 @@
     public static final int UNSUPPORTED_GRANT_TYPE = 1810;
     public static final int UNSUPPORTED_AUTHENTICATION_METHOD = 1811;
     
+    public static final int ID_TOKEN_CLAIM_ERROR = 1812;
+    public static final int ID_TOKEN_SIGNING_FAILED = 1813;
+    
 
     /**
      * 1900 User account and logins
diff --git a/full/Changes b/full/Changes
index 8a401af..b46a483 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,5 +1,5 @@
 version 0.60.4
-19/06/2018
+25/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)
@@ -7,6 +7,7 @@
     - 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)
+    - implemented signed OpenID token with default algorithm RSA256 (margaretha)
     
 version 0.60.3
 06/06/2018
diff --git a/full/pom.xml b/full/pom.xml
index 3bdf5d4..9ae96d9 100644
--- a/full/pom.xml
+++ b/full/pom.xml
@@ -32,14 +32,15 @@
 		<testResources>
 			<testResource>
 				<directory>src/test/resources</directory>
-				<filtering>true</filtering>
+				<!-- <filtering>true</filtering>
 				<includes>
+					<include>**/*.key</include>
 					<include>**/*.token</include>
 					<include>**/*.xml</include>
 					<include>**/*.conf</include>
 					<include>**/*.info</include>
 					<include>**/*.properties</include>
-				</includes>
+				</includes> -->
 			</testResource>
 			<testResource>
 				<directory>src/main/resources</directory>
@@ -156,7 +157,7 @@
 		<dependency>
 			<groupId>de.ids_mannheim.korap</groupId>
 			<artifactId>Kustvakt-core</artifactId>
-			<version>0.60.3</version>
+			<version>0.60.4</version>
 		</dependency>
 		<!-- LDAP -->
 		<dependency>
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/APIAuthentication.java b/full/src/main/java/de/ids_mannheim/korap/authentication/APIAuthentication.java
index be9d330..e984c11 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/APIAuthentication.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/APIAuthentication.java
@@ -6,8 +6,8 @@
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jwt.SignedJWT;
 
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.config.JWTSigner;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
 import de.ids_mannheim.korap.constant.TokenType;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
@@ -30,7 +30,7 @@
     //private Cache id_tokens = CacheManager.getInstance().getCache("id_tokens");
 
 
-    public APIAuthentication (KustvaktConfiguration config) throws JOSEException {
+    public APIAuthentication (FullConfiguration config) throws JOSEException {
         this.signedToken = new JWTSigner(config.getSharedSecret(),
                 config.getIssuer(), config.getTokenTTL());
     }
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java b/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
index 8258dcf..b76d113 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
@@ -26,15 +26,23 @@
  
 package de.ids_mannheim.korap.authentication;
 
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Properties;
+
 import com.nimbusds.jose.JOSEException;
-import com.unboundid.ldap.sdk.*;
+import com.unboundid.ldap.sdk.Attribute;
+import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.LDAPSearchException;
+import com.unboundid.ldap.sdk.SearchResult;
+import com.unboundid.ldap.sdk.SearchResultEntry;
+import com.unboundid.ldap.sdk.SearchScope;
 
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.constant.TokenType;
 
-import java.io.*;
-import java.util.*;
-
 
 /**
  * LDAP Login Tests
@@ -73,7 +81,7 @@
 	public static final int LDAP_AUTH_RLOCKED	= 3;
 	public static final int LDAP_AUTH_RNOTREG	= 4;
 
-    public LdapAuth3 (KustvaktConfiguration config) throws JOSEException {
+    public LdapAuth3 (FullConfiguration config) throws JOSEException {
         super(config);
     }	
     
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/OpenIDconnectAuthentication.java b/full/src/main/java/de/ids_mannheim/korap/authentication/OpenIDconnectAuthentication.java
index 635b2ca..549b4c7 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/OpenIDconnectAuthentication.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/OpenIDconnectAuthentication.java
@@ -1,9 +1,14 @@
 package de.ids_mannheim.korap.authentication;
 
+import java.text.ParseException;
+import java.util.Map;
+
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jwt.SignedJWT;
+
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.config.JWTSigner;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
 import de.ids_mannheim.korap.constant.TokenType;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
@@ -11,17 +16,9 @@
 import de.ids_mannheim.korap.interfaces.AuthenticationIface;
 import de.ids_mannheim.korap.interfaces.db.PersistenceClient;
 import de.ids_mannheim.korap.security.context.TokenContext;
-import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.user.User;
-import de.ids_mannheim.korap.utils.NamingUtils;
-import de.ids_mannheim.korap.utils.StringUtils;
 import net.sf.ehcache.CacheManager;
 import net.sf.ehcache.Element;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.Cacheable;
-
-import java.text.ParseException;
-import java.util.Map;
 
 /**
  * @author hanl
@@ -30,10 +27,10 @@
 public class OpenIDconnectAuthentication implements AuthenticationIface {
 
     private OAuthDb database;
-    private KustvaktConfiguration config;
+    private FullConfiguration config;
 
 
-    public OpenIDconnectAuthentication (KustvaktConfiguration config,
+    public OpenIDconnectAuthentication (FullConfiguration config,
                                         PersistenceClient client) {
         this.database = new OAuthDb(client);
         this.config = config;
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
index d237494..6c215e0 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
@@ -1,6 +1,19 @@
 package de.ids_mannheim.korap.config;
 
+import java.io.BufferedReader;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -9,6 +22,8 @@
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+import org.apache.commons.codec.binary.Base64;
+
 import de.ids_mannheim.korap.constant.AuthenticationMethod;
 import de.ids_mannheim.korap.interfaces.EncryptionIface;
 
@@ -58,12 +73,45 @@
     private Set<String> clientCredentialsScopes;
     private int maxAuthenticationAttempts;
 
-    public FullConfiguration (Properties properties) throws IOException {
+    private URL issuer;
+    private URI issuerURI;
+    private RSAPrivateKey rsaPrivateKey;
+
+    public FullConfiguration (Properties properties)
+            throws IOException, URISyntaxException, InvalidKeySpecException,
+            NoSuchAlgorithmException {
         super(properties);
+        setRSAPrivateKey();
+    }
+
+    public void setRSAPrivateKey () throws IOException, InvalidKeySpecException,
+            NoSuchAlgorithmException {
+        InputStream is = getClass().getClassLoader()
+                .getResourceAsStream("kustvakt-private.key");
+
+        if (is == null){
+            this.rsaPrivateKey = null;
+            return;
+        }
+            
+        String privateKey = null;
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(is));) {
+            privateKey = reader.readLine();
+        }
+        byte[] decodedKey = Base64.decodeBase64(privateKey);
+        KeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
+        this.rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
+                .generatePrivate(keySpec);
+    }
+
+    public RSAPrivateKey getRSAPrivateKey () {
+        return this.rsaPrivateKey;
     }
 
     @Override
-    public void load (Properties properties) throws IOException {
+    public void load (Properties properties)
+            throws IOException, URISyntaxException {
 
         super.load(properties);
         // EM: regex used for storing vc
@@ -79,6 +127,18 @@
                 properties.getProperty("security.encryption", "BCRYPT")));
 
         setOAuth2Configuration(properties);
+        setOpenIdConfiguration(properties);
+    }
+
+    private void setOpenIdConfiguration (Properties properties)
+            throws URISyntaxException, MalformedURLException {
+        String issuerStr = properties.getProperty("security.jwt.issuer", "");
+
+        if (!issuerStr.startsWith("http")) {
+            issuerStr = "http://" + issuerStr;
+        }
+        setIssuer(new URL(issuerStr));
+        setIssuerURI(issuer.toURI());
     }
 
     private void setOAuth2Configuration (Properties properties) {
@@ -375,4 +435,20 @@
         this.clientCredentialsScopes = clientCredentialsScopes;
     }
 
+    public URL getIssuer () {
+        return issuer;
+    }
+
+    public void setIssuer (URL issuer) {
+        this.issuer = issuer;
+    }
+
+    public URI getIssuerURI () {
+        return issuerURI;
+    }
+
+    public void setIssuerURI (URI issuerURI) {
+        this.issuerURI = issuerURI;
+    }
+
 }
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 8ce9b16..59f7e67 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
@@ -1,25 +1,40 @@
 package de.ids_mannheim.korap.oauth2.openid.service;
 
 import java.net.URI;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.Date;
 import java.util.Set;
 
 import org.springframework.stereotype.Service;
 
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
 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.ParseException;
 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.id.Audience;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.oauth2.sdk.id.Subject;
 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.claims.IDTokenClaimsSet;
 import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -28,7 +43,23 @@
 import de.ids_mannheim.korap.oauth2.entity.AccessScope;
 import de.ids_mannheim.korap.oauth2.entity.Authorization;
 import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
+import de.ids_mannheim.korap.utils.TimeUtils;
 
+/**
+ * ID Tokens MUST be signed using JWS and optionally both signed and
+ * then encrypted using JWS [JWS] and JWE [JWE] respectively.
+ * 
+ * ID Tokens MUST NOT use none as the alg value unless the Response
+ * Type used returns no ID Token from the Authorization Endpoint (such
+ * as when using the Authorization Code Flow) and the Client
+ * explicitly requested the use of none at Registration time.
+ * 
+ * ID Tokens SHOULD NOT use the JWS or JWE x5u, x5c, jku, or jwk
+ * Header Parameter fields.
+ * 
+ * @author margaretha
+ *
+ */
 @Service
 public class OpenIdTokenService extends OAuth2TokenService {
 
@@ -72,7 +103,7 @@
 
 
     private AccessTokenResponse createsAccessTokenResponse (
-            Authorization authorization) {
+            Authorization authorization) throws KustvaktException {
         Set<AccessScope> scopes = authorization.getScopes();
         String[] scopeArray = scopes.stream().map(scope -> scope.toString())
                 .toArray(String[]::new);
@@ -82,9 +113,12 @@
         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";
+            JWTClaimsSet claims = createIdTokenClaims(
+                    authorization.getClientId(), authorization.getUserId());
+            SignedJWT idToken = signIdToken(claims,
+                    // default
+                    new JWSHeader(JWSAlgorithm.RS256),
+                    config.getRSAPrivateKey());
             OIDCTokens tokens =
                     new OIDCTokens(idToken, accessToken, refreshToken);
             return new OIDCTokenResponse(tokens);
@@ -122,4 +156,71 @@
         }
         return new String[] { clientId, clientSecret };
     }
+
+    private JWTClaimsSet createIdTokenClaims (String client_id, String username)
+            throws KustvaktException {
+        // A locally unique and never reassigned identifier within the
+        // Issuer for the End-User
+        Subject sub = new Subject(username);
+        Issuer iss = new Issuer(config.getIssuerURI());
+        Audience aud = new Audience(client_id);
+        ArrayList<Audience> audList = new ArrayList<Audience>(1);
+        audList.add(aud);
+        Date iat = TimeUtils.getNow().toDate();
+        Date exp =
+                TimeUtils.getNow().plusSeconds(config.getTokenTTL()).toDate();
+
+        IDTokenClaimsSet claims =
+                new IDTokenClaimsSet(iss, sub, audList, exp, iat);
+        try {
+            return claims.toJWTClaimsSet();
+        }
+        catch (ParseException e) {
+            throw new KustvaktException(StatusCodes.ID_TOKEN_CLAIM_ERROR,
+                    e.getMessage());
+        }
+    }
+
+    /**
+     * id token should be signed and additionally encrypted
+     * according to keys and algorithms the client specified
+     * during registration
+     * 
+     * Currently supporting only:
+     * default algorithm = RSA SHA-256 (RS256)
+     * 
+     * @param jwtClaimsSet
+     *            id token claim set
+     * @param jwsHeader
+     *            jws header
+     * @param privateKey
+     * 
+     * @return
+     * @throws KustvaktException
+     */
+    private SignedJWT signIdToken (JWTClaimsSet jwtClaimsSet,
+            JWSHeader jwsHeader, PrivateKey privateKey)
+            throws KustvaktException {
+
+        SignedJWT idToken = new SignedJWT(jwsHeader, jwtClaimsSet);
+        JWSSigner signer = null;
+        if (jwsHeader.getAlgorithm().equals(JWSAlgorithm.RS256)) {
+            signer = new RSASSASigner(privateKey);
+        }
+        else {
+            throw new KustvaktException(StatusCodes.ID_TOKEN_SIGNING_FAILED,
+                    "Unsupported algorithm "
+                            + jwsHeader.getAlgorithm().getName());
+        }
+
+        try {
+            idToken.sign(signer);
+        }
+        catch (JOSEException e) {
+            throw new KustvaktException(StatusCodes.ID_TOKEN_SIGNING_FAILED,
+                    e.getMessage());
+        }
+
+        return idToken;
+    }
 }
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 b103e33..f1131b0 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
@@ -66,13 +66,8 @@
             throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
                     "response_type is missing.", OAuth2Error.INVALID_REQUEST);
         }
-        else if (responseType.equals("token")) {
-            throw new KustvaktException(StatusCodes.NOT_SUPPORTED,
-                    "response_type token is not supported.",
-                    OAuth2Error.INVALID_REQUEST);
-        }
         else if (!responseType.equals("code")) {
-            throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
+            throw new KustvaktException(StatusCodes.NOT_SUPPORTED,
                     "unsupported response_type: " + responseType,
                     OAuth2Error.INVALID_REQUEST);
         }
diff --git a/full/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java b/full/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java
index 82bab07..962a14f 100644
--- a/full/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java
@@ -12,7 +12,7 @@
 import com.nimbusds.jose.JOSEException;
 
 import de.ids_mannheim.korap.config.Attributes;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.config.SpringJerseyTest;
 import de.ids_mannheim.korap.constant.TokenType;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -23,7 +23,7 @@
 public class APIAuthenticationTest extends SpringJerseyTest {
 
     @Autowired
-    private KustvaktConfiguration config;
+    private FullConfiguration config;
 
     @Test
     public void testCreateGetTokenContext () throws KustvaktException,
diff --git a/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java b/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
index ac3a131..ed263c0 100644
--- a/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
@@ -6,6 +6,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URISyntaxException;
 import java.util.Map;
 import java.util.Properties;
 
@@ -59,7 +60,7 @@
 
     @Test(expected = KustvaktException.class)
     @Ignore
-    public void testBeanOverrideInjection () throws KustvaktException {
+    public void testBeanOverrideInjection () throws KustvaktException, URISyntaxException {
         helper().getContext()
                 .getConfiguration()
                 .setPropertiesAsStream(
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 c4156d6..6bd081a 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
@@ -2,14 +2,29 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.net.URI;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.text.ParseException;
+import java.util.Date;
 
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 
+import org.apache.commons.codec.binary.Base64;
 import org.apache.http.entity.ContentType;
 import org.apache.oltu.oauth2.common.message.types.TokenType;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.MultiValueMap;
@@ -17,6 +32,10 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.SignedJWT;
 import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.UniformInterfaceException;
@@ -24,6 +43,7 @@
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.config.SpringJerseyTest;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
@@ -33,24 +53,40 @@
 
     @Autowired
     private HttpAuthorizationHandler handler;
+    @Autowired
+    private FullConfiguration config;
 
     private String redirectUri =
             "https://korap.ids-mannheim.de/confidential/redirect";
+    private String username = "dory";
+
+    private static String publicKey;
+
+    @BeforeClass
+    public static void init () throws IOException {
+        InputStream is = OAuth2OpenIdControllerTest.class.getClassLoader()
+                .getResourceAsStream("kustvakt-public.key");
+
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(is));) {
+            publicKey = reader.readLine();
+        }
+    }
 
     private ClientResponse sendAuthorizationRequest (
             MultivaluedMap<String, String> form) throws KustvaktException {
         return resource().path("oauth2").path("openid").path("authorize")
                 .header(Attributes.AUTHORIZATION,
-                        handler.createBasicAuthorizationHeaderValue("dory",
+                        handler.createBasicAuthorizationHeaderValue(username,
                                 "password"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(HttpHeaders.CONTENT_TYPE,
                         ContentType.APPLICATION_FORM_URLENCODED)
                 .entity(form).post(ClientResponse.class);
     }
-    
-    private ClientResponse sendTokenRequest (MultivaluedMap<String, String> form)
-            throws KustvaktException {
+
+    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,
@@ -165,33 +201,86 @@
                 node.at("/error_description").asText());
     }
 
-    @Test
-    public void testRequestAuthorizationCodeUnsupportedResponseType ()
+    private void testRequestAuthorizationCodeUnsupportedResponseType (
+            MultivaluedMap<String, String> form, String type)
             throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("scope", "openid");
-        form.add("redirect_uri", redirectUri);
-        // we don't support implicit grant
-        form.add("response_type", "id_token token");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
-        form.add("nonce", "nonce");
 
         ClientResponse response = sendAuthorizationRequest(form);
+        System.out.println(response.getEntity(String.class));
         URI location = response.getLocation();
-        assertEquals(MediaType.APPLICATION_FORM_URLENCODED, response.getType().toString());
+        assertEquals(MediaType.APPLICATION_FORM_URLENCODED,
+                response.getType().toString());
 
         MultiValueMap<String, String> params =
                 UriComponentsBuilder.fromUri(location).build().getQueryParams();
         assertEquals("invalid_request", params.getFirst("error"));
-        assertEquals("unsupported+response_type%3A+id_token",
+        assertEquals("unsupported+response_type%3A+" + type,
                 params.getFirst("error_description"));
     }
-    
+
+    /**
+     * We don't support implicit grant. Implicit grant allows
+     * response_type:
+     * <ul>
+     * <li>id_token</li>
+     * <li>id_token token</li>
+     * </ul>
+     * 
+     * @throws KustvaktException
+     */
     @Test
-    public void testRequestAccessToken () throws KustvaktException {
+    public void testRequestAuthorizationCodeUnsupportedImplicitFlow ()
+            throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("scope", "openid");
+        form.add("redirect_uri", redirectUri);
+        form.add("response_type", "id_token");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("nonce", "nonce");
+
+        testRequestAuthorizationCodeUnsupportedResponseType(form, "id_token");
+
+        form.remove("response_type");
+        form.add("response_type", "id_token token");
+        testRequestAuthorizationCodeUnsupportedResponseType(form, "id_token");
+    }
+
+    /**
+     * Hybrid flow is not supported. Hybrid flow allows
+     * response_type:
+     * <ul>
+     * <li>code id_token</li>
+     * <li>code token</li>
+     * <li>code id_token token</li>
+     * </ul>
+     * 
+     * @throws KustvaktException
+     */
+
+    @Test
+    public void testRequestAuthorizationCodeUnsupportedHybridFlow ()
+            throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("scope", "openid");
+        form.add("redirect_uri", redirectUri);
+        form.add("response_type", "code id_token");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("nonce", "nonce");
+        testRequestAuthorizationCodeUnsupportedResponseType(form, "id_token");
+
+        form.remove("response_type");
+        form.add("response_type", "code token");
+        testRequestAuthorizationCodeUnsupportedResponseType(form, "token");
+    }
+
+    @Test
+    public void testRequestAccessToken ()
+            throws KustvaktException, ParseException, InvalidKeySpecException,
+            NoSuchAlgorithmException, JOSEException {
+        String client_id = "fCBbQkAyYzI4NzUxMg";
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("response_type", "code");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_id", client_id);
         form.add("redirect_uri", redirectUri);
         form.add("scope", "openid");
         form.add("state", "thisIsMyState");
@@ -201,17 +290,17 @@
         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_id", client_id);
         tokenForm.add("client_secret", "secret");
         tokenForm.add("code", code);
-        
+
         ClientResponse tokenResponse = sendTokenRequest(tokenForm);
         String entity = tokenResponse.getEntity(String.class);
-//        System.out.println(entity);
+        // System.out.println(entity);
 
         JsonNode node = JsonUtils.readTree(entity);
         assertNotNull(node.at("/access_token").asText());
@@ -219,7 +308,30 @@
         assertEquals(TokenType.BEARER.toString(),
                 node.at("/token_type").asText());
         assertNotNull(node.at("/expires_in").asText());
-        assertNotNull(node.at("/id_token").asText());
+        String id_token = node.at("/id_token").asText();
+        assertNotNull(id_token);
 
+        verifyingIdToken(id_token, username, client_id);
+    }
+
+    private void verifyingIdToken (String id_token, String username,
+            String client_id) throws ParseException, InvalidKeySpecException,
+            NoSuchAlgorithmException, JOSEException {
+        byte[] decodedPuk = Base64.decodeBase64(publicKey);
+        KeySpec keySpec = new X509EncodedKeySpec(decodedPuk);
+        RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
+                .generatePublic(keySpec);
+
+        SignedJWT signedJWT = SignedJWT.parse(id_token);
+        JWSVerifier verifier = new RSASSAVerifier(publicKey);
+        assertTrue(signedJWT.verify(verifier));
+
+        assertEquals(client_id,
+                signedJWT.getJWTClaimsSet().getAudience().get(0));
+        assertEquals(username, signedJWT.getJWTClaimsSet().getSubject());
+        assertEquals(config.getIssuerURI().toString(),
+                signedJWT.getJWTClaimsSet().getIssuer());
+        assertTrue(new Date()
+                .before(signedJWT.getJWTClaimsSet().getExpirationTime()));
     }
 }
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java
index 68f1b55..f791244 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java
@@ -29,6 +29,7 @@
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.BeansFactory;
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.config.JWTSigner;
 import de.ids_mannheim.korap.config.TestHelper;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -49,7 +50,8 @@
 
     @Autowired
     HttpAuthorizationHandler handler;
-    
+    @Autowired
+    FullConfiguration config;
 	private static String[] credentials;
 
 	@Override
@@ -201,8 +203,8 @@
 		String token = node.path("token").asText();
 
 		JWTSigner sign = new JWTSigner(BeansFactory.getKustvaktContext().getConfiguration().getSharedSecret(),
-				BeansFactory.getKustvaktContext().getConfiguration().getIssuer(), -1);
-
+				config.getIssuer(), -1);
+		        //BeansFactory.getKustvaktContext().getConfiguration().getIssuer(), -1);
 		SignedJWT jwt = sign.verifyToken(token);
 
 		while (true) {
diff --git a/full/src/test/resources/kustvakt-private.key b/full/src/test/resources/kustvakt-private.key
new file mode 100644
index 0000000..d419b89
--- /dev/null
+++ b/full/src/test/resources/kustvakt-private.key
@@ -0,0 +1 @@
+MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAIlVfcPe+PXGph6BX1zU9HQ1kSt0lz2LIGAB+krHcj5oaWeS/4xicvmmGRE5MeJQEMIcijl3OXjdZR7lK1dxn1UUHuZa3ijMnMgDcQz9BuGg+49R5KdSkkMwlVW5Bdt08TmU9teFdQpg+7bsVGKpSuW6yE6wkgo+Wwufw23ULNkjAgMBAAECgYBVq8o3zTm7gH+SmhwWOhaBBAWaeTH7x3WbzsAHtCG1gsb2QMJAHg4hZJdQokBXMKEzpkAoFxL4Lgxt2IJQG2ZL778uiQiy+xHI8VTXBNXmdo+F3hlNzEmJySSSCxYefSSv+DN/yBrOx0heGXR3vbefXey4a6q8RhthCuRfpHmqmQJBALyFdf4Oj4rozi/KI8yiD71+NNR7hHMtepn3YyY0zBXxk2YEwpcPkzBhdDiL6fYJjjoGFnqKLNqlgO8gHx+ET70CQQC6faQiLjUp50wbEAZqLY7Q353k2qTdAX8W9L2lF/79GEA+EJumQ2iWOu9qYqQuSMSKwheY6mdOVWj8yOMiu2pfAkEAll0cr3aNpw3o5tUjmKPqSgnPuWqLShKMJyHaQy75WMdF+ajyS+pwS7ZvLGrsQQF+H2mbpEFxZTN8kz3blRfDQQJBAKADPdm2HBegRkTSMy7XeDrwI+JBWEPpDMr9o9sMA9XWAQk/5s15+Tstxk9Z49VyynDkqKqkNY+Y6UQ8eedLN7ECQQCDiAsbwOe79EpsHdQBOZeNvpWu1x1TxieN0nCAa/zQz8qupHkL/u8VI8csz+s3qOcgxpJqsn58G8eb9Jmk9fGY
\ No newline at end of file
diff --git a/full/src/test/resources/kustvakt-public.key b/full/src/test/resources/kustvakt-public.key
new file mode 100644
index 0000000..087d6f7
--- /dev/null
+++ b/full/src/test/resources/kustvakt-public.key
@@ -0,0 +1 @@
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJVX3D3vj1xqYegV9c1PR0NZErdJc9iyBgAfpKx3I+aGlnkv+MYnL5phkROTHiUBDCHIo5dzl43WUe5StXcZ9VFB7mWt4ozJzIA3EM/QbhoPuPUeSnUpJDMJVVuQXbdPE5lPbXhXUKYPu27FRiqUrlushOsJIKPlsLn8Nt1CzZIwIDAQAB
\ No newline at end of file
diff --git a/lite/pom.xml b/lite/pom.xml
index 9636eba..a1e95b9 100644
--- a/lite/pom.xml
+++ b/lite/pom.xml
@@ -147,7 +147,7 @@
 		<dependency>
 			<groupId>de.ids_mannheim.korap</groupId>
 			<artifactId>Kustvakt-core</artifactId>
-			<version>0.60.3</version>
+			<version>0.60.4</version>
 		</dependency>
 		
 		<!-- Spring -->