keycloak-uncached

KEYCLOAK-3058 Support for validation of "aud" in adapters

9/14/2018 4:45:00 AM

Changes

adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java 58(+0 -58)

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
index 701cad8..8192e71 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
@@ -482,6 +482,16 @@ public class AdapterDeploymentContext {
         public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
             delegate.setPublicKeyCacheTtl(publicKeyCacheTtl);
         }
+
+        @Override
+        public boolean isVerifyTokenAudience() {
+            return delegate.isVerifyTokenAudience();
+        }
+
+        @Override
+        public void setVerifyTokenAudience(boolean verifyTokenAudience) {
+            delegate.setVerifyTokenAudience(verifyTokenAudience);
+        }
     }
 
     protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
index e7211a9..07409f3 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
@@ -27,7 +27,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.OIDCHttpFacade;
-import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.authorization.client.AuthorizationDeniedException;
 import org.keycloak.authorization.client.AuthzClient;
@@ -171,7 +171,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
             }
 
             if (authzResponse != null) {
-                return AdapterRSATokenVerifier.verifyToken(authzResponse.getToken(), deployment);
+                return AdapterTokenVerifier.verifyToken(authzResponse.getToken(), deployment);
             }
         } catch (AuthorizationDeniedException ignore) {
             LOGGER.debug("Authorization denied", ignore);
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
index 966f8c0..eeda3e3 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
@@ -18,7 +18,7 @@
 package org.keycloak.adapters;
 
 import org.jboss.logging.Logger;
-import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.adapters.spi.AuthChallenge;
 import org.keycloak.adapters.spi.AuthOutcome;
 import org.keycloak.adapters.spi.HttpFacade;
@@ -96,7 +96,7 @@ public class BearerTokenRequestAuthenticator {
             }
         }
         try {
-            token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
+            token = AdapterTokenVerifier.verifyToken(tokenString, deployment);
         } catch (VerificationException e) {
             log.error("Failed to verify token", e);
             challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
index 7d67dd6..1665c92 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
@@ -19,7 +19,8 @@ package org.keycloak.adapters;
 
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakPrincipal;
-import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
+import org.keycloak.TokenVerifier;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.KeycloakUriBuilder;
@@ -71,7 +72,11 @@ public class CookieTokenStore {
 
         try {
             // Skip check if token is active now. It's supposed to be done later by the caller
-            AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true);
+            TokenVerifier<AccessToken> tokenVerifier = AdapterTokenVerifier.createVerifier(accessTokenString, deployment, true, AccessToken.class)
+                    .checkActive(false)
+                    .verify();
+            AccessToken accessToken = tokenVerifier.getToken();
+
             IDToken idToken;
             if (idTokenString != null && idTokenString.length() > 0) {
                 try {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
index 28ef006..1c27d6e 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
@@ -23,7 +23,7 @@ import org.keycloak.adapters.AdapterUtils;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
-import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.FindFile;
 import org.keycloak.common.util.reflections.Reflections;
@@ -202,8 +202,16 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
 
 
     protected Auth bearerAuth(String tokenString) throws VerificationException {
-        AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
+        AccessToken token = AdapterTokenVerifier.verifyToken(tokenString, deployment);
+        return postTokenVerification(tokenString, token);
+    }
+
 
+    /**
+     * Called after accessToken was verified (including signature, expiration etc)
+     *
+     */
+    protected Auth postTokenVerification(String tokenString, AccessToken token) {
         boolean verifyCaller;
         if (deployment.isUseResourceRoleMappings()) {
             verifyCaller = token.isVerifyCaller(deployment.getResourceName());
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java
index fffd39b..b39ff85 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java
@@ -27,6 +27,7 @@ import org.apache.http.message.BasicNameValuePair;
 import org.jboss.logging.Logger;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.KeycloakUriBuilder;
 import org.keycloak.constants.ServiceUrlConstants;
@@ -56,11 +57,15 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
 
     private static final Logger log = Logger.getLogger(DirectAccessGrantsLoginModule.class);
 
+    public static final String SCOPE_OPTION = "scope";
+
     private String refreshToken;
+    private String scope;
 
     @Override
     public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
         super.initialize(subject, callbackHandler, sharedState, options);
+        this.scope = (String)options.get(SCOPE_OPTION);
 
         // This is used just for logout
         Iterator<RefreshTokenHolder> iterator = subject.getPrivateCredentials(RefreshTokenHolder.class).iterator();
@@ -89,6 +94,10 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
         formparams.add(new BasicNameValuePair("username", username));
         formparams.add(new BasicNameValuePair("password", password));
 
+        if (scope != null) {
+            formparams.add(new BasicNameValuePair(OAuth2Constants.SCOPE, scope));
+        }
+
         ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
 
         UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
@@ -121,7 +130,8 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
         // refreshToken will be saved to privateCreds of Subject for now
         refreshToken = tokenResponse.getRefreshToken();
 
-        return bearerAuth(tokenResponse.getToken());
+        AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenResponse.getToken(), tokenResponse.getIdToken(), deployment);
+        return postTokenVerification(tokenResponse.getToken(), tokens.getAccessToken());
     }
 
     @Override
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index e1fce5b..89f38cc 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -92,10 +92,11 @@ public class KeycloakDeployment {
     // https://tools.ietf.org/html/rfc7636
     protected boolean pkce = false;
     protected boolean ignoreOAuthQueryParameter;
-    
+
     protected Map<String, String> redirectRewriteRules;
 
     protected boolean delegateBearerErrorResponseSending = false;
+    protected boolean verifyTokenAudience = false;
 
     public KeycloakDeployment() {
     }
@@ -477,4 +478,12 @@ public class KeycloakDeployment {
     public void setDelegateBearerErrorResponseSending(boolean delegateBearerErrorResponseSending) {
         this.delegateBearerErrorResponseSending = delegateBearerErrorResponseSending;
     }
+
+    public boolean isVerifyTokenAudience() {
+        return verifyTokenAudience;
+    }
+
+    public void setVerifyTokenAudience(boolean verifyTokenAudience) {
+        this.verifyTokenAudience = verifyTokenAudience;
+    }
 }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index fa7da95..936c065 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -122,6 +122,7 @@ public class KeycloakDeploymentBuilder {
         deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
         deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
         deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
+        deployment.setVerifyTokenAudience(adapterConfig.isVerifyTokenAudience());
 
         if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
             throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index 2538edb..fb36c4e 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -19,7 +19,7 @@ package org.keycloak.adapters;
 
 import org.jboss.logging.Logger;
 import org.keycloak.OAuth2Constants;
-import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.adapters.spi.AdapterSessionStore;
 import org.keycloak.adapters.spi.AuthChallenge;
 import org.keycloak.adapters.spi.AuthOutcome;
@@ -40,7 +40,6 @@ import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Map;
-import java.util.logging.Level;
 
 
 /**
@@ -359,15 +358,9 @@ public class OAuthRequestAuthenticator {
         }
 
         try {
-            token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
-            if (idTokenString != null) {
-                try {
-                    JWSInput input = new JWSInput(idTokenString);
-                    idToken = input.readJsonContent(IDToken.class);
-                } catch (JWSInputException e) {
-                    throw new VerificationException();
-                }
-            }
+            AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenString, idTokenString, deployment);
+            token = tokens.getAccessToken();
+            idToken = tokens.getIdToken();
             log.debug("Token Verification succeeded!");
         } catch (VerificationException e) {
             log.error("failed verification of token: " + e.getMessage());
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index b4d017b..0e8b825 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -20,30 +20,27 @@ package org.keycloak.adapters;
 import java.security.PublicKey;
 
 import org.jboss.logging.Logger;
+import org.keycloak.TokenVerifier;
 import org.keycloak.adapters.authentication.ClientCredentialsProvider;
 import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
-import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.adapters.spi.UserSessionManagement;
+import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.StreamUtil;
 import org.keycloak.jose.jwk.JSONWebKeySet;
 import org.keycloak.jose.jwk.JWK;
 import org.keycloak.jose.jwk.JWKBuilder;
-import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.representations.JsonWebToken;
 import org.keycloak.representations.VersionRepresentation;
 import org.keycloak.constants.AdapterConstants;
 import org.keycloak.jose.jws.JWSInput;
-import org.keycloak.jose.jws.JWSInputException;
-import org.keycloak.jose.jws.crypto.RSAProvider;
-import org.keycloak.representations.VersionRepresentation;
 import org.keycloak.representations.adapters.action.AdminAction;
 import org.keycloak.representations.adapters.action.LogoutAction;
 import org.keycloak.representations.adapters.action.PushNotBeforeAction;
 import org.keycloak.representations.adapters.action.TestAvailabilityAction;
 import org.keycloak.util.JsonSerialization;
 
-import java.security.PublicKey;
-
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
@@ -213,17 +210,19 @@ public class PreAuthActionsHandler {
         }
 
         try {
-            JWSInput input = new JWSInput(token);
-            PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment);
-            if (RSAProvider.verify(input, publicKey)) {
-                return input;
+            // Check just signature. Other things checked in validateAction
+            TokenVerifier tokenVerifier = AdapterTokenVerifier.createVerifier(token, deployment, false, JsonWebToken.class);
+            tokenVerifier.verify();
+            return new JWSInput(token);
+        } catch (VerificationException ignore) {
+            log.warn("admin request failed, unable to verify token: "  + ignore.getMessage());
+            if (log.isDebugEnabled()) {
+                log.debug(ignore.getMessage(), ignore);
             }
-        } catch (JWSInputException ignore) {
-        }
 
-        log.warn("admin request failed, unable to verify token");
-        facade.getResponse().sendError(403, "no token");
-        return null;
+            facade.getResponse().sendError(403, "token failed verification");
+            return null;
+        }
     }
 
 
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
index fa1cefd..81d0966 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
@@ -20,7 +20,7 @@ package org.keycloak.adapters;
 import org.jboss.logging.Logger;
 import org.keycloak.AuthorizationContext;
 import org.keycloak.KeycloakSecurityContext;
-import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.Time;
 import org.keycloak.representations.AccessToken;
@@ -130,7 +130,8 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
         String tokenString = response.getToken();
         AccessToken token = null;
         try {
-            token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
+            AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenString, response.getIdToken(), deployment);
+            token = tokens.getAccessToken();
             log.debug("Token Verification succeeded!");
         } catch (VerificationException e) {
             log.error("failed verification of token");
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterTokenVerifier.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterTokenVerifier.java
new file mode 100644
index 0000000..ab5b69d
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterTokenVerifier.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.adapters.rotation;
+
+import org.jboss.logging.Logger;
+import org.keycloak.TokenVerifier;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.common.VerificationException;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+import org.keycloak.representations.JsonWebToken;
+
+import java.security.PublicKey;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AdapterTokenVerifier {
+
+    private static final Logger log = Logger.getLogger(AdapterTokenVerifier.class);
+
+
+    /**
+     * Verifies bearer token. Typically called when bearer token (access token) is sent to the service, which wants to verify it. Hence it also checks the audience in the token.
+     *
+     * @param tokenString
+     * @param deployment
+     * @return
+     * @throws VerificationException
+     */
+    public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
+        TokenVerifier<AccessToken> tokenVerifier = createVerifier(tokenString, deployment, true, AccessToken.class);
+
+        // Verify audience of bearer-token
+        if (deployment.isVerifyTokenAudience()) {
+            tokenVerifier.audience(deployment.getResourceName());
+        }
+
+        return tokenVerifier.verify().getToken();
+    }
+
+
+    /**
+     * Verify access token and ID token. Typically called after successful tokenResponse is received from Keycloak
+     *
+     * @param accessTokenString
+     * @param idTokenString
+     * @param deployment
+     * @return verified and parsed accessToken and idToken
+     * @throws VerificationException
+     */
+    public static VerifiedTokens verifyTokens(String accessTokenString, String idTokenString, KeycloakDeployment deployment) throws VerificationException {
+        // Adapters currently do most of the checks including signature etc on the access token
+        TokenVerifier<AccessToken> tokenVerifier = createVerifier(accessTokenString, deployment, true, AccessToken.class);
+        AccessToken accessToken = tokenVerifier.verify().getToken();
+
+        if (idTokenString != null) {
+            // Don't verify signature again on IDToken
+            IDToken idToken = TokenVerifier.create(idTokenString, IDToken.class).getToken();
+            TokenVerifier<IDToken> idTokenVerifier = TokenVerifier.createWithoutSignature(idToken);
+
+            // Always verify audience on IDToken
+            idTokenVerifier.audience(deployment.getResourceName());
+
+            idTokenVerifier.verify();
+            return new VerifiedTokens(accessToken, idToken);
+        } else {
+            return new VerifiedTokens(accessToken, null);
+        }
+    }
+
+
+    /**
+     * Creates verifier, initializes it from the KeycloakDeployment and adds the publicKey and some default basic checks (activeness and tokenType). Useful if caller wants to add/remove/update
+     * some checks
+     *
+     * @param tokenString
+     * @param deployment
+     * @param withDefaultChecks
+     * @param tokenClass
+     * @param <T>
+     * @return tokenVerifier
+     * @throws VerificationException
+     */
+    public static <T extends JsonWebToken> TokenVerifier<T> createVerifier(String tokenString, KeycloakDeployment deployment, boolean withDefaultChecks, Class<T> tokenClass) throws VerificationException {
+        TokenVerifier<T> tokenVerifier = TokenVerifier.create(tokenString, tokenClass);
+
+        if (withDefaultChecks) {
+            tokenVerifier
+                    .withDefaultChecks()
+                    .realmUrl(deployment.getRealmInfoUrl());
+        }
+
+        String kid = tokenVerifier.getHeader().getKeyId();
+        PublicKey publicKey = getPublicKey(kid, deployment);
+        tokenVerifier.publicKey(publicKey);
+
+        return tokenVerifier;
+    }
+
+
+    private static PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
+        PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
+
+        PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
+        if (publicKey == null) {
+            log.errorf("Didn't find publicKey for kid: %s", kid);
+            throw new VerificationException("Didn't find publicKey for specified kid");
+        }
+
+        return publicKey;
+    }
+
+
+    public static class VerifiedTokens {
+
+        private final AccessToken accessToken;
+        private final IDToken idToken;
+
+        public VerifiedTokens(AccessToken accessToken, IDToken idToken) {
+            this.accessToken = accessToken;
+            this.idToken = idToken;
+        }
+
+
+        public AccessToken getAccessToken() {
+            return accessToken;
+        }
+
+        public IDToken getIdToken() {
+            return idToken;
+        }
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
index a30115f..770876b 100644
--- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
+++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
@@ -77,6 +77,7 @@ public class KeycloakDeploymentBuilderTest {
         assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
         assertEquals(120, deployment.getPublicKeyCacheTtl());
         assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
+        assertTrue(deployment.isVerifyTokenAudience());
     }
 
     @Test
diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak.json b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
index e1b8881..9a7dd22 100644
--- a/adapters/oidc/adapter-core/src/test/resources/keycloak.json
+++ b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
@@ -34,6 +34,7 @@
     "min-time-between-jwks-requests": 20,
     "public-key-cache-ttl": 120,
     "ignore-oauth-query-parameter": true,
+    "verify-token-audience": true,
     "redirect-rewrite-rules" : {
   	"^/wsmaster/api/(.*)$" : "/api/$1"
      }
diff --git a/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SecureDeploymentDefinition.java b/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SecureDeploymentDefinition.java
index 367fde7..dc7b3c6 100755
--- a/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SecureDeploymentDefinition.java
+++ b/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SecureDeploymentDefinition.java
@@ -98,6 +98,12 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
                     .setValidator(new IntRangeValidator(-1, true))
                     .setAllowExpression(true)
                     .build();
+    protected static final SimpleAttributeDefinition PUBLIC_KEY_CACHE_TTL =
+            new SimpleAttributeDefinitionBuilder("public-key-cache-ttl", ModelType.INT, true)
+                    .setXmlName("public-key-cache-ttl")
+                    .setAllowExpression(true)
+                    .setValidator(new IntRangeValidator(-1, true))
+                    .build();
 
     protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
     static {
@@ -110,6 +116,7 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
         DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
+        DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_KEY_CACHE_TTL);
     }
 
     protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
diff --git a/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SharedAttributeDefinitons.java b/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SharedAttributeDefinitons.java
index 697d2a8..f4dfdd1 100755
--- a/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SharedAttributeDefinitons.java
+++ b/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SharedAttributeDefinitons.java
@@ -173,6 +173,13 @@ class SharedAttributeDefinitons {
                     .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
                     .build();
 
+    protected static final SimpleAttributeDefinition VERIFY_TOKEN_AUDIENCE =
+            new SimpleAttributeDefinitionBuilder("verify-token-audience", ModelType.BOOLEAN, true)
+                    .setXmlName("verify-token-audience")
+                    .setAllowExpression(true)
+                    .setDefaultValue(new ModelNode(false))
+                    .build();
+
 
 
     protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
@@ -200,6 +207,7 @@ class SharedAttributeDefinitons {
         ATTRIBUTES.add(TOKEN_STORE);
         ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
         ATTRIBUTES.add(PROXY_URL);
+        ATTRIBUTES.add(VERIFY_TOKEN_AUDIENCE);
     }
 
     /**
diff --git a/adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/org/keycloak/subsystem/as7/LocalDescriptions.properties b/adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/org/keycloak/subsystem/as7/LocalDescriptions.properties
index f78d928..ca01ed3 100755
--- a/adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/org/keycloak/subsystem/as7/LocalDescriptions.properties
+++ b/adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/org/keycloak/subsystem/as7/LocalDescriptions.properties
@@ -47,6 +47,7 @@ keycloak.realm.register-node-period=how often to re-register node
 keycloak.realm.token-store=cookie or session storage for auth session data
 keycloak.realm.principal-attribute=token attribute to use to set Principal name
 keycloak.realm.proxy-url=The URL for the HTTP proxy if one is used.
+keycloak.realm.verify-token-audience=If true, then during bearer-only authentication, the adapter will verify if token contains this client name (resource) as an audience
 
 keycloak.secure-deployment=A deployment secured by Keycloak
 keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
@@ -83,7 +84,9 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
 keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login.  Change this to true if you want to turn this off
 keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
 keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
+keycloak.secure-deployment.public-key-cache-ttl=Maximum time the downloaded public keys are considered valid. When this time reach, the adapter is forced to download public keys from keycloak server
 keycloak.secure-deployment.proxy-url=The URL for the HTTP proxy if one is used.
+keycloak.secure-deployment.verify-token-audience=If true, then during bearer-only authentication, the adapter will verify if token contains this client name (resource) as an audience
 keycloak.secure-deployment.credential=Credential value
 
 keycloak.credential=Credential
diff --git a/adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd b/adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd
index 9472597..0aee422 100755
--- a/adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd
+++ b/adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd
@@ -66,6 +66,7 @@
             <xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
             <xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
             <xs:element name="proxy-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
+            <xs:element name="verify-token-audience" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
         </xs:all>
         <xs:attribute name="name" type="xs:string" use="required">
             <xs:annotation>
@@ -108,7 +109,9 @@
             <xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
             <xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
             <xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+            <xs:element name="public-key-cache-ttl" type="xs:integer" minOccurs="0" maxOccurs="1"/>
             <xs:element name="proxy-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
+            <xs:element name="verify-token-audience" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
         </xs:all>
         <xs:attribute name="name" type="xs:string" use="required">
             <xs:annotation>
diff --git a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
index 5011667..2472dfe 100644
--- a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
+++ b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
@@ -24,16 +24,13 @@ import org.keycloak.OAuthErrorException;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.ServerRequest;
-import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.KeycloakUriBuilder;
-import org.keycloak.jose.jws.JWSInput;
-import org.keycloak.jose.jws.JWSInputException;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.IDToken;
 
-import javax.ws.rs.client.Client;
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Form;
 import javax.ws.rs.core.HttpHeaders;
@@ -537,15 +534,9 @@ public class KeycloakInstalled {
         refreshToken = tokenResponse.getRefreshToken();
         idTokenString = tokenResponse.getIdToken();
 
-        token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
-        if (idTokenString != null) {
-            try {
-                JWSInput input = new JWSInput(idTokenString);
-                idToken = input.readJsonContent(IDToken.class);
-            } catch (JWSInputException e) {
-                throw new VerificationException();
-            }
-        }
+        AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenString, idTokenString, deployment);
+        token = tokens.getAccessToken();
+        idToken = tokens.getIdToken();
     }
 
     public AccessToken getToken() {
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java
index bf9dd29..1eb783d 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java
@@ -96,6 +96,12 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
                     .setValidator(new IntRangeValidator(-1, true))
                     .setAllowExpression(true)
                     .build();
+    protected static final SimpleAttributeDefinition PUBLIC_KEY_CACHE_TTL =
+            new SimpleAttributeDefinitionBuilder("public-key-cache-ttl", ModelType.INT, true)
+                    .setXmlName("public-key-cache-ttl")
+                    .setAllowExpression(true)
+                    .setValidator(new IntRangeValidator(-1, true))
+                    .build();
 
     protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
     static {
@@ -108,6 +114,7 @@ public class SecureDeploymentDefinition extends SimpleResourceDefinition {
         DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
+        DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_KEY_CACHE_TTL);
     }
 
     protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
index 0751da8..94180e8 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
@@ -194,6 +194,13 @@ public class SharedAttributeDefinitons {
                     .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
                     .build();
 
+    protected static final SimpleAttributeDefinition VERIFY_TOKEN_AUDIENCE =
+            new SimpleAttributeDefinitionBuilder("verify-token-audience", ModelType.BOOLEAN, true)
+                    .setXmlName("verify-token-audience")
+                    .setAllowExpression(true)
+                    .setDefaultValue(new ModelNode(false))
+                    .build();
+
     protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
     static {
         ATTRIBUTES.add(REALM_PUBLIC_KEY);
@@ -222,6 +229,7 @@ public class SharedAttributeDefinitons {
         ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
         ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
         ATTRIBUTES.add(PROXY_URL);
+        ATTRIBUTES.add(VERIFY_TOKEN_AUDIENCE);
     }
 
     /**
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
index 30dd04f..e14c718 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
@@ -50,6 +50,7 @@ keycloak.realm.principal-attribute=token attribute to use to set Principal name
 keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
 keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
 keycloak.realm.proxy-url=The URL for the HTTP proxy if one is used.
+keycloak.realm.verify-token-audience=If true, then during bearer-only authentication, the adapter will verify if token contains this client name (resource) as an audience
 
 keycloak.secure-deployment=A deployment secured by Keycloak
 keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
@@ -87,9 +88,11 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
 keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login.  Change this to true if you want to turn this off
 keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
 keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
+keycloak.secure-deployment.public-key-cache-ttl=Maximum time the downloaded public keys are considered valid. When this time reach, the adapter is forced to download public keys from keycloak server
 keycloak.secure-deployment.autodetect-bearer-only=autodetect bearer-only requests
 keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
 keycloak.secure-deployment.proxy-url=The URL for the HTTP proxy if one is used.
+keycloak.secure-deployment.verify-token-audience=If true, then during bearer-only authentication, the adapter will verify if token contains this client name (resource) as an audience
 keycloak.secure-deployment.credential=Credential value
 
 keycloak.credential=Credential
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
index ebdb6d9..4d00ed6 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -69,6 +69,7 @@
             <xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
             <xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
             <xs:element name="proxy-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
+            <xs:element name="verify-token-audience" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
         </xs:all>
         <xs:attribute name="name" type="xs:string" use="required">
             <xs:annotation>
@@ -112,9 +113,11 @@
             <xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
             <xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
             <xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+            <xs:element name="public-key-cache-ttl" type="xs:integer" minOccurs="0" maxOccurs="1"/>
             <xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
             <xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
             <xs:element name="proxy-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
+            <xs:element name="verify-token-audience" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
         </xs:all>
         <xs:attribute name="name" type="xs:string" use="required">
             <xs:annotation>
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml b/adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
index 3cc3f20..2315ea0 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
@@ -23,12 +23,14 @@
         <turn-off-change-session-id-on-login>false</turn-off-change-session-id-on-login>
         <token-minimum-time-to-live>10</token-minimum-time-to-live>
         <min-time-between-jwks-requests>20</min-time-between-jwks-requests>
+        <public-key-cache-ttl>3600</public-key-cache-ttl>
         <realm-public-key>
             MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
         </realm-public-key>
         <auth-server-url>http://localhost:8080/auth</auth-server-url>
         <ssl-required>EXTERNAL</ssl-required>
         <proxy-url>http://localhost:9000</proxy-url>
+        <verify-token-audience>true</verify-token-audience>
         <credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
     </secure-deployment>
     <secure-deployment name="http-endpoint">
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationDefinition.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationDefinition.java
index 613c946..bcfd399 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationDefinition.java
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationDefinition.java
@@ -95,6 +95,12 @@ abstract class AbstractAdapterConfigurationDefinition extends SimpleResourceDefi
                     .setValidator(new IntRangeValidator(-1, true))
                     .setAllowExpression(true)
                     .build();
+    protected static final SimpleAttributeDefinition PUBLIC_KEY_CACHE_TTL =
+            new SimpleAttributeDefinitionBuilder("public-key-cache-ttl", ModelType.INT, true)
+                    .setXmlName("public-key-cache-ttl")
+                    .setAllowExpression(true)
+                    .setValidator(new IntRangeValidator(-1, true))
+                    .build();
 
     static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
 
@@ -108,6 +114,7 @@ abstract class AbstractAdapterConfigurationDefinition extends SimpleResourceDefi
         DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
         DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
+        DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_KEY_CACHE_TTL);
     }
 
     static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList();
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java
index 281e0a9..1366cb8 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java
@@ -200,6 +200,13 @@ public class SharedAttributeDefinitons {
                     .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
                     .build();
 
+    protected static final SimpleAttributeDefinition VERIFY_TOKEN_AUDIENCE =
+            new SimpleAttributeDefinitionBuilder("verify-token-audience", ModelType.BOOLEAN, true)
+                    .setXmlName("verify-token-audience")
+                    .setAllowExpression(true)
+                    .setDefaultValue(new ModelNode(false))
+                    .build();
+
 
     protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
     static {
@@ -230,6 +237,7 @@ public class SharedAttributeDefinitons {
         ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
         ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
         ATTRIBUTES.add(PROXY_URL);
+        ATTRIBUTES.add(VERIFY_TOKEN_AUDIENCE);
     }
 
     private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties
index 769800c..8678ae5 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties
@@ -53,6 +53,7 @@ keycloak.realm.principal-attribute=token attribute to use to set Principal name
 keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
 keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
 keycloak.realm.proxy-url=The URL for the HTTP proxy if one is used.
+keycloak.realm.verify-token-audience=If true, then during bearer-only authentication, the adapter will verify if token contains this client name (resource) as an audience
 
 keycloak.secure-deployment=A deployment secured by Keycloak
 keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
@@ -93,8 +94,10 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
 keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login.  Change this to true if you want to turn this off
 keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
 keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
+keycloak.secure-deployment.public-key-cache-ttl=Maximum time the downloaded public keys are considered valid. When this time reach, the adapter is forced to download public keys from keycloak server
 keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
 keycloak.secure-deployment.proxy-url=The URL for the HTTP proxy if one is used.
+keycloak.secure-deployment.verify-token-audience=If true, then during bearer-only authentication, the adapter will verify if token contains this client name (resource) as an audience
 
 keycloak.secure-server=A deployment secured by Keycloak
 keycloak.secure-server.add=Add a deployment to be secured by Keycloak
@@ -135,8 +138,10 @@ keycloak.secure-server.principal-attribute=token attribute to use to set Princip
 keycloak.secure-server.turn-off-change-session-id-on-login=The session id is changed by default on a successful login.  Change this to true if you want to turn this off
 keycloak.secure-server.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
 keycloak.secure-server.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
+keycloak.secure-server.public-key-cache-ttl=Maximum time the downloaded public keys are considered valid. When this time reach, the adapter is forced to download public keys from keycloak server
 keycloak.secure-server.ignore-oauth-query-parameter=disable query parameter parsing for access_token
 keycloak.secure-server.proxy-url=The URL for the HTTP proxy if one is used.
+keycloak.secure-server.verify-token-audience=If true, then during bearer-only authentication, the adapter will verify if token contains this client name (resource) as an audience
 
 keycloak.secure-deployment.credential=Credential value
 keycloak.secure-server.credential=Credential value
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
index 18080d6..62ce35d 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -71,6 +71,7 @@
             <xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
             <xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
             <xs:element name="proxy-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
+            <xs:element name="verify-token-audience" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
         </xs:all>
         <xs:attribute name="name" type="xs:string" use="required">
             <xs:annotation>
@@ -116,9 +117,11 @@
             <xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
             <xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
             <xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+            <xs:element name="public-key-cache-ttl" type="xs:integer" minOccurs="0" maxOccurs="1"/>
             <xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
             <xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
             <xs:element name="proxy-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
+            <xs:element name="verify-token-audience" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
         </xs:all>
         <xs:attribute name="name" type="xs:string" use="required">
             <xs:annotation>
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml b/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
index 0b703b8..367aec1 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
@@ -53,6 +53,7 @@
         <turn-off-change-session-id-on-login>false</turn-off-change-session-id-on-login>
         <token-minimum-time-to-live>10</token-minimum-time-to-live>
         <min-time-between-jwks-requests>20</min-time-between-jwks-requests>
+        <public-key-cache-ttl>3600</public-key-cache-ttl>
         <realm-public-key>
             MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
         </realm-public-key>
@@ -60,6 +61,7 @@
         <ssl-required>EXTERNAL</ssl-required>
         <confidential-port>443</confidential-port>
         <proxy-url>http://localhost:9000</proxy-url>
+        <verify-token-audience>true</verify-token-audience>
         <credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
         <redirect-rewrite-rule name="^/wsmaster/api/(.*)$">api/$1/</redirect-rewrite-rule>
     </secure-deployment>
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index 2eb5089..095a613 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -40,7 +40,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
         "register-node-at-startup", "register-node-period", "token-store", "principal-attribute",
         "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live",
         "min-time-between-jwks-requests", "public-key-cache-ttl",
-        "policy-enforcer", "ignore-oauth-query-parameter"
+        "policy-enforcer", "ignore-oauth-query-parameter", "verify-token-audience"
 })
 public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClientConfig {
 
@@ -85,6 +85,8 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
     protected boolean pkce = false;
     @JsonProperty("ignore-oauth-query-parameter")
     protected boolean ignoreOAuthQueryParameter = false;
+    @JsonProperty("verify-token-audience")
+    protected boolean verifyTokenAudience = false;
 
     /**
      * The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}.
@@ -268,4 +270,12 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
     public void setIgnoreOAuthQueryParameter(boolean ignoreOAuthQueryParameter) {
         this.ignoreOAuthQueryParameter = ignoreOAuthQueryParameter;
     }
+
+    public boolean isVerifyTokenAudience() {
+        return verifyTokenAudience;
+    }
+
+    public void setVerifyTokenAudience(boolean verifyTokenAudience) {
+        this.verifyTokenAudience = verifyTokenAudience;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/TokenVerifier.java b/core/src/main/java/org/keycloak/TokenVerifier.java
index 39eeed0..71e125f 100755
--- a/core/src/main/java/org/keycloak/TokenVerifier.java
+++ b/core/src/main/java/org/keycloak/TokenVerifier.java
@@ -133,6 +133,37 @@ public class TokenVerifier<T extends JsonWebToken> {
         }
     };
 
+
+    public static class AudienceCheck implements Predicate<JsonWebToken> {
+
+        private final String expectedAudience;
+
+        public AudienceCheck(String expectedAudience) {
+            this.expectedAudience = expectedAudience;
+        }
+
+        @Override
+        public boolean test(JsonWebToken t) throws VerificationException {
+            if (expectedAudience == null) {
+                throw new VerificationException("Missing expectedAudience");
+            }
+
+            String[] audience = t.getAudience();
+            if (audience == null) {
+                throw new VerificationException("No audience in the token");
+            }
+
+            for (String aud : audience) {
+                if (expectedAudience.equals(aud)) {
+                    return true;
+                }
+            }
+
+            throw new VerificationException("Expected audience not available in the token");
+        }
+    };
+
+
     private String tokenString;
     private Class<? extends T> clazz;
     private PublicKey publicKey;
@@ -311,6 +342,16 @@ public class TokenVerifier<T extends JsonWebToken> {
         return replaceCheck(RealmUrlCheck.class, this.checkRealmUrl, new RealmUrlCheck(realmUrl));
     }
 
+    /**
+     * Add check for verifying that token contains the expectedAudience
+     *
+     * @param expectedAudience Audience, which needs to be in the target token. Can't be null
+     * @return This token verifier
+     */
+    public TokenVerifier<T> audience(String expectedAudience) {
+        return this.replaceCheck(AudienceCheck.class, true, new AudienceCheck(expectedAudience));
+    }
+
     public TokenVerifier<T> parse() throws VerificationException {
         if (jws == null) {
             if (tokenString == null) {
diff --git a/core/src/test/java/org/keycloak/RSAVerifierTest.java b/core/src/test/java/org/keycloak/RSAVerifierTest.java
index 8418f14..26f7060 100755
--- a/core/src/test/java/org/keycloak/RSAVerifierTest.java
+++ b/core/src/test/java/org/keycloak/RSAVerifierTest.java
@@ -248,9 +248,45 @@ public class RSAVerifierTest {
         AccessToken v = null;
         try {
             v = verifySkeletonKeyToken(encoded);
+            Assert.fail();
         } catch (VerificationException ignored) {
         }
     }
 
+    @Test
+    public void testAudience() throws Exception {
+        token.addAudience("my-app");
+        token.addAudience("your-app");
+
+        String encoded = new JWSBuilder()
+                .jsonContent(token)
+                .rsa256(idpPair.getPrivate());
+
+        verifyAudience(encoded, "my-app");
+        verifyAudience(encoded, "your-app");
+
+        try {
+            verifyAudience(encoded, "other-app");
+            Assert.fail();
+        } catch (VerificationException ignored) {
+            System.out.println(ignored.getMessage());
+        }
+
+        try {
+            verifyAudience(encoded, null);
+            Assert.fail();
+        } catch (VerificationException ignored) {
+            System.out.println(ignored.getMessage());
+        }
+    }
+
+    private void verifyAudience(String encodedToken, String expectedAudience) throws VerificationException {
+        TokenVerifier.create(encodedToken, AccessToken.class)
+                .publicKey(idpPair.getPublic())
+                .realmUrl("http://localhost:8080/auth/realm")
+                .audience(expectedAudience)
+                .verify();
+    }
+
 
 }
diff --git a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
index a53bc8c..5721ecb 100644
--- a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
+++ b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
@@ -31,7 +31,7 @@ import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.ServerRequest;
 import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
-import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.StreamUtil;
 import org.keycloak.common.util.UriUtils;
@@ -43,7 +43,7 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.io.ByteArrayOutputStream;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -152,7 +152,8 @@ public class ProductServiceAccountServlet extends HttpServlet {
     private void setTokens(HttpServletRequest req, KeycloakDeployment deployment, AccessTokenResponse tokenResponse) throws IOException, VerificationException {
         String token = tokenResponse.getToken();
         String refreshToken = tokenResponse.getRefreshToken();
-        AccessToken tokenParsed = AdapterRSATokenVerifier.verifyToken(token, deployment);
+        AdapterTokenVerifier.VerifiedTokens parsedTokens = AdapterTokenVerifier.verifyTokens(token, tokenResponse.getIdToken(), deployment);
+        AccessToken tokenParsed = parsedTokens.getAccessToken();
         req.getSession().setAttribute(TOKEN, token);
         req.getSession().setAttribute(REFRESH_TOKEN, refreshToken);
         req.getSession().setAttribute(TOKEN_PARSED, tokenParsed);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
index 8dadaaf..325907d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
@@ -22,13 +22,17 @@ import org.keycloak.authentication.ClientAuthenticator;
 import org.keycloak.authentication.ClientAuthenticatorFactory;
 import org.keycloak.authorization.admin.AuthorizationService;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientScopeModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.ClientInstallationProvider;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper;
 import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.util.JsonSerialization;
@@ -64,6 +68,10 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
             rep.setCredentials(adapterConfig);
         }
 
+        if (showVerifyTokenAudience(client)) {
+            rep.setVerifyTokenAudience(true);
+        }
+
         configureAuthorizationSettings(session, client, rep);
 
         String json = null;
@@ -95,6 +103,24 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
     }
 
 
+    // Check if there is audience client scope created for particular client. If yes, admin wants verifying token audience
+    static boolean showVerifyTokenAudience(ClientModel client) {
+        String clientId = client.getClientId();
+        ClientScopeModel clientScope = KeycloakModelUtils.getClientScopeByName(client.getRealm(), clientId);
+        if (clientScope == null) {
+            return false;
+        }
+
+        for (ProtocolMapperModel protocolMapper : clientScope.getProtocolMappers()) {
+            if (AudienceProtocolMapper.PROVIDER_ID.equals(protocolMapper.getProtocolMapper()) && (clientId.equals(protocolMapper.getConfig().get(AudienceProtocolMapper.INCLUDED_CLIENT_AUDIENCE)))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
     @Override
     public String getProtocol() {
         return OIDCLoginProtocol.LOGIN_PROTOCOL;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java
index d0bc939..dd762dc 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java
@@ -49,6 +49,11 @@ public class KeycloakOIDCJbossSubsystemClientInstallation implements ClientInsta
         }
         buffer.append("    <ssl-required>").append(realm.getSslRequired().name()).append("</ssl-required>\n");
         buffer.append("    <resource>").append(client.getClientId()).append("</resource>\n");
+
+        if (KeycloakOIDCClientInstallation.showVerifyTokenAudience(client)) {
+            buffer.append("    <verify-token-audience>true</verify-token-audience>\n");
+        }
+
         String cred = client.getSecret();
         if (KeycloakOIDCClientInstallation.showClientCredentialsAdapterConfig(client)) {
             Map<String, Object> adapterConfig = KeycloakOIDCClientInstallation.getClientCredentialsAdapterConfig(session, client);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java
index 8cd4eae..bf63858 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java
@@ -36,7 +36,7 @@ public class AudienceProtocolMapper extends AbstractOIDCProtocolMapper implement
 
     private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
 
-    private static final String INCLUDED_CLIENT_AUDIENCE = "included.client.audience";
+    public static final String INCLUDED_CLIENT_AUDIENCE = "included.client.audience";
     private static final String INCLUDED_CLIENT_AUDIENCE_LABEL = "included.client.audience.label";
     private static final String INCLUDED_CLIENT_AUDIENCE_HELP_TEXT = "included.client.audience.tooltip";
 
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index d391e04..b78aaee 100644
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -210,7 +210,7 @@ public class ClientManager {
     }
 
     @JsonPropertyOrder({"realm", "realm-public-key", "bearer-only", "auth-server-url", "ssl-required",
-            "resource", "public-client", "credentials",
+            "resource", "public-client", "verify-token-audience", "credentials",
             "use-resource-role-mappings"})
     public static class InstallationAdapterConfig extends BaseRealmConfig {
         @JsonProperty("resource")
@@ -223,6 +223,8 @@ public class ClientManager {
         protected Boolean publicClient;
         @JsonProperty("credentials")
         protected Map<String, Object> credentials;
+        @JsonProperty("verify-token-audience")
+        protected Boolean verifyTokenAudience;
         @JsonProperty("policy-enforcer")
         protected PolicyEnforcerConfig enforcerConfig;
 
@@ -250,6 +252,14 @@ public class ClientManager {
             this.credentials = credentials;
         }
 
+        public Boolean getVerifyTokenAudience() {
+            return verifyTokenAudience;
+        }
+
+        public void setVerifyTokenAudience(Boolean verifyTokenAudience) {
+            this.verifyTokenAudience = verifyTokenAudience;
+        }
+
         public Boolean getPublicClient() {
             return publicClient;
         }
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java
index f928454..e58a905 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java
@@ -74,21 +74,20 @@ public class CustomerServlet extends HttpServlet {
        
 
         //try {
-        StringBuilder result = new StringBuilder();
         String urlBase = ServletTestUtils.getUrlBase(req);
 
-        URL url = new URL(urlBase + "/customer-db/");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setRequestMethod("GET");
-        conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + context.getTokenString());
-        BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
-        String line;
-        while ((line = rd.readLine()) != null) {
-            result.append(line);
+        // Decide what to call based on the URL suffix
+        String serviceUrl;
+        if (req.getRequestURI().endsWith("/call-customer-db-audience-required")) {
+            serviceUrl = urlBase + "/customer-db-audience-required/";
+        } else {
+            serviceUrl = urlBase + "/customer-db/";
         }
-        rd.close();
+
+        String result = invokeService(serviceUrl, context);
+
         resp.setContentType("text/html");
-        pw.println(result.toString());
+        pw.println(result);
         pw.flush();
 //
 //            Response response = target.request().get();
@@ -106,4 +105,28 @@ public class CustomerServlet extends HttpServlet {
 //        }
     }
 
+
+    private String invokeService(String serviceUrl, KeycloakSecurityContext context) throws IOException {
+        StringBuilder result = new StringBuilder();
+
+        URL url = new URL(serviceUrl);
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod("GET");
+        conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + context.getTokenString());
+
+        if (conn.getResponseCode() != 200) {
+            conn.getErrorStream().close();
+            return "Service returned: " + conn.getResponseCode();
+        }
+
+        BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+        String line;
+        while ((line = rd.readLine()) != null) {
+            result.append(line);
+        }
+        rd.close();
+
+        return result.toString();
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerDbAudienceRequired.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerDbAudienceRequired.java
new file mode 100644
index 0000000..1ba1171
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerDbAudienceRequired.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.adapter.page;
+
+import java.net.URL;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CustomerDbAudienceRequired extends AbstractPageWithInjectedUrl {
+
+    public static final String DEPLOYMENT_NAME = "customer-db-audience-required";
+
+    @ArquillianResource
+    @OperateOnDeployment(DEPLOYMENT_NAME)
+    private URL url;
+
+    @Override
+    public URL getInjectedUrl() {
+        return url;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortal.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortal.java
index 51c0978..70395f9 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortal.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortal.java
@@ -44,4 +44,14 @@ public class CustomerPortal extends AbstractPageWithInjectedUrl {
         return url + "/logout";
     }
 
+    public String callCustomerDbAudienceRequiredUrl(boolean attachAudienceScope) {
+        String url = this.url + "/call-customer-db-audience-required";
+
+        if (attachAudienceScope) {
+            url = url + "?scope=customer-db-audience-required";
+        }
+
+        return url;
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/jaas/LoginModulesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/jaas/LoginModulesTest.java
new file mode 100644
index 0000000..03000ed
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/jaas/LoginModulesTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.adapter.jaas;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.ws.rs.core.Response;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.jaas.AbstractKeycloakLoginModule;
+import org.keycloak.adapters.jaas.BearerTokenLoginModule;
+import org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule;
+import org.keycloak.adapters.jaas.RolePrincipal;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.utils.io.IOUtil;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LoginModulesTest extends AbstractKeycloakTest {
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        testRealms.add(IOUtil.loadRealm("/adapter-test/demorealm.json"));
+    }
+
+    @Before
+    public void generateAudienceClientScope() {
+        if (ApiUtil.findClientScopeByName(adminClient.realm("demo"), "customer-db-audience-required") != null) {
+            return;
+        }
+
+        // Generate audience client scope
+        Response resp = adminClient.realm("demo").clientScopes().generateAudienceClientScope("customer-db-audience-required");
+        String clientScopeId = ApiUtil.getCreatedId(resp);
+        resp.close();
+        ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal");
+        client.addOptionalClientScope(clientScopeId);
+    }
+
+
+    @Test
+    public void testDirectAccessGrantLoginModuleLoginFailed() throws Exception {
+        LoginContext loginContext = new LoginContext("does-not-matter", null,
+                createJaasCallbackHandler("bburke@redhat.com", "bad-password"),
+                createJaasConfigurationForDirectGrant(null));
+
+        try {
+            loginContext.login();
+            Assert.fail("Not expected to successfully login");
+        } catch (LoginException le) {
+            // Ignore
+        }
+    }
+
+
+    @Test
+    public void testDirectAccessGrantLoginModuleLoginSuccess() throws Exception {
+        oauth.realm("demo");
+
+        LoginContext loginContext = directGrantLogin(null);
+        Subject subject = loginContext.getSubject();
+
+        // Assert principals in subject
+        KeycloakPrincipal principal = subject.getPrincipals(KeycloakPrincipal.class).iterator().next();
+        Assert.assertEquals("bburke@redhat.com", principal.getKeycloakSecurityContext().getToken().getPreferredUsername());
+        assertToken(principal.getKeycloakSecurityContext().getTokenString(), true);
+
+        Set<RolePrincipal> roles = subject.getPrincipals(RolePrincipal.class);
+        Assert.assertEquals(1, roles.size());
+        Assert.assertEquals("user", roles.iterator().next().getName());
+
+        // Logout and assert token not valid anymore
+        loginContext.logout();
+        assertToken(principal.getKeycloakSecurityContext().getTokenString(), false);
+    }
+
+
+    @Test
+    public void testBearerLoginFailedLogin() throws Exception {
+        oauth.realm("demo");
+
+        LoginContext directGrantCtx = directGrantLogin(null);
+        String accessToken = directGrantCtx.getSubject().getPrincipals(KeycloakPrincipal.class).iterator().next()
+                .getKeycloakSecurityContext().getTokenString();
+
+        LoginContext bearerCtx = new LoginContext("does-not-matter", null,
+                createJaasCallbackHandler("doesn-not-matter", accessToken),
+                createJaasConfigurationForBearer());
+
+        // Login should fail due insufficient audience in the token
+        try {
+            bearerCtx.login();
+            Assert.fail("Not expected to successfully login");
+        } catch (LoginException le) {
+            // Ignore
+        }
+
+        directGrantCtx.logout();
+    }
+
+
+    @Test
+    public void testBearerLoginSuccess() throws Exception {
+        oauth.realm("demo");
+
+        LoginContext directGrantCtx = directGrantLogin("customer-db-audience-required");
+        String accessToken = directGrantCtx.getSubject().getPrincipals(KeycloakPrincipal.class).iterator().next()
+                .getKeycloakSecurityContext().getTokenString();
+
+        LoginContext bearerCtx = new LoginContext("does-not-matter", null,
+                createJaasCallbackHandler("doesn-not-matter", accessToken),
+                createJaasConfigurationForBearer());
+
+        // Login should be successful
+        bearerCtx.login();
+
+        // Assert subject
+        Subject subject = bearerCtx.getSubject();
+
+        KeycloakPrincipal principal = subject.getPrincipals(KeycloakPrincipal.class).iterator().next();
+        Assert.assertEquals("bburke@redhat.com", principal.getKeycloakSecurityContext().getToken().getPreferredUsername());
+        assertToken(principal.getKeycloakSecurityContext().getTokenString(), true);
+
+        Set<RolePrincipal> roles = subject.getPrincipals(RolePrincipal.class);
+        Assert.assertEquals(1, roles.size());
+        Assert.assertEquals("user", roles.iterator().next().getName());
+
+        // Logout
+        bearerCtx.logout();
+        directGrantCtx.logout();
+    }
+
+
+    private LoginContext directGrantLogin(String scope) throws LoginException {
+        LoginContext loginContext = new LoginContext("does-not-matter", null,
+                createJaasCallbackHandler("bburke@redhat.com", "password"),
+                createJaasConfigurationForDirectGrant(scope));
+
+        loginContext.login();
+
+        return loginContext;
+    }
+
+
+    private void assertToken(String accessToken, boolean expectActive) throws IOException {
+        String introspectionResponse = oauth.introspectAccessTokenWithClientCredential("customer-portal", "password", accessToken);
+        ObjectMapper objectMapper = new ObjectMapper();
+        JsonNode jsonNode = objectMapper.readTree(introspectionResponse);
+        Assert.assertEquals(expectActive, jsonNode.get("active").asBoolean());
+    }
+
+
+    private CallbackHandler createJaasCallbackHandler(final String principal, final String password) {
+        return new CallbackHandler() {
+
+            @Override
+            public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                for (Callback callback : callbacks) {
+                    if (callback instanceof NameCallback) {
+                        NameCallback nameCallback = (NameCallback) callback;
+                        nameCallback.setName(principal);
+                    } else if (callback instanceof PasswordCallback) {
+                        PasswordCallback passwordCallback = (PasswordCallback) callback;
+                        passwordCallback.setPassword(password.toCharArray());
+                    } else {
+                        throw new UnsupportedCallbackException(callback, "Unsupported callback: " + callback.getClass().getCanonicalName());
+                    }
+                }
+            }
+        };
+    }
+
+
+    private Configuration createJaasConfigurationForDirectGrant(String scope) {
+        return new Configuration() {
+
+            @Override
+            public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+                Map<String, Object> options = new HashMap<>();
+                options.put(AbstractKeycloakLoginModule.KEYCLOAK_CONFIG_FILE_OPTION, "classpath:adapter-test/customer-portal/WEB-INF/keycloak.json");
+                if (scope != null) {
+                    options.put(DirectAccessGrantsLoginModule.SCOPE_OPTION, scope);
+                }
+
+                AppConfigurationEntry LMConfiguration = new AppConfigurationEntry(DirectAccessGrantsLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
+                return new AppConfigurationEntry[] { LMConfiguration };
+            }
+        };
+    }
+
+
+    private Configuration createJaasConfigurationForBearer() {
+        return new Configuration() {
+
+            @Override
+            public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+                Map<String, Object> options = new HashMap<>();
+                options.put(AbstractKeycloakLoginModule.KEYCLOAK_CONFIG_FILE_OPTION, "classpath:adapter-test/customer-db-audience-required/WEB-INF/keycloak.json");
+
+                AppConfigurationEntry LMConfiguration = new AppConfigurationEntry(BearerTokenLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
+                return new AppConfigurationEntry[] { LMConfiguration };
+            }
+        };
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java
index f2fad1c..5149f58 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java
@@ -76,6 +76,7 @@ import org.keycloak.testsuite.adapter.page.BasicAuth;
 import org.keycloak.testsuite.adapter.page.ClientSecretJwtSecurePortal;
 import org.keycloak.testsuite.adapter.page.CustomerCookiePortal;
 import org.keycloak.testsuite.adapter.page.CustomerDb;
+import org.keycloak.testsuite.adapter.page.CustomerDbAudienceRequired;
 import org.keycloak.testsuite.adapter.page.CustomerDbErrorPage;
 import org.keycloak.testsuite.adapter.page.CustomerPortal;
 import org.keycloak.testsuite.adapter.page.CustomerPortalNoConf;
@@ -213,6 +214,11 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
         return servletDeployment(CustomerDb.DEPLOYMENT_NAME, AdapterActionsFilter.class, CustomerDatabaseServlet.class);
     }
 
+    @Deployment(name = CustomerDbAudienceRequired.DEPLOYMENT_NAME)
+    protected static WebArchive customerDbAudienceRequired() {
+        return servletDeployment(CustomerDbAudienceRequired.DEPLOYMENT_NAME, AdapterActionsFilter.class, CustomerDatabaseServlet.class);
+    }
+
     @Deployment(name = CustomerDbErrorPage.DEPLOYMENT_NAME)
     protected static WebArchive customerDbErrorPage() {
         return servletDeployment(CustomerDbErrorPage.DEPLOYMENT_NAME, CustomerDatabaseServlet.class, ErrorServlet.class);
@@ -836,6 +842,50 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
         }
     }
 
+
+    @Test
+    public void testVerifyTokenAudience() {
+        // Generate audience client scope
+        Response resp = adminClient.realm("demo").clientScopes().generateAudienceClientScope("customer-db-audience-required");
+        String clientScopeId = ApiUtil.getCreatedId(resp);
+        resp.close();
+        ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal");
+        client.addOptionalClientScope(clientScopeId);
+
+        // Login without audience scope. Invoke service should end with failure
+        driver.navigate().to(customerPortal.callCustomerDbAudienceRequiredUrl(false));
+        assertTrue(testRealmLoginPage.form().isUsernamePresent());
+        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+        testRealmLoginPage.form().login("bburke@redhat.com", "password");
+        assertCurrentUrlEquals(customerPortal.callCustomerDbAudienceRequiredUrl(false));
+
+        String pageSource = driver.getPageSource();
+        Assert.assertTrue(pageSource.contains("Service returned: 401"));
+        Assert.assertFalse(pageSource.contains("Stian Thorgersen"));
+
+        // Logout TODO: will be good to not request logout to force adapter to use additional scope (and other request parameters)
+        driver.navigate().to(customerPortal.logout());
+        waitForPageToLoad();
+
+        // Login with requested audience
+        driver.navigate().to(customerPortal.callCustomerDbAudienceRequiredUrl(true));
+        assertTrue(testRealmLoginPage.form().isUsernamePresent());
+        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+        testRealmLoginPage.form().login("bburke@redhat.com", "password");
+        assertCurrentUrlEquals(customerPortal.callCustomerDbAudienceRequiredUrl(false));
+
+        pageSource = driver.getPageSource();
+        Assert.assertFalse(pageSource.contains("Service returned: 401"));
+        assertLogged();
+
+        // logout
+        String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
+                .queryParam(OAuth2Constants.REDIRECT_URI, customerPortal.toString()).build("demo").toString();
+        driver.navigate().to(logoutUri);
+        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+    }
+
+
     @Test
     public void testBasicAuth() {
         String value = "hello";
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
index 6ff3bbe..d759263 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
@@ -17,14 +17,20 @@
 
 package org.keycloak.testsuite.admin.client;
 
+import javax.ws.rs.core.Response;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
 import org.keycloak.testsuite.ProfileAssume;
 import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
+import org.keycloak.testsuite.util.AdminEventPaths;
+import org.keycloak.testsuite.util.WaitUtils;
 
 import static org.junit.Assert.assertThat;
 import static org.hamcrest.Matchers.*;
@@ -97,6 +103,27 @@ public class InstallationTest extends AbstractClientTest {
         assertThat(json, containsString("bearer-only"));
         assertThat(json, not(containsString("public-client")));
         assertThat(json, not(containsString("credentials")));
+        assertThat(json, not(containsString("verify-token-audience")));
+    }
+
+    @Test
+    public void testOidcBearerOnlyJsonWithAudienceClientScope() {
+        // Generate audience client scope
+        Response resp = testRealmResource().clientScopes().generateAudienceClientScope(OIDC_NAME_BEARER_ONLY_NAME);
+        String clientScopeId = ApiUtil.getCreatedId(resp);
+        resp.close();
+        assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientScopeGenerateAudienceClientScopePath(), null, ResourceType.CLIENT_SCOPE);
+
+        String json = oidcBearerOnlyClient.getInstallationProvider("keycloak-oidc-keycloak-json");
+        assertOidcInstallationConfig(json);
+        assertThat(json, containsString("bearer-only"));
+        assertThat(json, not(containsString("public-client")));
+        assertThat(json, not(containsString("credentials")));
+        assertThat(json, containsString("verify-token-audience"));
+
+        // Remove clientScope
+        testRealmResource().clientScopes().get(clientScopeId).remove();
+        assertAdminEvents.assertEvent(getRealmId(), OperationType.DELETE, AdminEventPaths.clientScopeResourcePath(clientScopeId), null, ResourceType.CLIENT_SCOPE);
     }
 
     @Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java
index 980465d..0992b5e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java
@@ -170,6 +170,11 @@ public class AdminEventPaths {
         return uri.toString();
     }
 
+    public static String clientScopeGenerateAudienceClientScopePath() {
+        URI uri = UriBuilder.fromUri("").path(RealmResource.class, "clientScopes").path(ClientScopesResource.class, "generateAudienceClientScope").build();
+        return uri.toString();
+    }
+
     public static String clientScopeRoleMappingsRealmLevelPath(String clientScopeDbId) {
         URI uri = UriBuilder.fromUri(clientScopeResourcePath(clientScopeDbId)).path(ClientScopeResource.class, "getScopeMappings")
                 .path(RoleMappingResource.class, "realmLevel")
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/META-INF/context.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/META-INF/context.xml
new file mode 100644
index 0000000..b4ddcce
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/META-INF/context.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<Context path="/customer-portal">
+    <Valve className="org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve"/>
+</Context>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..8c59313
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/WEB-INF/jetty-web.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+    <Get name="securityHandler">
+        <Set name="authenticator">
+            <New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
+                <!--
+                <Set name="adapterConfig">
+                    <New class="org.keycloak.representations.adapters.config.AdapterConfig">
+                        <Set name="realm">tomcat</Set>
+                        <Set name="resource">customer-portal</Set>
+                        <Set name="authServerUrl">http://localhost:8180/auth</Set>
+                        <Set name="sslRequired">external</Set>
+                        <Set name="credentials">
+                            <Map>
+                                <Entry>
+                                    <Item>secret</Item>
+                                    <Item>password</Item>
+                                </Entry>
+                            </Map>
+                        </Set>
+                        <Set name="realmKey">MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</Set>
+                    </New>
+                </Set>
+                -->
+            </New>
+        </Set>
+    </Get>
+</Configure>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/WEB-INF/keycloak.json
new file mode 100644
index 0000000..34c5310
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/WEB-INF/keycloak.json
@@ -0,0 +1,11 @@
+{
+  "realm" : "demo",
+  "resource" : "customer-db-audience-required",
+  "auth-server-url": "http://localhost:8180/auth",
+  "ssl-required" : "external",
+  "bearer-only" : true,
+  "enable-cors" : true,
+  "public-key-cache-ttl": 600,
+  "verify-token-audience": true
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/WEB-INF/web.xml
new file mode 100644
index 0000000..56ed0e7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-db-audience-required/WEB-INF/web.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+
+    <module-name>customer-db</module-name>
+
+
+    <filter>
+        <filter-name>AdapterActionsFilter</filter-name>
+        <filter-class>org.keycloak.testsuite.adapter.filter.AdapterActionsFilter</filter-class>
+    </filter>
+
+    <servlet>
+        <servlet-name>Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.adapter.servlet.CustomerDatabaseServlet</servlet-class>
+    </servlet>
+
+    <filter-mapping>
+        <filter-name>AdapterActionsFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <servlet-mapping>
+        <servlet-name>Servlet</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Users</web-resource-name>
+            <url-pattern>/*</url-pattern>
+        </web-resource-collection>
+        <auth-constraint>
+            <role-name>user</role-name>
+        </auth-constraint>
+    </security-constraint>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Unsecured</web-resource-name>
+            <url-pattern>/unsecured/*</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
+    <login-config>
+        <auth-method>KEYCLOAK</auth-method>
+        <realm-name>demo</realm-name>
+    </login-config>
+
+    <security-role>
+        <role-name>admin</role-name>
+    </security-role>
+    <security-role>
+        <role-name>user</role-name>
+    </security-role>
+    
+</web-app>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json
index 32703f9..e6e9c42 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json
@@ -1,7 +1,7 @@
 {
     "realm": "demo",
     "resource": "customer-portal",
-    "auth-server-url": "http://localhostt:8180/auth",
+    "auth-server-url": "http://localhost:8180/auth",
     "ssl-required" : "external",
     "expose-token": true,
     "min-time-between-jwks-requests": 120,
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
index bd1ced2..a45098c 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
@@ -134,6 +134,13 @@
             "bearerOnly": true
         },
         {
+            "clientId": "customer-db-audience-required",
+            "enabled": true,
+            "adminUrl": "/customer-db-audience-required",
+            "baseUrl": "/customer-db-audience-required",
+            "bearerOnly": true
+        },
+        {
             "clientId": "customer-portal",
             "enabled": true,
             "adminUrl": "/customer-portal",