@Override
  public OAuth2AccessTokenEntity createIdToken(
      ClientDetailsEntity client,
      OAuth2Request request,
      Date issueTime,
      String sub,
      OAuth2AccessTokenEntity accessToken) {

    JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();

    if (client.getIdTokenSignedResponseAlg() != null) {
      signingAlg = client.getIdTokenSignedResponseAlg();
    }

    OAuth2AccessTokenEntity idTokenEntity = new OAuth2AccessTokenEntity();
    JWTClaimsSet.Builder idClaims = new JWTClaimsSet.Builder();

    // if the auth time claim was explicitly requested OR if the client always wants the auth time,
    // put it in
    if (request.getExtensions().containsKey("max_age")
        || (request
            .getExtensions()
            .containsKey(
                "idtoken")) // TODO: parse the ID Token claims (#473) -- for now assume it could be
                            // in there
        || (client.getRequireAuthTime() != null && client.getRequireAuthTime())) {

      if (request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP) != null) {

        Long authTimestamp =
            Long.parseLong(
                (String) request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP));
        if (authTimestamp != null) {
          idClaims.claim("auth_time", authTimestamp / 1000L);
        }
      } else {
        // we couldn't find the timestamp!
        logger.warn(
            "Unable to find authentication timestamp! There is likely something wrong with the configuration.");
      }
    }

    idClaims.issueTime(issueTime);

    if (client.getIdTokenValiditySeconds() != null) {
      Date expiration =
          new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L));
      idClaims.expirationTime(expiration);
      idTokenEntity.setExpiration(expiration);
    }

    idClaims.issuer(configBean.getIssuer());
    idClaims.subject(sub);
    idClaims.audience(Lists.newArrayList(client.getClientId()));
    idClaims.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it

    String nonce = (String) request.getExtensions().get("nonce");
    if (!Strings.isNullOrEmpty(nonce)) {
      idClaims.claim("nonce", nonce);
    }

    Set<String> responseTypes = request.getResponseTypes();

    if (responseTypes.contains("token")) {
      // calculate the token hash
      Base64URL at_hash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken);
      idClaims.claim("at_hash", at_hash);
    }

    if (client.getIdTokenEncryptedResponseAlg() != null
        && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
        && client.getIdTokenEncryptedResponseEnc() != null
        && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
        && (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) {

      JWTEncryptionAndDecryptionService encrypter = encrypters.getEncrypter(client);

      if (encrypter != null) {

        EncryptedJWT idToken =
            new EncryptedJWT(
                new JWEHeader(
                    client.getIdTokenEncryptedResponseAlg(),
                    client.getIdTokenEncryptedResponseEnc()),
                idClaims.build());

        encrypter.encryptJwt(idToken);

        idTokenEntity.setJwt(idToken);

      } else {
        logger.error("Couldn't find encrypter for client: " + client.getClientId());
      }

    } else {

      JWT idToken;

      if (signingAlg.equals(Algorithm.NONE)) {
        // unsigned ID token
        idToken = new PlainJWT(idClaims.build());

      } else {

        // signed ID token

        if (signingAlg.equals(JWSAlgorithm.HS256)
            || signingAlg.equals(JWSAlgorithm.HS384)
            || signingAlg.equals(JWSAlgorithm.HS512)) {

          JWSHeader header =
              new JWSHeader(
                  signingAlg,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  jwtService.getDefaultSignerKeyId(),
                  null,
                  null);
          idToken = new SignedJWT(header, idClaims.build());

          JWTSigningAndValidationService signer =
              symmetricCacheService.getSymmetricValidtor(client);

          // sign it with the client's secret
          signer.signJwt((SignedJWT) idToken);
        } else {
          idClaims.claim("kid", jwtService.getDefaultSignerKeyId());

          JWSHeader header =
              new JWSHeader(
                  signingAlg,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  jwtService.getDefaultSignerKeyId(),
                  null,
                  null);

          idToken = new SignedJWT(header, idClaims.build());

          // sign it with the server's key
          jwtService.signJwt((SignedJWT) idToken);
        }
      }

      idTokenEntity.setJwt(idToken);
    }

    idTokenEntity.setAuthenticationHolder(accessToken.getAuthenticationHolder());

    // create a scope set with just the special "id-token" scope
    // Set<String> idScopes = new HashSet<String>(token.getScope()); // this would copy the original
    // token's scopes in, we don't really want that
    Set<String> idScopes = Sets.newHashSet(SystemScopeService.ID_TOKEN_SCOPE);
    idTokenEntity.setScope(idScopes);

    idTokenEntity.setClient(accessToken.getClient());

    return idTokenEntity;
  }