keycloak-uncached

Changes

adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/BearerTokenPolicyEnforcer.java 81(+0 -81)

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index 402ed48..c54191f 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -18,7 +18,10 @@
 package org.keycloak.adapters.authorization;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 import org.jboss.logging.Logger;
@@ -161,7 +164,8 @@ public abstract class AbstractPolicyEnforcer {
                         if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
                             policyEnforcer.getPathMatcher().removeFromCache(getPath(request));
                         }
-                        return true;
+
+                        return hasValidClaims(actualPathConfig, httpFacade, authorization);
                     }
                 }
             } else {
@@ -183,6 +187,41 @@ public abstract class AbstractPolicyEnforcer {
         return false;
     }
 
+    private boolean hasValidClaims(PathConfig actualPathConfig, OIDCHttpFacade httpFacade, Authorization authorization) {
+        Map<String, Map<String, Object>> claimInformationPointConfig = actualPathConfig.getClaimInformationPointConfig();
+
+        if (claimInformationPointConfig != null) {
+            Map<String, List<String>> claims = new HashMap<>();
+
+            for (Entry<String, Map<String, Object>> entry : claimInformationPointConfig.entrySet()) {
+                ClaimInformationPointProviderFactory factory = policyEnforcer.getClaimInformationPointProviderFactories().get(entry.getKey());
+
+                if (factory == null) {
+                    throw new RuntimeException("Could not find claim information provider with name [" + entry.getKey() + "]");
+                }
+
+                claims.putAll(factory.create(entry.getValue()).resolve(httpFacade));
+            }
+
+            Map<String, List<String>> grantedClaims = authorization.getClaims();
+
+            if (grantedClaims != null) {
+                if (claims.isEmpty()) {
+                    return false;
+                }
+                for (Entry<String, List<String>> entry : grantedClaims.entrySet()) {
+                    List<String> requestClaims = claims.get(entry.getKey());
+
+                    if (requestClaims == null || requestClaims.isEmpty() || !entry.getValue().containsAll(requestClaims)) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
     protected void handleAccessDenied(OIDCHttpFacade httpFacade) {
         httpFacade.getResponse().sendError(403);
     }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/ClaimsInformationPointProvider.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/ClaimsInformationPointProvider.java
new file mode 100644
index 0000000..0221c8d
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/ClaimsInformationPointProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 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.authorization.cip;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
+import org.keycloak.adapters.authorization.util.PlaceHolders;
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ClaimsInformationPointProvider implements ClaimInformationPointProvider {
+
+    private final Map<String, Object> config;
+
+    public ClaimsInformationPointProvider(Map<String, Object> config) {
+        this.config = config;
+    }
+
+    @Override
+    public Map<String, List<String>> resolve(HttpFacade httpFacade) {
+        Map<String, List<String>> claims = new HashMap<>();
+
+        for (Entry<String, Object> configEntry : config.entrySet()) {
+            String claimName = configEntry.getKey();
+            Object claimValue = configEntry.getValue();
+            List<String> values = new ArrayList<>();
+
+            if (claimValue instanceof String) {
+                values = getValues(claimValue.toString(), httpFacade);
+            } else if (claimValue instanceof Collection) {
+                Iterator iterator = Collection.class.cast(claimValue).iterator();
+
+                while (iterator.hasNext()) {
+                    List<String> resolvedValues = getValues(iterator.next().toString(), httpFacade);
+
+                    if (!resolvedValues.isEmpty()) {
+                        values.addAll(resolvedValues);
+                    }
+                }
+            }
+
+            if (!values.isEmpty()) {
+                claims.put(claimName, values);
+            }
+        }
+
+        return claims;
+    }
+
+    private List<String> getValues(String value, HttpFacade httpFacade) {
+        return PlaceHolders.resolve(value, httpFacade);
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/ClaimsInformationPointProviderFactory.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/ClaimsInformationPointProviderFactory.java
new file mode 100644
index 0000000..c86c201
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/ClaimsInformationPointProviderFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 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.authorization.cip;
+
+import java.util.Map;
+
+import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
+import org.keycloak.adapters.authorization.PolicyEnforcer;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ClaimsInformationPointProviderFactory implements ClaimInformationPointProviderFactory<ClaimsInformationPointProvider> {
+
+    @Override
+    public String getName() {
+        return "claims";
+    }
+
+    @Override
+    public void init(PolicyEnforcer policyEnforcer) {
+
+    }
+
+    @Override
+    public ClaimsInformationPointProvider create(Map<String, Object> config) {
+        return new ClaimsInformationPointProvider(config);
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/HttpClaimInformationPointProvider.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/HttpClaimInformationPointProvider.java
new file mode 100644
index 0000000..51efb90
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/HttpClaimInformationPointProvider.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2018 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.authorization.cip;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.util.EntityUtils;
+import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
+import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.authorization.util.JsonUtils;
+import org.keycloak.adapters.authorization.util.PlaceHolders;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.authorization.client.util.HttpResponseException;
+import org.keycloak.common.util.StreamUtil;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class HttpClaimInformationPointProvider implements ClaimInformationPointProvider {
+
+    private final Map<String, Object> config;
+    private final HttpClient httpClient;
+
+    public HttpClaimInformationPointProvider(Map<String, Object> config, PolicyEnforcer policyEnforcer) {
+        this.config = config;
+        this.httpClient = policyEnforcer.getDeployment().getClient();
+    }
+
+    @Override
+    public Map<String, List<String>> resolve(HttpFacade httpFacade) {
+        try {
+            InputStream responseStream = executeRequest(httpFacade);
+
+            try (InputStream inputStream = new BufferedInputStream(responseStream)) {
+                JsonNode jsonNode = JsonSerialization.mapper.readTree(inputStream);
+                Map<String, List<String>> claims = new HashMap<>();
+                Map<String, Object> claimsDef = (Map<String, Object>) config.get("claims");
+
+                if (claimsDef == null) {
+                    Iterator<String> nodeNames = jsonNode.fieldNames();
+
+                    while (nodeNames.hasNext()) {
+                        String nodeName = nodeNames.next();
+                        claims.put(nodeName, JsonUtils.getValues(jsonNode.get(nodeName)));
+                    }
+                } else {
+                    for (Entry<String, Object> claimDef : claimsDef.entrySet()) {
+                        List<String> jsonPaths = new ArrayList<>();
+
+                        if (claimDef.getValue() instanceof Collection) {
+                            jsonPaths.addAll(Collection.class.cast(claimDef.getValue()));
+                        } else {
+                            jsonPaths.add(claimDef.getValue().toString());
+                        }
+
+                        List<String> claimValues = new ArrayList<>();
+
+                        for (String path : jsonPaths) {
+                            claimValues.addAll(JsonUtils.getValues(jsonNode, path));
+                        }
+
+                        claims.put(claimDef.getKey(), claimValues);
+                    }
+                }
+
+                return claims;
+            }
+        } catch (IOException cause) {
+            throw new RuntimeException("Could not obtain claims from http claim information point [" + config.get("url") + "] response", cause);
+        }
+    }
+
+    private InputStream executeRequest(HttpFacade httpFacade) {
+        String method = config.get("method").toString();
+
+        if (method == null) {
+            method = "GET";
+        }
+
+        RequestBuilder builder = null;
+
+        if ("GET".equalsIgnoreCase(method)) {
+            builder = RequestBuilder.get();
+        } else {
+            builder = RequestBuilder.post();
+        }
+
+        builder.setUri(config.get("url").toString());
+
+        byte[] bytes = new byte[0];
+
+        try {
+            setParameters(builder, httpFacade);
+
+            if (config.containsKey("headers")) {
+                setHeaders(builder, httpFacade);
+            }
+
+            HttpResponse response = httpClient.execute(builder.build());
+            HttpEntity entity = response.getEntity();
+
+            if (entity != null) {
+                bytes = EntityUtils.toByteArray(entity);
+            }
+
+            StatusLine statusLine = response.getStatusLine();
+            int statusCode = statusLine.getStatusCode();
+
+            if (statusCode < 200 || statusCode >= 300) {
+                throw new HttpResponseException("Unexpected response from server: " + statusCode + " / " + statusLine.getReasonPhrase(), statusCode, statusLine.getReasonPhrase(), bytes);
+            }
+
+            return new ByteArrayInputStream(bytes);
+        } catch (Exception cause) {
+            try {
+                throw new RuntimeException("Error executing http method [" + builder + "]. Response : " + StreamUtil.readString(new ByteArrayInputStream(bytes), Charset.forName("UTF-8")), cause);
+            } catch (Exception e) {
+                throw new RuntimeException("Error executing http method [" + builder + "]", cause);
+            }
+        }
+    }
+
+    private void setHeaders(RequestBuilder builder, HttpFacade httpFacade) {
+        Object headersDef = config.get("headers");
+
+        if (headersDef != null) {
+            Map<String, Object> headers = Map.class.cast(headersDef);
+
+            for (Entry<String, Object> header : headers.entrySet()) {
+                Object value = header.getValue();
+                List<String> headerValues = new ArrayList<>();
+
+                if (value instanceof Collection) {
+                    Collection values = Collection.class.cast(value);
+                    Iterator iterator = values.iterator();
+
+                    while (iterator.hasNext()) {
+                        headerValues.addAll(PlaceHolders.resolve(iterator.next().toString(), httpFacade));
+                    }
+                } else {
+                    headerValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
+                }
+
+                for (String headerValue : headerValues) {
+                    builder.addHeader(header.getKey(), headerValue);
+                }
+            }
+        }
+    }
+
+    private void setParameters(RequestBuilder builder, HttpFacade httpFacade) {
+        Object config = this.config.get("parameters");
+
+        if (config != null) {
+            Map<String, Object> paramsDef = Map.class.cast(config);
+
+            for (Entry<String, Object> paramDef : paramsDef.entrySet()) {
+                Object value = paramDef.getValue();
+                List<String> paramValues = new ArrayList<>();
+
+                if (value instanceof Collection) {
+                    Collection values = Collection.class.cast(value);
+                    Iterator iterator = values.iterator();
+
+                    while (iterator.hasNext()) {
+                        paramValues.addAll(PlaceHolders.resolve(iterator.next().toString(), httpFacade));
+                    }
+                } else {
+                    paramValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
+                }
+
+                for (String paramValue : paramValues) {
+                    builder.addParameter(paramDef.getKey(), paramValue);
+                }
+            }
+        }
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/HttpClaimInformationPointProviderFactory.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/HttpClaimInformationPointProviderFactory.java
new file mode 100644
index 0000000..6e36562
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/cip/HttpClaimInformationPointProviderFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 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.authorization.cip;
+
+import java.util.Map;
+
+import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
+import org.keycloak.adapters.authorization.PolicyEnforcer;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class HttpClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<HttpClaimInformationPointProvider> {
+
+    private PolicyEnforcer policyEnforcer;
+
+    @Override
+    public String getName() {
+        return "http";
+    }
+
+    @Override
+    public void init(PolicyEnforcer policyEnforcer) {
+        this.policyEnforcer = policyEnforcer;
+    }
+
+    @Override
+    public HttpClaimInformationPointProvider create(Map<String, Object> config) {
+        return new HttpClaimInformationPointProvider(config, policyEnforcer);
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/ClaimInformationPointProvider.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/ClaimInformationPointProvider.java
new file mode 100644
index 0000000..fb594ef
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/ClaimInformationPointProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 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.authorization;
+
+import java.util.List;
+import java.util.Map;
+
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface ClaimInformationPointProvider {
+
+    Map<String, List<String>> resolve(HttpFacade httpFacade);
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/ClaimInformationPointProviderFactory.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/ClaimInformationPointProviderFactory.java
new file mode 100644
index 0000000..894debc
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/ClaimInformationPointProviderFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 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.authorization;
+
+import java.util.Map;
+
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface ClaimInformationPointProviderFactory<C extends ClaimInformationPointProvider> {
+
+    String getName();
+
+    void init(PolicyEnforcer policyEnforcer);
+
+    C create(Map<String, Object> config);
+}
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 0732ee9..4e87c90 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
@@ -18,7 +18,11 @@
 package org.keycloak.adapters.authorization;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakSecurityContext;
@@ -28,6 +32,9 @@ import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.authorization.client.AuthorizationDeniedException;
 import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.resource.PermissionResource;
+import org.keycloak.authorization.client.resource.ProtectionResource;
+import org.keycloak.common.util.Base64;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
 import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
@@ -35,7 +42,7 @@ import org.keycloak.representations.idm.authorization.AuthorizationRequest;
 import org.keycloak.representations.idm.authorization.AuthorizationResponse;
 import org.keycloak.representations.idm.authorization.Permission;
 import org.keycloak.representations.idm.authorization.PermissionRequest;
-import org.keycloak.representations.idm.authorization.PermissionResponse;
+import org.keycloak.util.JsonSerialization;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -72,7 +79,27 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
         AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
 
         if (newAuthorization != null) {
-            authorization.getPermissions().addAll(newAuthorization.getPermissions());
+            List<Permission> grantedPermissions = authorization.getPermissions();
+            List<Permission> newPermissions = newAuthorization.getPermissions();
+
+            for (Permission newPermission : newPermissions) {
+                if (!grantedPermissions.contains(newPermission)) {
+                    grantedPermissions.add(newPermission);
+                }
+            }
+
+            Map<String, List<String>> newClaims = newAuthorization.getClaims();
+
+            if (newClaims != null) {
+                Map<String, List<String>> claims = authorization.getClaims();
+
+                if (claims == null) {
+                    claims = new HashMap<>();
+                    authorization.setClaims(claims);
+                }
+
+                claims.putAll(newClaims);
+            }
         }
 
         original.setAuthorization(authorization);
@@ -81,8 +108,29 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
     }
 
     @Override
-    protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade facade) {
-        handleAccessDenied(facade);
+    protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
+        if (isBearerAuthorization(httpFacade)) {
+            HttpFacade.Response response = httpFacade.getResponse();
+            AuthzClient authzClient = getAuthzClient();
+            String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient, httpFacade);
+
+            if (ticket != null) {
+                response.setStatus(401);
+                response.setHeader("WWW-Authenticate", new StringBuilder("UMA realm=\"").append(authzClient.getConfiguration().getRealm()).append("\"").append(",as_uri=\"")
+                        .append(authzClient.getServerConfiguration().getIssuer()).append("\"").append(",ticket=\"").append(ticket).append("\"").toString());
+            } else {
+                response.setStatus(403);
+            }
+
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug("Sending challenge");
+            }
+
+            return true;
+        }
+
+        handleAccessDenied(httpFacade);
+
         return true;
     }
 
@@ -106,28 +154,31 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
     }
 
     private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
+        if (getPolicyEnforcer().getDeployment().isBearerOnly() || (isBearerAuthorization(httpFacade) && getEnforcerConfig().getUserManagedAccess() != null)) {
+            return null;
+        }
+
         try {
             KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
             String accessTokenString = securityContext.getTokenString();
-            AuthzClient authzClient = getAuthzClient();
             KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
-            PermissionRequest permissionRequest = new PermissionRequest();
-
-            permissionRequest.setResourceId(pathConfig.getId());
-            permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
-
             AccessToken accessToken = securityContext.getToken();
-            AuthorizationRequest authzRequest;
+            AuthorizationRequest authzRequest = new AuthorizationRequest();
 
             if (getEnforcerConfig().getUserManagedAccess() != null) {
-                PermissionResponse permissionResponse = authzClient.protection().permission().create(permissionRequest);
-                authzRequest = new AuthorizationRequest();
-                authzRequest.setTicket(permissionResponse.getTicket());
+                String ticket = getPermissionTicket(pathConfig, methodConfig, getAuthzClient(), httpFacade);
+                authzRequest.setTicket(ticket);
             } else {
-                authzRequest = new AuthorizationRequest();
                 if (accessToken.getAuthorization() != null) {
                     authzRequest.addPermission(pathConfig.getId(), methodConfig.getScopes());
                 }
+
+                Map<String, List<String>> claims = getClaims(pathConfig, httpFacade);
+
+                if (!claims.isEmpty()) {
+                    authzRequest.setClaimTokenFormat("urn:ietf:params:oauth:token-type:jwt");
+                    authzRequest.setClaimToken(Base64.encodeBytes(JsonSerialization.writeValueAsBytes(claims)));
+                }
             }
 
             if (accessToken.getAuthorization() != null) {
@@ -135,18 +186,70 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
             }
 
             LOGGER.debug("Obtaining authorization for authenticated user.");
-            AuthorizationResponse authzResponse = authzClient.authorization(accessTokenString).authorize(authzRequest);
+            AuthorizationResponse authzResponse = getAuthzClient().authorization(accessTokenString).authorize(authzRequest);
 
             if (authzResponse != null) {
                 return AdapterRSATokenVerifier.verifyToken(authzResponse.getToken(), deployment);
             }
-
-            return null;
-        } catch (AuthorizationDeniedException e) {
-            LOGGER.debug("Authorization denied", e);
-            return null;
+        } catch (AuthorizationDeniedException ignore) {
+            LOGGER.debug("Authorization denied", ignore);
         } catch (Exception e) {
             throw new RuntimeException("Unexpected error during authorization request.", e);
         }
+
+        return null;
+    }
+
+    private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient, HttpFacade httpFacade) {
+        if (getEnforcerConfig().getUserManagedAccess() != null) {
+            ProtectionResource protection = authzClient.protection();
+            PermissionResource permission = protection.permission();
+            PermissionRequest permissionRequest = new PermissionRequest();
+
+            permissionRequest.setResourceId(pathConfig.getId());
+            permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
+
+            Map<String, List<String>> claims = getClaims(pathConfig, httpFacade);
+
+            if (!claims.isEmpty()) {
+                permissionRequest.setClaims(claims);
+            }
+
+            return permission.create(permissionRequest).getTicket();
+        }
+
+        return null;
+    }
+
+    private Map<String, List<String>> getClaims(PathConfig pathConfig, HttpFacade httpFacade) {
+        Map<String, List<String>> claims = new HashMap<>();
+        Map<String, Map<String, Object>> claimInformationPointConfig = pathConfig.getClaimInformationPointConfig();
+
+        if (claimInformationPointConfig != null) {
+            for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
+                ClaimInformationPointProviderFactory factory = getPolicyEnforcer().getClaimInformationPointProviderFactories().get(claimDef.getKey());
+
+                if (factory != null) {
+                    claims.putAll(factory.create(claimDef.getValue()).resolve(httpFacade));
+                }
+            }
+        }
+        return claims;
+    }
+
+    private boolean isBearerAuthorization(OIDCHttpFacade httpFacade) {
+        List<String> authHeaders = httpFacade.getRequest().getHeaders("Authorization");
+        if (authHeaders == null || authHeaders.size() == 0) {
+            return false;
+        }
+
+        for (String authHeader : authHeaders) {
+            String[] split = authHeader.trim().split("\\s+");
+            if (split == null || split.length != 2) continue;
+            if (!split[0].equalsIgnoreCase("Bearer")) continue;
+            return true;
+        }
+
+        return false;
     }
 }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
index 2ccdf28..50278b9 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java
@@ -21,10 +21,12 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.ServiceLoader;
 
 import org.jboss.logging.Logger;
 import org.keycloak.AuthorizationContext;
@@ -55,6 +57,7 @@ public class PolicyEnforcer {
     private final PolicyEnforcerConfig enforcerConfig;
     private final PathConfigMatcher pathMatcher;
     private final Map<String, PathConfig> paths;
+    private final Map<String, ClaimInformationPointProviderFactory> claimInformationPointProviderFactories = new HashMap<>();
 
     public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
         this.deployment = deployment;
@@ -80,20 +83,17 @@ public class PolicyEnforcer {
                 LOGGER.debug(pathConfig);
             }
         }
+
+        loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, ClaimInformationPointProviderFactory.class.getClassLoader()));
+        loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, Thread.currentThread().getContextClassLoader()));
     }
 
     public AuthorizationContext enforce(OIDCHttpFacade facade) {
         if (LOGGER.isDebugEnabled()) {
-            LOGGER.debugv("Policy enforcement is enable. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
+            LOGGER.debugv("Policy enforcement is enabled. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
         }
 
-        AuthorizationContext context;
-
-        if (deployment.isBearerOnly()) {
-            context = new BearerTokenPolicyEnforcer(this).authorize(facade);
-        } else {
-            context = new KeycloakAdapterPolicyEnforcer(this).authorize(facade);
-        }
+        AuthorizationContext context = new KeycloakAdapterPolicyEnforcer(this).authorize(facade);
 
         if (LOGGER.isDebugEnabled()) {
             LOGGER.debugv("Policy enforcement result for path [{0}] is : {1}", facade.getRequest().getURI(), context.isGranted() ? "GRANTED" : "DENIED");
@@ -126,6 +126,22 @@ public class PolicyEnforcer {
         return deployment;
     }
 
+    public Map<String, ClaimInformationPointProviderFactory> getClaimInformationPointProviderFactories() {
+        return claimInformationPointProviderFactories;
+    }
+
+    private void loadClaimInformationPointProviders(ServiceLoader<ClaimInformationPointProviderFactory> loader) {
+        Iterator<ClaimInformationPointProviderFactory> iterator = loader.iterator();
+
+        while (iterator.hasNext()) {
+            ClaimInformationPointProviderFactory factory = iterator.next();
+
+            factory.init(this);
+
+            claimInformationPointProviderFactories.put(factory.getName(), factory);
+        }
+    }
+
     private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
         boolean loadPathsFromServer = true;
 
@@ -164,6 +180,10 @@ public class PolicyEnforcer {
                 LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
                 List<ResourceRepresentation> resources = protectedResource.findByUri(path);
 
+                if (resources.isEmpty()) {
+                    resources = protectedResource.findByMatchingUri(path);
+                }
+
                 if (resources.size() == 1) {
                     resource = resources.get(0);
                 } else if (resources.size() > 1) {
@@ -173,16 +193,14 @@ public class PolicyEnforcer {
                 }
             }
 
-            if (resource == null) {
-                throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration.");
+            if (resource != null) {
+                pathConfig.setId(resource.getId());
             }
 
-            pathConfig.setId(resource.getId());
-
             PathConfig existingPath = null;
 
             for (PathConfig current : paths.values()) {
-                if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
+                if (current.getPath().equals(pathConfig.getPath())) {
                     existingPath = current;
                     break;
                 }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/JsonUtils.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/JsonUtils.java
new file mode 100644
index 0000000..36c6d9b
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/JsonUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 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.authorization.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class JsonUtils {
+
+    public static List<String> getValues(JsonNode jsonNode, String path) {
+        return getValues(jsonNode.at(path));
+    }
+
+    public static List<String> getValues(JsonNode jsonNode) {
+        List<String> values = new ArrayList<>();
+
+        if (jsonNode.isArray()) {
+            Iterator<JsonNode> iterator = jsonNode.iterator();
+
+            while (iterator.hasNext()) {
+                String value = iterator.next().textValue();
+
+                if (value != null) {
+                    values.add(value);
+                }
+            }
+        } else {
+            String value = jsonNode.textValue();
+
+            if (value != null) {
+                values.add(value);
+            }
+        }
+
+        return values;
+    }
+
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/KeycloakSecurityContextPlaceHolderResolver.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/KeycloakSecurityContextPlaceHolderResolver.java
new file mode 100644
index 0000000..d59cfd7
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/KeycloakSecurityContextPlaceHolderResolver.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 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.authorization.util;
+
+import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class KeycloakSecurityContextPlaceHolderResolver implements PlaceHolderResolver {
+
+    public static final String NAME = "keycloak";
+
+    @Override
+    public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
+        String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
+        OIDCHttpFacade oidcHttpFacade = OIDCHttpFacade.class.cast(httpFacade);
+        KeycloakSecurityContext securityContext = oidcHttpFacade.getSecurityContext();
+
+        if (securityContext == null) {
+            return null;
+        }
+
+        if (source.endsWith("access_token")) {
+            return Arrays.asList(securityContext.getTokenString());
+        }
+
+        if (source.endsWith("id_token")) {
+            return Arrays.asList(securityContext.getIdTokenString());
+        }
+
+        JsonNode jsonNode;
+
+        if (source.startsWith("access_token[")) {
+            jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getToken());
+        } else if (source.startsWith("id_token[")) {
+            jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getIdToken());
+        } else {
+            throw new RuntimeException("Invalid placeholder [" + placeHolder + "]");
+        }
+
+        return JsonUtils.getValues(jsonNode, getParameter(source, "Invalid placeholder [" + placeHolder + "]"));
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/PlaceHolderResolver.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/PlaceHolderResolver.java
new file mode 100644
index 0000000..62d6e1e
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/PlaceHolderResolver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 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.authorization.util;
+
+import java.util.List;
+
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface PlaceHolderResolver {
+
+    List<String> resolve(String placeHolder, HttpFacade httpFacade);
+
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/PlaceHolders.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/PlaceHolders.java
new file mode 100644
index 0000000..7a39dbf
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/PlaceHolders.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 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.authorization.util;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class PlaceHolders {
+
+    private static Map<String, PlaceHolderResolver> resolvers = new HashMap<>();
+
+    static {
+        resolvers.put(RequestPlaceHolderResolver.NAME, new RequestPlaceHolderResolver());
+        resolvers.put(KeycloakSecurityContextPlaceHolderResolver.NAME, new KeycloakSecurityContextPlaceHolderResolver());
+    }
+
+    private static Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(.+?)\\}");
+    private static Pattern PLACEHOLDER_PARAM_PATTERN = Pattern.compile("\\[(.+?)\\]");
+
+    public static List<String> resolve(String value, HttpFacade httpFacade) {
+        Map<String, List<String>> placeHolders = parsePlaceHolders(value, httpFacade);
+
+        if (!placeHolders.isEmpty()) {
+            value = formatPlaceHolder(value);
+
+            for (Entry<String, List<String>> entry : placeHolders.entrySet()) {
+                List<String> values = entry.getValue();
+
+                if (values.isEmpty() || values.size() > 1) {
+                    return values;
+                }
+
+                value = value.replaceAll(entry.getKey(), values.get(0)).trim();
+            }
+        }
+
+        return Arrays.asList(value);
+    }
+
+    static String getParameter(String source, String messageIfNotFound) {
+        Matcher matcher = PLACEHOLDER_PARAM_PATTERN.matcher(source);
+
+        while (matcher.find()) {
+            return matcher.group(1).replaceAll("'", "");
+        }
+
+        if (messageIfNotFound != null) {
+            throw new RuntimeException(messageIfNotFound);
+        }
+
+        return null;
+    }
+
+    private static Map<String, List<String>> parsePlaceHolders(String value, HttpFacade httpFacade) {
+        Map<String, List<String>> placeHolders = new HashMap<>();
+        Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
+
+        while (matcher.find()) {
+            String placeHolder = matcher.group(1);
+            int resolverNameIdx = placeHolder.indexOf('.');
+
+            if (resolverNameIdx == -1) {
+                throw new RuntimeException("Invalid placeholder [" + value + "]. Could not find resolver name.");
+            }
+
+            PlaceHolderResolver resolver = resolvers.get(placeHolder.substring(0, resolverNameIdx));
+
+            if (resolver != null) {
+                List<String> resolved = resolver.resolve(placeHolder, httpFacade);
+
+                if (resolved != null) {
+                    placeHolders.put(formatPlaceHolder(placeHolder), resolved);
+                }
+            }
+        }
+
+        return placeHolders;
+    }
+
+    private static String formatPlaceHolder(String placeHolder) {
+        return placeHolder.replaceAll("\\{", "").replace("}", "").replace("[", "").replace("]", "").replace("[", "").replace("]", "");
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/RequestPlaceHolderResolver.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/RequestPlaceHolderResolver.java
new file mode 100644
index 0000000..cdd362d
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/util/RequestPlaceHolderResolver.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2018 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.authorization.util;
+
+import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.HttpFacade.Cookie;
+import org.keycloak.adapters.spi.HttpFacade.Request;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class RequestPlaceHolderResolver implements PlaceHolderResolver {
+
+    static String NAME = "request";
+
+    @Override
+    public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
+        String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
+        Request request = httpFacade.getRequest();
+
+        if (source.startsWith("parameter")) {
+            String parameterName = getParameter(source, "Could not obtain parameter name from placeholder [" + source + "]");
+            String parameterValue = request.getQueryParamValue(parameterName);
+
+            if (parameterValue == null) {
+                parameterValue = request.getFirstParam(parameterName);
+            }
+
+            if (parameterValue != null) {
+                return Arrays.asList(parameterValue);
+            }
+        } else if (source.startsWith("header")) {
+            String headerName = getParameter(source, "Could not obtain header name from placeholder [" + source + "]");
+            List<String> headerValue = request.getHeaders(headerName);
+
+            if (headerValue != null) {
+                return headerValue;
+            }
+        } else if (source.startsWith("cookie")) {
+            String cookieName = getParameter(source, "Could not obtain cookie name from placeholder [" + source + "]");
+            Cookie cookieValue = request.getCookie(cookieName);
+
+            if (cookieValue != null) {
+                return Arrays.asList(cookieValue.getValue());
+            }
+        } else if (source.startsWith("remoteAddr")) {
+            String value = request.getRemoteAddr();
+
+            if (value != null) {
+                return Arrays.asList(value);
+            }
+        } else if (source.startsWith("method")) {
+            String value = request.getMethod();
+
+            if (value != null) {
+                return Arrays.asList(value);
+            }
+        } else if (source.startsWith("uri")) {
+            String value = request.getURI();
+
+            if (value != null) {
+                return Arrays.asList(value);
+            }
+        } else if (source.startsWith("relativePath")) {
+            String value = request.getRelativePath();
+
+            if (value != null) {
+                return Arrays.asList(value);
+            }
+        } else if (source.startsWith("secure")) {
+            return Arrays.asList(String.valueOf(request.isSecure()));
+        } else if (source.startsWith("body")) {
+            String contentType = request.getHeader("Content-Type");
+
+            if (contentType == null) {
+                contentType = "";
+            }
+
+            InputStream body = request.getInputStream(true);
+
+            try {
+                if (body == null || body.available() == 0) {
+                    return Collections.emptyList();
+                }
+            } catch (IOException cause) {
+                throw new RuntimeException("Failed to check available bytes in request input stream", cause);
+            }
+
+            if (body.markSupported()) {
+                body.mark(0);
+            }
+
+            List<String> values = new ArrayList<>();
+
+            try {
+                switch (contentType) {
+                    case "application/json":
+                        try {
+                            JsonNode jsonNode = JsonSerialization.mapper.readTree(new BufferedInputStream(body) {
+                                @Override
+                                public void close() {
+                                    // we can't close the stream because it may be used later by the application
+                                }
+                            });
+                            String path = getParameter(source, null);
+
+                            if (path == null) {
+                                values.addAll(JsonUtils.getValues(jsonNode));
+                            } else {
+                                values.addAll(JsonUtils.getValues(jsonNode, path));
+                            }
+                        } catch (IOException cause) {
+                            throw new RuntimeException("Could not extract claim from request JSON body", cause);
+                        }
+                        break;
+                    default:
+                        StringBuilder value = new StringBuilder();
+                        BufferedReader reader = new BufferedReader(new InputStreamReader(body));
+
+                        try {
+                            int ch;
+
+                            while ((ch = reader.read()) != -1) {
+                                value.append((char) ch);
+                            }
+                        } catch (IOException cause) {
+                            throw new RuntimeException("Could not extract claim from request body", cause);
+                        }
+
+                        values.add(value.toString());
+                }
+            } finally {
+                if (body.markSupported()) {
+                    try {
+                        body.reset();
+                    } catch (IOException cause) {
+                        throw new RuntimeException("Failed to reset request input stream", cause);
+                    }
+                }
+            }
+
+            return values;
+        }
+
+        return Collections.emptyList();
+    }
+}
diff --git a/adapters/oidc/adapter-core/src/main/resources/META-INF/services/org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory b/adapters/oidc/adapter-core/src/main/resources/META-INF/services/org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory
new file mode 100644
index 0000000..f40afed
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/resources/META-INF/services/org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory
@@ -0,0 +1,19 @@
+#
+# * Copyright 2018 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.
+#
+
+org.keycloak.adapters.authorization.cip.ClaimsInformationPointProviderFactory
+org.keycloak.adapters.authorization.cip.HttpClaimInformationPointProviderFactory
\ No newline at end of file
diff --git a/adapters/oidc/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java b/adapters/oidc/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
index 1a0eb9c..0d984eb 100755
--- a/adapters/oidc/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
+++ b/adapters/oidc/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
@@ -27,6 +27,9 @@ import javax.security.cert.X509Certificate;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.SecurityContext;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.List;
@@ -51,6 +54,8 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
 
     protected class RequestFacade implements OIDCHttpFacade.Request {
 
+        private InputStream inputStream;
+
         @Override
         public String getFirstParam(String param) {
             throw new RuntimeException("NOT IMPLEMENTED");
@@ -108,6 +113,19 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
 
         @Override
         public InputStream getInputStream() {
+            return getInputStream(false);
+        }
+
+        @Override
+        public InputStream getInputStream(boolean buffered) {
+            if (inputStream != null) {
+                return inputStream;
+            }
+
+            if (buffered) {
+                return inputStream = new BufferedInputStream(requestContext.getEntityStream());
+            }
+
             return requestContext.getEntityStream();
         }
 
diff --git a/adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 67c9f08..47c9a93 100755
--- a/adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -36,6 +36,8 @@ import javax.security.cert.X509Certificate;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
@@ -265,6 +267,8 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
         public Request getRequest() {
             return new Request() {
 
+                private InputStream inputStream;
+
                 @Override
                 public String getFirstParam(String param) {
                     return servletRequest.getParameter(param);
@@ -314,10 +318,27 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
 
                 @Override
                 public InputStream getInputStream() {
+                    return getInputStream(false);
+                }
+
+                @Override
+                public InputStream getInputStream(boolean buffered) {
+                    if (inputStream != null) {
+                        return inputStream;
+                    }
+
+                    if (buffered) {
+                        try {
+                            return inputStream = new BufferedInputStream(servletRequest.getInputStream());
+                        } catch (IOException e) {
+                            throw new RuntimeException(e);
+                        }
+                    }
+
                     try {
                         return servletRequest.getInputStream();
-                    } catch (IOException ioe) {
-                        throw new RuntimeException(ioe);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
                     }
                 }
 
diff --git a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
index 848ca45..f4fa074 100755
--- a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
+++ b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
@@ -24,6 +24,8 @@ import org.keycloak.adapters.spi.LogoutError;
 import org.springframework.util.Assert;
 
 import javax.servlet.http.HttpServletRequest;
+
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -40,6 +42,7 @@ import java.util.List;
 class WrappedHttpServletRequest implements Request {
 
     private final HttpServletRequest request;
+    private InputStream inputStream;
 
     /**
      * Creates a new request for the given <code>HttpServletRequest</code>
@@ -122,10 +125,27 @@ class WrappedHttpServletRequest implements Request {
 
     @Override
     public InputStream getInputStream() {
+        return getInputStream(false);
+    }
+
+    @Override
+    public InputStream getInputStream(boolean buffered) {
+        if (inputStream != null) {
+            return inputStream;
+        }
+
+        if (buffered) {
+            try {
+                return inputStream = new BufferedInputStream(request.getInputStream());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
         try {
             return request.getInputStream();
         } catch (IOException e) {
-            throw new RuntimeException("Unable to get request input stream", e);
+            throw new RuntimeException(e);
         }
     }
 
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java
index 543e848..bda24bb 100644
--- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java
@@ -40,6 +40,8 @@ import org.wildfly.security.http.Scope;
 
 import javax.security.auth.callback.CallbackHandler;
 import javax.security.cert.X509Certificate;
+
+import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -158,6 +160,8 @@ class ElytronHttpFacade implements OIDCHttpFacade {
     @Override
     public Request getRequest() {
         return new Request() {
+            private InputStream inputStream;
+
             @Override
             public String getMethod() {
                 return request.getRequestMethod();
@@ -184,7 +188,7 @@ class ElytronHttpFacade implements OIDCHttpFacade {
 
             @Override
             public String getFirstParam(String param) {
-                throw new RuntimeException("Not implemented.");
+                return request.getFirstParameterValue(param);
             }
 
             @Override
@@ -230,6 +234,19 @@ class ElytronHttpFacade implements OIDCHttpFacade {
 
             @Override
             public InputStream getInputStream() {
+                return getInputStream(false);
+            }
+
+            @Override
+            public InputStream getInputStream(boolean buffered) {
+                if (inputStream != null) {
+                    return inputStream;
+                }
+
+                if (buffered) {
+                    return inputStream = new BufferedInputStream(request.getInputStream());
+                }
+
                 return request.getInputStream();
             }
 
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronHttpFacade.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronHttpFacade.java
index 1458dd9..8b31a31 100644
--- a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronHttpFacade.java
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronHttpFacade.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.adapters.saml.elytron;
 
+import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -146,6 +147,8 @@ class ElytronHttpFacade implements HttpFacade {
     @Override
     public Request getRequest() {
         return new Request() {
+            private InputStream inputStream;
+
             @Override
             public String getMethod() {
                 return request.getRequestMethod();
@@ -207,6 +210,19 @@ class ElytronHttpFacade implements HttpFacade {
 
             @Override
             public InputStream getInputStream() {
+                return getInputStream(false);
+            }
+
+            @Override
+            public InputStream getInputStream(boolean buffered) {
+                if (inputStream != null) {
+                    return inputStream;
+                }
+
+                if (buffered) {
+                    return inputStream = new BufferedInputStream(request.getInputStream());
+                }
+
                 return request.getInputStream();
             }
 
diff --git a/adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java b/adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java
index 2429286..0636ebc 100755
--- a/adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java
+++ b/adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java
@@ -69,6 +69,7 @@ public interface HttpFacade {
         String getHeader(String name);
         List<String> getHeaders(String name);
         InputStream getInputStream();
+        InputStream getInputStream(boolean buffered);
 
         String getRemoteAddr();
         void setError(AuthenticationError error);
diff --git a/adapters/spi/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java b/adapters/spi/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java
index dac7973..d097ee4 100755
--- a/adapters/spi/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java
+++ b/adapters/spi/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java
@@ -25,6 +25,8 @@ import org.keycloak.common.util.UriUtils;
 
 import javax.security.cert.X509Certificate;
 import javax.servlet.http.HttpServletResponse;
+
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -69,6 +71,9 @@ public class JettyHttpFacade implements HttpFacade {
     }
 
     protected class RequestFacade implements Request {
+
+        private InputStream inputStream;
+
         @Override
         public String getURI() {
             StringBuffer buf = request.getRequestURL();
@@ -128,6 +133,23 @@ public class JettyHttpFacade implements HttpFacade {
 
         @Override
         public InputStream getInputStream() {
+            return getInputStream(false);
+        }
+
+        @Override
+        public InputStream getInputStream(boolean buffered) {
+            if (inputStream != null) {
+                return inputStream;
+            }
+
+            if (buffered) {
+                try {
+                    return inputStream = new BufferedInputStream(request.getInputStream());
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
             try {
                 return request.getInputStream();
             } catch (IOException e) {
diff --git a/adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java b/adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
index 11e4f93..f77b1e3 100755
--- a/adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
+++ b/adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
@@ -27,6 +27,8 @@ import org.keycloak.common.util.UriUtils;
 import javax.security.cert.X509Certificate;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -51,6 +53,9 @@ public class ServletHttpFacade implements HttpFacade {
     }
 
     protected class RequestFacade implements Request {
+
+        private InputStream inputStream;
+
         @Override
         public String getMethod() {
             return request.getMethod();
@@ -132,6 +137,23 @@ public class ServletHttpFacade implements HttpFacade {
 
         @Override
         public InputStream getInputStream() {
+            return getInputStream(false);
+        }
+
+        @Override
+        public InputStream getInputStream(boolean buffered) {
+            if (inputStream != null) {
+                return inputStream;
+            }
+
+            if (buffered) {
+                try {
+                    return inputStream = new BufferedInputStream(request.getInputStream());
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
             try {
                 return request.getInputStream();
             } catch (IOException e) {
diff --git a/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
index 631474e..c2813b2 100755
--- a/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
+++ b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
@@ -26,6 +26,8 @@ import org.keycloak.common.util.UriUtils;
 
 import javax.security.cert.X509Certificate;
 import javax.servlet.http.HttpServletResponse;
+
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -69,6 +71,9 @@ public class CatalinaHttpFacade implements HttpFacade {
     }
 
     protected class RequestFacade implements Request {
+
+        private InputStream inputStream;
+
         @Override
         public String getURI() {
             StringBuffer buf = request.getRequestURL();
@@ -136,6 +141,23 @@ public class CatalinaHttpFacade implements HttpFacade {
 
         @Override
         public InputStream getInputStream() {
+            return getInputStream(false);
+        }
+
+        @Override
+        public InputStream getInputStream(boolean buffered) {
+            if (inputStream != null) {
+                return inputStream;
+            }
+
+            if (buffered) {
+                try {
+                    return inputStream = new BufferedInputStream(request.getInputStream());
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
             try {
                 return request.getInputStream();
             } catch (IOException e) {
diff --git a/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
index 21102f1..d47b363 100755
--- a/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
+++ b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -19,6 +19,10 @@ package org.keycloak.adapters.undertow;
 
 import io.undertow.server.HttpServerExchange;
 import io.undertow.server.handlers.CookieImpl;
+import io.undertow.server.handlers.form.FormData;
+import io.undertow.server.handlers.form.FormData.FormValue;
+import io.undertow.server.handlers.form.FormDataParser;
+import io.undertow.server.handlers.form.FormParserFactory;
 import io.undertow.util.AttachmentKey;
 import io.undertow.util.Headers;
 import io.undertow.util.HttpString;
@@ -28,6 +32,8 @@ import org.keycloak.adapters.spi.LogoutError;
 import org.keycloak.common.util.KeycloakUriBuilder;
 
 import javax.security.cert.X509Certificate;
+
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -75,6 +81,11 @@ public class UndertowHttpFacade implements HttpFacade {
     }
 
     protected class RequestFacade implements Request {
+
+        private InputStream inputStream;
+        private final FormParserFactory formParserFactory = FormParserFactory.builder().build();
+        private FormData formData;
+
         @Override
         public String getURI() {
             KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
@@ -96,7 +107,34 @@ public class UndertowHttpFacade implements HttpFacade {
 
         @Override
         public String getFirstParam(String param) {
-            throw new RuntimeException("Not implemented yet");
+            Deque<String> values = exchange.getQueryParameters().get(param);
+
+            if (values != null && !values.isEmpty()) {
+                return values.getFirst();
+            }
+
+            if (formData == null && "post".equalsIgnoreCase(getMethod())) {
+                FormDataParser parser = formParserFactory.createParser(exchange);
+                try {
+                    formData = parser.parseBlocking();
+                } catch (IOException cause) {
+                    throw new RuntimeException("Failed to parse form parameters", cause);
+                }
+            }
+
+            if (formData != null) {
+                Deque<FormValue> formValues = formData.get(param);
+
+                if (formValues != null && !formValues.isEmpty()) {
+                    FormValue firstValue = formValues.getFirst();
+
+                    if (!firstValue.isFile()) {
+                        return firstValue.getValue();
+                    }
+                }
+            }
+
+            return null;
         }
 
         @Override
@@ -136,7 +174,21 @@ public class UndertowHttpFacade implements HttpFacade {
 
         @Override
         public InputStream getInputStream() {
+            return getInputStream(false);
+        }
+
+        @Override
+        public InputStream getInputStream(boolean buffered) {
             if (!exchange.isBlocking()) exchange.startBlocking();
+
+            if (inputStream != null) {
+                return inputStream;
+            }
+
+            if (buffered) {
+                return inputStream = new BufferedInputStream(exchange.getInputStream());
+            }
+
             return exchange.getInputStream();
         }
 
diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 36778e1..542712d 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -88,6 +88,9 @@ public class AccessToken extends IDToken {
         @JsonProperty("permissions")
         private List<Permission> permissions;
 
+        @JsonProperty("claims")
+        private Map<String, List<String>> claims;
+
         public List<Permission> getPermissions() {
             return permissions;
         }
@@ -95,6 +98,14 @@ public class AccessToken extends IDToken {
         public void setPermissions(List<Permission> permissions) {
             this.permissions = permissions;
         }
+
+        public void setClaims(Map<String, List<String>> claims) {
+            this.claims = claims;
+        }
+
+        public Map<String, List<String>> getClaims() {
+            return claims;
+        }
     }
 
     @JsonProperty("trusted-certs")
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
index d01d7c5..81dc506 100644
--- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
@@ -19,6 +19,7 @@ package org.keycloak.representations.adapters.config;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonInclude;
@@ -139,6 +140,9 @@ public class PolicyEnforcerConfig {
         @JsonProperty("enforcement-mode")
         private EnforcementMode enforcementMode = EnforcementMode.ENFORCING;
 
+        @JsonProperty("claim-information-point")
+        private Map<String, Map<String, Object>> claimInformationPointConfig;
+
         @JsonIgnore
         private PathConfig parentConfig;
 
@@ -198,6 +202,14 @@ public class PolicyEnforcerConfig {
             this.enforcementMode = enforcementMode;
         }
 
+        public Map<String, Map<String, Object>> getClaimInformationPointConfig() {
+            return claimInformationPointConfig;
+        }
+
+        public void setClaimInformationPointConfig(Map<String, Map<String, Object>> claimInformationPointConfig) {
+            this.claimInformationPointConfig = claimInformationPointConfig;
+        }
+
         @Override
         public String toString() {
             return "PathConfig{" +
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java b/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java
index 764ae02..14f1f3d 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 
 import org.keycloak.representations.idm.authorization.PermissionTicketToken.ResourcePermission;
 
@@ -40,6 +41,7 @@ public class AuthorizationRequest {
     private String audience;
     private String accessToken;
     private boolean submitRequest;
+    private Map<String, List<String>> claims;
 
     public AuthorizationRequest(String ticket) {
         this.ticket = ticket;
@@ -129,6 +131,14 @@ public class AuthorizationRequest {
         return accessToken;
     }
 
+    public Map<String, List<String>> getClaims() {
+        return claims;
+    }
+
+    public void setClaims(Map<String, List<String>> claims) {
+        this.claims = claims;
+    }
+
     public void addPermission(String resourceId, List<String> scopes) {
         addPermission(resourceId, scopes.toArray(new String[scopes.size()]));
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java b/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java
index ed392f0..53760c3 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java
@@ -16,13 +16,14 @@
  */
 package org.keycloak.representations.idm.authorization;
 
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
@@ -72,6 +73,21 @@ public class Permission {
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Permission that = (Permission) o;
+
+        return getResourceId().equals(that.resourceId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(resourceId);
+    }
+
+    @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
 
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionRequest.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionRequest.java
index 5830e16..bd2a94b 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionRequest.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionRequest.java
@@ -18,10 +18,15 @@
 package org.keycloak.representations.idm.authorization;
 
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.keycloak.json.StringListMapDeserializer;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -32,6 +37,9 @@ public class PermissionRequest {
     private Set<String> scopes;
     private String resourceServerId;
 
+    @JsonDeserialize(using = StringListMapDeserializer.class)
+    private Map<String, List<String>> claims;
+
     public PermissionRequest(String resourceId, String... scopes) {
         this.resourceId = resourceId;
         if (scopes != null) {
@@ -69,4 +77,28 @@ public class PermissionRequest {
     public String getResourceServerId() {
         return resourceServerId;
     }
+
+    public Map<String, List<String>> getClaims() {
+        return claims;
+    }
+
+    public void setClaims(Map<String, List<String>> claims) {
+        this.claims = claims;
+    }
+
+    public void setClaim(String name, String... value) {
+        if (claims == null) {
+            claims = new HashMap<>();
+        }
+
+        claims.put(name, Arrays.asList(value));
+    }
+
+    public void addScope(String... name) {
+        if (scopes == null) {
+            scopes = new HashSet<>();
+        }
+
+        scopes.addAll(Arrays.asList(name));
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionTicketToken.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionTicketToken.java
index ff4a927..a9f6ba5 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionTicketToken.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PermissionTicketToken.java
@@ -18,10 +18,13 @@ package org.keycloak.representations.idm.authorization;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import org.keycloak.TokenIdGenerator;
+import org.keycloak.json.StringListMapDeserializer;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.JsonWebToken;
 
@@ -32,6 +35,9 @@ public class PermissionTicketToken extends JsonWebToken {
 
     private final List<ResourcePermission> resources;
 
+    @JsonDeserialize(using = StringListMapDeserializer.class)
+    private Map<String, List<String>> claims;
+
     public PermissionTicketToken() {
         this(new ArrayList<ResourcePermission>());
     }
@@ -59,6 +65,14 @@ public class PermissionTicketToken extends JsonWebToken {
         return this.resources;
     }
 
+    public Map<String, List<String>> getClaims() {
+        return claims;
+    }
+
+    public void setClaims(Map<String, List<String>> claims) {
+        this.claims = claims;
+    }
+
     public static class ResourcePermission {
 
         @JsonProperty("id")
diff --git a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
index cd912d7..ef84de1 100755
--- a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
+++ b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
@@ -43,6 +43,8 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -171,6 +173,8 @@ public class OfflineAccessPortalServlet extends HttpServlet {
             public Request getRequest() {
                 return new Request() {
 
+                    private InputStream inputStream;
+
                     @Override
                     public String getMethod() {
                         return servletRequest.getMethod();
@@ -220,10 +224,27 @@ public class OfflineAccessPortalServlet extends HttpServlet {
 
                     @Override
                     public InputStream getInputStream() {
+                        return getInputStream(false);
+                    }
+
+                    @Override
+                    public InputStream getInputStream(boolean buffered) {
+                        if (inputStream != null) {
+                            return inputStream;
+                        }
+
+                        if (buffered) {
+                            try {
+                                return inputStream = new BufferedInputStream(servletRequest.getInputStream());
+                            } catch (IOException e) {
+                                throw new RuntimeException(e);
+                            }
+                        }
+
                         try {
                             return servletRequest.getInputStream();
-                        } catch (IOException ioe) {
-                            throw new RuntimeException(ioe);
+                        } catch (IOException e) {
+                            throw new RuntimeException(e);
                         }
                     }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java b/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
index 0719bab..8f33fcf 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
@@ -142,5 +142,9 @@ public interface Attributes {
         public long asLong(int idx) {
             return Long.parseLong(asString(idx));
         }
+
+        public double asDouble(int idx) {
+            return Double.parseDouble(asString(idx));
+        }
     }
 }
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
index 989d228..b791f7f 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.authorization.authorization;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -54,6 +55,7 @@ import org.keycloak.authorization.store.ScopeStore;
 import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.authorization.util.Permissions;
 import org.keycloak.authorization.util.Tokens;
+import org.keycloak.common.util.Base64Url;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.JWSInputException;
@@ -77,12 +79,15 @@ import org.keycloak.representations.idm.authorization.PermissionTicketToken;
 import org.keycloak.services.CorsErrorResponseException;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.resources.Cors;
+import org.keycloak.util.JsonSerialization;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class AuthorizationTokenService {
 
+    public static final String CLAIM_TOKEN_FORMAT_ID_TOKEN = "http://openid.net/specs/openid-connect-core-1_0.html#IDToken";
+
     private static final Logger logger = Logger.getLogger(AuthorizationTokenService.class);
     private static Map<String, BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext>> SUPPORTED_CLAIM_TOKEN_FORMATS;
 
@@ -91,17 +96,30 @@ public class AuthorizationTokenService {
         SUPPORTED_CLAIM_TOKEN_FORMATS.put("urn:ietf:params:oauth:token-type:jwt", (authorizationRequest, authorization) -> {
             String claimToken = authorizationRequest.getClaimToken();
 
-            if (claimToken == null) {
-                claimToken = authorizationRequest.getAccessToken();
+            if (claimToken != null) {
+                try {
+                    Map claims = JsonSerialization.readValue(Base64Url.decode(authorizationRequest.getClaimToken()), Map.class);
+                    authorizationRequest.setClaims(claims);
+                    return new KeycloakEvaluationContext(new KeycloakIdentity(authorization.getKeycloakSession(), Tokens.getAccessToken(authorizationRequest.getAccessToken(), authorization.getKeycloakSession())), claims, authorization.getKeycloakSession());
+                } catch (IOException cause) {
+                    throw new RuntimeException("Failed to map claims from claim token [" + claimToken + "]", cause);
+                }
             }
 
-            return new KeycloakEvaluationContext(new KeycloakIdentity(authorization.getKeycloakSession(), Tokens.getAccessToken(claimToken, authorization.getKeycloakSession())), authorization.getKeycloakSession());
+            throw new RuntimeException("Claim token can not be null");
         });
-        SUPPORTED_CLAIM_TOKEN_FORMATS.put("http://openid.net/specs/openid-connect-core-1_0.html#IDToken", (authorizationRequest, authorization) -> {
+        SUPPORTED_CLAIM_TOKEN_FORMATS.put(CLAIM_TOKEN_FORMAT_ID_TOKEN, (authorizationRequest, authorization) -> {
             try {
                 KeycloakSession keycloakSession = authorization.getKeycloakSession();
-                IDToken idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, authorization.getRealm(), authorizationRequest.getClaimToken());
-                return new KeycloakEvaluationContext(new KeycloakIdentity(keycloakSession, idToken), keycloakSession);
+                RealmModel realm = authorization.getRealm();
+                String accessToken = authorizationRequest.getAccessToken();
+
+                if (accessToken == null) {
+                    throw new RuntimeException("Claim token can not be null and must be a valid IDToken");
+                }
+
+                IDToken idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, realm, accessToken);
+                return new KeycloakEvaluationContext(new KeycloakIdentity(keycloakSession, idToken), authorizationRequest.getClaims(), keycloakSession);
             } catch (OAuthErrorException cause) {
                 throw new RuntimeException("Failed to verify ID token", cause);
             }
@@ -127,8 +145,16 @@ public class AuthorizationTokenService {
             throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Invalid authorization request.", Status.BAD_REQUEST);
         }
 
+        // it is not secure to allow public clients to push arbitrary claims because message can be tampered
+        if (isPublicClientRequestingEntitlemesWithClaims(request)) {
+            throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Public clients are not allowed to send claims", Status.FORBIDDEN);
+        }
+
         try {
             PermissionTicketToken ticket = getPermissionTicket(request);
+
+            request.setClaims(ticket.getClaims());
+
             ResourceServer resourceServer = getResourceServer(ticket);
             KeycloakEvaluationContext evaluationContext = createEvaluationContext(request);
             KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity());
@@ -153,7 +179,7 @@ public class AuthorizationTokenService {
             }
 
             ClientModel targetClient = this.authorization.getRealm().getClientById(resourceServer.getId());
-            AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(identity, permissions, targetClient), request.getRpt() != null);
+            AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(identity, permissions, request, targetClient), request.getRpt() != null);
 
             return Cors.add(httpRequest, Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response))
                     .allowedOrigins(getKeycloakSession().getContext().getUri(), targetClient)
@@ -170,6 +196,10 @@ public class AuthorizationTokenService {
         }
     }
 
+    private boolean isPublicClientRequestingEntitlemesWithClaims(AuthorizationRequest request) {
+        return request.getClaimToken() != null && getKeycloakSession().getContext().getClient().isPublicClient() && request.getTicket() == null;
+    }
+
     private List<Result> evaluatePermissions(AuthorizationRequest authorizationRequest, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
         return authorization.evaluators()
                 .from(createPermissions(ticket, authorizationRequest, resourceServer, identity, authorization), evaluationContext)
@@ -188,7 +218,7 @@ public class AuthorizationTokenService {
                 .evaluate();
     }
 
-    private AccessTokenResponse createRequestingPartyToken(KeycloakIdentity identity, List<Permission> entitlements, ClientModel targetClient) {
+    private AccessTokenResponse createRequestingPartyToken(KeycloakIdentity identity, List<Permission> entitlements, AuthorizationRequest request, ClientModel targetClient) {
         KeycloakSession keycloakSession = getKeycloakSession();
         AccessToken accessToken = identity.getAccessToken();
         UserSessionModel userSessionModel = keycloakSession.sessions().getUserSession(getRealm(), accessToken.getSessionState());
@@ -205,6 +235,7 @@ public class AuthorizationTokenService {
         Authorization authorization = new Authorization();
 
         authorization.setPermissions(entitlements);
+        authorization.setClaims(request.getClaims());
 
         rpt.setAuthorization(authorization);
 
@@ -264,7 +295,7 @@ public class AuthorizationTokenService {
         String claimTokenFormat = authorizationRequest.getClaimTokenFormat();
 
         if (claimTokenFormat == null) {
-            claimTokenFormat = "urn:ietf:params:oauth:token-type:jwt";
+            claimTokenFormat = CLAIM_TOKEN_FORMAT_ID_TOKEN;
         }
 
         BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat);
diff --git a/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java b/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java
index dec33cd..e740cdf 100644
--- a/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java
+++ b/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java
@@ -22,7 +22,6 @@ import org.keycloak.authorization.attribute.Attributes;
 import org.keycloak.authorization.identity.Identity;
 import org.keycloak.authorization.policy.evaluation.EvaluationContext;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.representations.AccessToken;
 
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
@@ -31,6 +30,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -39,10 +39,16 @@ public class DefaultEvaluationContext implements EvaluationContext {
 
     protected final KeycloakSession keycloakSession;
     protected final Identity identity;
+    private final Map<String, List<String>> claims;
 
     public DefaultEvaluationContext(Identity identity, KeycloakSession keycloakSession) {
-        this.keycloakSession = keycloakSession;
+        this(identity, null, keycloakSession);
+    }
+
+    public DefaultEvaluationContext(Identity identity, Map<String, List<String>> claims, KeycloakSession keycloakSession) {
         this.identity = identity;
+        this.claims = claims;
+        this.keycloakSession = keycloakSession;
     }
 
     @Override
@@ -51,7 +57,7 @@ public class DefaultEvaluationContext implements EvaluationContext {
     }
 
     public Map<String, Collection<String>> getBaseAttributes() {
-        HashMap<String, Collection<String>> attributes = new HashMap<>();
+        Map<String, Collection<String>> attributes = new HashMap<>();
 
         attributes.put("kc.time.date_time", Arrays.asList(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
         attributes.put("kc.client.network.ip_address", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteAddr()));
@@ -65,6 +71,12 @@ public class DefaultEvaluationContext implements EvaluationContext {
 
         attributes.put("kc.realm.name", Arrays.asList(this.keycloakSession.getContext().getRealm().getName()));
 
+        if (claims != null) {
+            for (Entry<String, List<String>> entry : claims.entrySet()) {
+                attributes.put(entry.getKey(), entry.getValue());
+            }
+        }
+
         return attributes;
     }
 
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java
index 047ff5a..bbb4218 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java
@@ -20,6 +20,7 @@ package org.keycloak.authorization.common;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 import org.keycloak.authorization.identity.Identity;
@@ -34,7 +35,11 @@ public class KeycloakEvaluationContext extends DefaultEvaluationContext {
     private final KeycloakIdentity identity;
 
     public KeycloakEvaluationContext(KeycloakIdentity identity, KeycloakSession keycloakSession) {
-        super(identity, keycloakSession);
+        this(identity, null, keycloakSession);
+    }
+
+    public KeycloakEvaluationContext(KeycloakIdentity identity, Map<String, List<String>> claims, KeycloakSession keycloakSession) {
+        super(identity, claims, keycloakSession);
         this.identity = identity;
     }
 
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
index b2ea5d4..c02ecb5 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
@@ -112,7 +112,6 @@ public class KeycloakIdentity implements Identity {
             if (token instanceof AccessToken) {
                 this.accessToken = AccessToken.class.cast(token);
             } else {
-                UserModel userById = keycloakSession.users().getUserById(token.getSubject(), realm);
                 UserSessionModel userSession = keycloakSession.sessions().getUserSession(realm, token.getSessionState());
                 ClientModel client = realm.getClientByClientId(token.getIssuedFor());
                 AuthenticatedClientSessionModel clientSessionModel = userSession.getAuthenticatedClientSessions().get(client.getId());
@@ -123,7 +122,7 @@ public class KeycloakIdentity implements Identity {
                         requestedRoles.add(role);
                     }
                 }
-                this.accessToken = new TokenManager().createClientAccessToken(keycloakSession, requestedRoles, realm, client, userById, userSession, clientSessionModel);
+                this.accessToken = new TokenManager().createClientAccessToken(keycloakSession, requestedRoles, realm, client, userSession.getUser(), userSession, clientSessionModel);
             }
 
             AccessToken.Access realmAccess = this.accessToken.getRealmAccess();
diff --git a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
index 7dd3496..e045d31 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
@@ -16,32 +16,30 @@
  */
 package org.keycloak.authorization.protection.permission;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.Response;
+
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.common.KeycloakIdentity;
 import org.keycloak.authorization.model.Resource;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
-import org.keycloak.models.ClientModel;
-import org.keycloak.representations.idm.authorization.PermissionRequest;
-import org.keycloak.representations.idm.authorization.PermissionResponse;
 import org.keycloak.authorization.store.ResourceStore;
 import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeyManager;
-import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.authorization.PermissionRequest;
+import org.keycloak.representations.idm.authorization.PermissionResponse;
 import org.keycloak.representations.idm.authorization.PermissionTicketToken;
-import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
-import org.keycloak.representations.idm.authorization.ResourceRepresentation;
-import org.keycloak.representations.idm.authorization.ScopeRepresentation;
 import org.keycloak.services.ErrorResponseException;
 
-import javax.ws.rs.core.Response;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
@@ -65,9 +63,9 @@ public class AbstractPermissionService {
         return Response.status(Response.Status.CREATED).entity(new PermissionResponse(createPermissionTicket(request))).build();
     }
 
-    private List<ResourceRepresentation> verifyRequestedResource(List<PermissionRequest> request) {
+    private List<PermissionTicketToken.ResourcePermission> verifyRequestedResource(List<PermissionRequest> request) {
         ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
-        List<ResourceRepresentation> requestedResources = new ArrayList<>();
+        List<PermissionTicketToken.ResourcePermission> requestedResources = new ArrayList<>();
 
         for (PermissionRequest permissionRequest : request) {
             String resourceSetId = permissionRequest.getResourceId();
@@ -104,19 +102,10 @@ public class AbstractPermissionService {
             }
 
             if (resources.isEmpty()) {
-                requestedResources.add(new ResourceRepresentation(null, verifyRequestedScopes(permissionRequest, null)));
-
+                requestedResources.add(new PermissionTicketToken.ResourcePermission(null, verifyRequestedScopes(permissionRequest, null)));
             } else {
                 for (Resource resource : resources) {
-                    Set<ScopeRepresentation> scopes = verifyRequestedScopes(permissionRequest, resource);
-
-                    ResourceRepresentation representation = new ResourceRepresentation(resource.getName(), scopes);
-
-                    representation.setId(resource.getId());
-                    representation.setOwnerManagedAccess(resource.isOwnerManagedAccess());
-                    representation.setOwner(new ResourceOwnerRepresentation(resource.getOwner()));
-
-                    requestedResources.add(representation);
+                    requestedResources.add(new PermissionTicketToken.ResourcePermission(resource.getId(), verifyRequestedScopes(permissionRequest, resource)));
                 }
             }
         }
@@ -124,7 +113,7 @@ public class AbstractPermissionService {
         return requestedResources;
     }
 
-    private Set<ScopeRepresentation> verifyRequestedScopes(PermissionRequest request, Resource resource) {
+    private Set<String> verifyRequestedScopes(PermissionRequest request, Resource resource) {
         Set<String> requestScopes = request.getScopes();
 
         if (requestScopes == null) {
@@ -153,24 +142,31 @@ public class AbstractPermissionService {
                 throw new ErrorResponseException("invalid_scope", "Scope [" + scopeName + "] is invalid", Response.Status.BAD_REQUEST);
             }
 
-            return ModelToRepresentation.toRepresentation(scope);
+            return scope.getName();
         }).collect(Collectors.toSet());
     }
 
     private String createPermissionTicket(List<PermissionRequest> request) {
-        List<PermissionTicketToken.ResourcePermission> permissions = verifyRequestedResource(request).stream().flatMap(resource -> {
-            List<PermissionTicketToken.ResourcePermission> perms = new ArrayList<>();
-            Set<ScopeRepresentation> scopes = resource.getScopes();
-
-            perms.add(new PermissionTicketToken.ResourcePermission(resource.getId(), scopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet())));
-
-            return perms.stream();
-        }).collect(Collectors.toList());
+        List<PermissionTicketToken.ResourcePermission> permissions = verifyRequestedResource(request);
 
         KeyManager.ActiveRsaKey keys = this.authorization.getKeycloakSession().keys().getActiveRsaKey(this.authorization.getRealm());
         ClientModel targetClient = authorization.getRealm().getClientById(resourceServer.getId());
+        PermissionTicketToken token = new PermissionTicketToken(permissions, targetClient.getClientId(), this.identity.getAccessToken());
+        Map<String, List<String>> claims = new HashMap<>();
+
+        for (PermissionRequest permissionRequest : request) {
+            Map<String, List<String>> requestClaims = permissionRequest.getClaims();
+
+            if (requestClaims != null) {
+                claims.putAll(requestClaims);
+            }
+        }
+
+        if (!claims.isEmpty()) {
+            token.setClaims(claims);
+        }
 
-        return new JWSBuilder().kid(keys.getKid()).jsonContent(new PermissionTicketToken(permissions, targetClient.getClientId(), this.identity.getAccessToken()))
+        return new JWSBuilder().kid(keys.getKid()).jsonContent(token)
                 .rsa256(keys.getPrivateKey());
     }
 }
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 38d2489..b6e1050 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -997,6 +997,8 @@ public class TokenEndpoint {
             accessTokenString = new AppAuthManager().extractAuthorizationHeaderToken(headers);
         }
 
+        // we allow public clients to authenticate using a bearer token, where the token should be a valid access token.
+        // public clients don't have secret and should be able to obtain a RPT by providing an access token previously issued by the server
         if (accessTokenString != null) {
             AccessToken accessToken = Tokens.getAccessToken(session);
 
@@ -1004,7 +1006,11 @@ public class TokenEndpoint {
                 throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Invalid bearer token", Status.UNAUTHORIZED);
             }
 
-            cors.allowedOrigins(uriInfo, realm.getClientByClientId(accessToken.getIssuedFor()));
+            ClientModel client = realm.getClientByClientId(accessToken.getIssuedFor());
+
+            session.getContext().setClient(client);
+
+            cors.allowedOrigins(uriInfo, client);
         }
 
         String claimToken = null;
@@ -1014,18 +1020,30 @@ public class TokenEndpoint {
             claimToken = formParams.get("claim_token").get(0);
         }
 
+        String claimTokenFormat = formParams.getFirst("claim_token_format");
+
+        if (claimToken != null && claimTokenFormat == null) {
+            claimTokenFormat = AuthorizationTokenService.CLAIM_TOKEN_FORMAT_ID_TOKEN;
+        }
+
         if (accessTokenString == null) {
             // in case no bearer token is provided, we force client authentication
             checkClient();
-            // Clients need to authenticate in order to obtain a RPT from the server.
-            // In order to support cases where the client is obtaining permissions on its on behalf, we issue a temporary access token
-            accessTokenString = AccessTokenResponse.class.cast(clientCredentialsGrant().getEntity()).getToken();
+
+            // if a claim token is provided, we check if the format is a OpenID Connect IDToken and assume the token represents the identity asking for permissions
+            if (AuthorizationTokenService.CLAIM_TOKEN_FORMAT_ID_TOKEN.equalsIgnoreCase(claimTokenFormat)) {
+                accessTokenString = claimToken;
+            } else {
+                // Clients need to authenticate in order to obtain a RPT from the server.
+                // In order to support cases where the client is obtaining permissions on its on behalf, we issue a temporary access token
+                accessTokenString = AccessTokenResponse.class.cast(clientCredentialsGrant().getEntity()).getToken();
+            }
         }
 
         AuthorizationRequest authorizationRequest = new AuthorizationRequest(formParams.getFirst("ticket"));
 
         authorizationRequest.setClaimToken(claimToken);
-        authorizationRequest.setClaimTokenFormat(formParams.getFirst("claim_token_format"));
+        authorizationRequest.setClaimTokenFormat(claimTokenFormat);
         authorizationRequest.setPct(formParams.getFirst("pct"));
         authorizationRequest.setRpt(formParams.getFirst("rpt"));
         authorizationRequest.setScope(formParams.getFirst("scope"));
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-claim-information-point-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-claim-information-point-authz-service.json
new file mode 100644
index 0000000..04f0906
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/keycloak-claim-information-point-authz-service.json
@@ -0,0 +1,25 @@
+{
+  "realm": "servlet-authz",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://localhost:8180/auth",
+  "ssl-required" : "external",
+  "resource" : "servlet-authz-app",
+  "public-client" : false,
+  "credentials": {
+    "secret": "secret"
+  },
+  "policy-enforcer": {
+    "on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp",
+    "lazy-load-paths": true,
+    "paths": [
+      {
+        "path": "/protected/context/context.jsp",
+        "claim-information-point": {
+          "claims": {
+            "request-claim": "{request.parameter['request-claim']}"
+          }
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
index b074ebc..b986bb6 100644
--- a/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/servlet-authz-app-authz-service.json
@@ -56,6 +56,10 @@
           "name": "write"
         }
       ]
+    },
+    {
+      "name": "Resource Protected With Claim",
+      "uri": "/protected/context/context.jsp"
     }
   ],
   "policies": [
@@ -183,6 +187,26 @@
         "scopes": "[\"write\"]",
         "applyPolicies": "[\"Deny Policy\"]"
       }
+    },
+    {
+      "name": "Resource Protected With Claim Permission",
+      "type": "resource",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
+      "config": {
+        "resources": "[\"Resource Protected With Claim\"]",
+        "applyPolicies": "[\"Request Claim Policy\"]"
+      }
+    },
+    {
+      "name": "Request Claim Policy",
+      "description": "A policy that grants access based on claims from an http request",
+      "type": "js",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
+      "config": {
+        "code": "var context = $evaluation.getContext();\nvar attributes = context.getAttributes();\nvar claim = attributes.getValue('request-claim');\n\nif (claim && claim.asString(0) == 'expected-value') {\n   $evaluation.grant();\n}"
+      }
     }
   ]
 }
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/context/context.jsp b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/context/context.jsp
new file mode 100644
index 0000000..e01c5da
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/protected/context/context.jsp
@@ -0,0 +1,14 @@
+<%@page import="org.keycloak.AuthorizationContext" %>
+<%@ page import="org.keycloak.KeycloakSecurityContext" %>
+
+<%
+    KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
+    AuthorizationContext authzContext = keycloakSecurityContext.getAuthorizationContext();
+%>
+
+<html>
+<body>
+<h2>Access granted: <%= authzContext.isGranted() %></h2>
+<%@include file="../../logout-include.jsp"%>
+</body>
+</html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzCIPAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzCIPAdapterTest.java
new file mode 100644
index 0000000..8ab56da
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzCIPAdapterTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 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.example.authorization;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public abstract class AbstractServletAuthzCIPAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
+
+    @Deployment(name = RESOURCE_SERVER_ID, managed = false)
+    public static WebArchive deployment() throws IOException {
+        return exampleDeployment(RESOURCE_SERVER_ID)
+                .addAsWebInfResource(new File(TEST_APPS_HOME_DIR + "/servlet-authz-app/keycloak-claim-information-point-authz-service.json"), "keycloak.json");
+    }
+
+    @Test
+    public void testClaimInformationPoint() {
+        performTests(() -> {
+            login("alice", "alice");
+            assertWasNotDenied();
+
+            this.driver.navigate().to(getResourceServerUrl() + "/protected/context/context.jsp?request-claim=unexpected-value");
+
+            assertWasDenied();
+
+            this.driver.navigate().to(getResourceServerUrl() + "/protected/context/context.jsp?request-claim=expected-value");
+            assertWasNotDenied();
+            hasText("Access granted: true");
+
+            this.driver.navigate().to(getResourceServerUrl() + "/protected/context/context.jsp");
+
+            assertWasDenied();
+        });
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ClaimInformationPointProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ClaimInformationPointProviderTest.java
new file mode 100644
index 0000000..87c4382
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ClaimInformationPointProviderTest.java
@@ -0,0 +1,457 @@
+/*
+ * Copyright 2018 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.admin.client.authorization;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.cert.X509Certificate;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.undertow.Undertow;
+import io.undertow.server.handlers.form.FormData;
+import io.undertow.server.handlers.form.FormDataParser;
+import io.undertow.server.handlers.form.FormParserFactory;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
+import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
+import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.HttpFacade.Cookie;
+import org.keycloak.adapters.spi.HttpFacade.Request;
+import org.keycloak.adapters.spi.HttpFacade.Response;
+import org.keycloak.adapters.spi.LogoutError;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.ProfileAssume;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ClaimInformationPointProviderTest extends AbstractKeycloakTest {
+
+    private static Undertow httpService;
+
+    @BeforeClass
+    public static void onBeforeClass() {
+        ProfileAssume.assumePreview();
+        httpService = Undertow.builder().addHttpListener(8989, "localhost").setHandler(exchange -> {
+            if (exchange.isInIoThread()) {
+                try {
+                    if (exchange.getRelativePath().equals("/post-claim-information-provider")) {
+                        FormParserFactory parserFactory = FormParserFactory.builder().build();
+                        FormDataParser parser = parserFactory.createParser(exchange);
+                        FormData formData = parser.parseBlocking();
+
+                        if (!"Bearer tokenString".equals(exchange.getRequestHeaders().getFirst("Authorization"))
+                                || !"post".equalsIgnoreCase(exchange.getRequestMethod().toString())
+                                || !"application/x-www-form-urlencoded".equals(exchange.getRequestHeaders().getFirst("Content-Type"))
+                                || !exchange.getRequestHeaders().get("header-b").contains("header-b-value1")
+                                || !exchange.getRequestHeaders().get("header-b").contains("header-b-value2")
+                                || !formData.get("param-a").getFirst().getValue().equals("param-a-value1")
+                                || !formData.get("param-a").getLast().getValue().equals("param-a-value2")
+                                || !formData.get("param-subject").getFirst().getValue().equals("sub")
+                                || !formData.get("param-user-name").getFirst().getValue().equals("username")
+                                || !formData.get("param-other-claims").getFirst().getValue().equals("param-other-claims-value1")
+                                || !formData.get("param-other-claims").getLast().getValue().equals("param-other-claims-value2")) {
+                            exchange.setStatusCode(400);
+                            return;
+                        }
+
+                        exchange.setStatusCode(200);
+                    } else if (exchange.getRelativePath().equals("/get-claim-information-provider")) {
+                        if (!"Bearer idTokenString".equals(exchange.getRequestHeaders().getFirst("Authorization"))
+                                || !"get".equalsIgnoreCase(exchange.getRequestMethod().toString())
+                                || !exchange.getRequestHeaders().get("header-b").contains("header-b-value1")
+                                || !exchange.getRequestHeaders().get("header-b").contains("header-b-value2")
+                                || !exchange.getQueryParameters().get("param-a").contains("param-a-value1")
+                                || !exchange.getQueryParameters().get("param-a").contains("param-a-value2")
+                                || !exchange.getQueryParameters().get("param-subject").contains("sub")
+                                || !exchange.getQueryParameters().get("param-user-name").contains("username")) {
+                            exchange.setStatusCode(400);
+                            return;
+                        }
+
+                        exchange.setStatusCode(200);
+                    } else {
+                        exchange.setStatusCode(404);
+                    }
+                } finally {
+                    if (exchange.getStatusCode() == 200) {
+                        try {
+                            ObjectMapper mapper = JsonSerialization.mapper;
+                            JsonParser jsonParser = mapper.getFactory().createParser("{\"a\": \"a-value1\", \"b\": \"b-value1\", \"d\": [\"d-value1\", \"d-value2\"]}");
+                            TreeNode treeNode = mapper.readTree(jsonParser);
+                            exchange.getResponseSender().send(treeNode.toString());
+                        } catch (Exception ignore) {
+                            ignore.printStackTrace();
+                        }
+                    }
+                    exchange.endExchange();
+                }
+            }
+        }).build();
+
+        httpService.start();
+    }
+
+    @AfterClass
+    public static void onAfterClass() {
+        httpService.stop();
+    }
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation realm = loadRealm(getClass().getResourceAsStream("/authorization-test/test-authz-realm.json"));
+        testRealms.add(realm);
+    }
+
+    private ClaimInformationPointProvider getClaimInformationProviderForPath(String path, String providerName) {
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/authorization-test/enforcer-config-claims-provider.json"));
+        deployment.setClient(HttpClients.createDefault());
+        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+        Map<String, ClaimInformationPointProviderFactory> providers = policyEnforcer.getClaimInformationPointProviderFactories();
+
+        PathConfig pathConfig = policyEnforcer.getPaths().get(path);
+
+        assertNotNull(pathConfig);
+
+        Map<String, Map<String, Object>> cipConfig = pathConfig.getClaimInformationPointConfig();
+
+        assertNotNull(cipConfig);
+
+        ClaimInformationPointProviderFactory factory = providers.get(providerName);
+
+        assertNotNull(factory);
+
+        Map<String, Object> claimsConfig = cipConfig.get(providerName);
+
+        return factory.create(claimsConfig);
+    }
+
+    @Test
+    public void testBasicClaimsInformationPoint() {
+        HttpFacade httpFacade = createHttpFacade();
+        Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(httpFacade);
+
+        assertEquals("parameter-a", claims.get("claim-from-request-parameter").get(0));
+        assertEquals("header-b", claims.get("claim-from-header").get(0));
+        assertEquals("cookie-c", claims.get("claim-from-cookie").get(0));
+        assertEquals("user-remote-addr", claims.get("claim-from-remoteAddr").get(0));
+        assertEquals("GET", claims.get("claim-from-method").get(0));
+        assertEquals("/app/request-uri", claims.get("claim-from-uri").get(0));
+        assertEquals("/request-relative-path", claims.get("claim-from-relativePath").get(0));
+        assertEquals("true", claims.get("claim-from-secure").get(0));
+        assertEquals("static value", claims.get("claim-from-static-value").get(0));
+        assertEquals("static", claims.get("claim-from-multiple-static-value").get(0));
+        assertEquals("value", claims.get("claim-from-multiple-static-value").get(1));
+        assertEquals("Test param-other-claims-value1 and parameter-a", claims.get("param-replace-multiple-placeholder").get(0));
+    }
+
+    @Test
+    public void testBodyJsonClaimsInformationPoint() throws Exception {
+        Map<String, List<String>> headers = new HashMap<>();
+
+        headers.put("Content-Type", Arrays.asList("application/json"));
+
+        ObjectMapper mapper = JsonSerialization.mapper;
+        JsonParser parser = mapper.getFactory().createParser("{\"a\": {\"b\": {\"c\": \"c-value\"}}, \"d\": [\"d-value1\", \"d-value2\"]}");
+        TreeNode treeNode = mapper.readTree(parser);
+        HttpFacade httpFacade = createHttpFacade(headers, new ByteArrayInputStream(treeNode.toString().getBytes()));
+
+        Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(httpFacade);
+
+        assertEquals("c-value", claims.get("claim-from-json-body-object").get(0));
+        assertEquals("d-value2", claims.get("claim-from-json-body-array").get(0));
+    }
+
+    @Test
+    public void testBodyClaimsInformationPoint() {
+        HttpFacade httpFacade = createHttpFacade(new HashMap<>(), new ByteArrayInputStream("raw-body-text".getBytes()));
+
+        Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(httpFacade);
+
+        assertEquals("raw-body-text", claims.get("claim-from-body").get(0));
+    }
+
+    @Test
+    public void testHttpClaimInformationPointProviderWithoutClaims() {
+        HttpFacade httpFacade = createHttpFacade();
+
+        Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-get-claim-provider", "http").resolve(httpFacade);
+
+        assertEquals("a-value1", claims.get("a").get(0));
+        assertEquals("b-value1", claims.get("b").get(0));
+        assertEquals("d-value1", claims.get("d").get(0));
+        assertEquals("d-value2", claims.get("d").get(1));
+
+        assertNull(claims.get("claim-a"));
+        assertNull(claims.get("claim-d"));
+        assertNull(claims.get("claim-d0"));
+        assertNull(claims.get("claim-d-all"));
+    }
+
+    @Test
+    public void testHttpClaimInformationPointProviderWithClaims() {
+        HttpFacade httpFacade = createHttpFacade();
+
+        Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-post-claim-provider", "http").resolve(httpFacade);
+
+        assertEquals("a-value1", claims.get("claim-a").get(0));
+        assertEquals("d-value1", claims.get("claim-d").get(0));
+        assertEquals("d-value2", claims.get("claim-d").get(1));
+        assertEquals("d-value1", claims.get("claim-d0").get(0));
+        assertEquals("d-value1", claims.get("claim-d-all").get(0));
+        assertEquals("d-value2", claims.get("claim-d-all").get(1));
+
+        assertNull(claims.get("a"));
+        assertNull(claims.get("b"));
+        assertNull(claims.get("d"));
+    }
+
+    private HttpFacade createHttpFacade(Map<String, List<String>> headers, InputStream requestBody) {
+        return new OIDCHttpFacade() {
+            private Request request;
+
+            @Override
+            public KeycloakSecurityContext getSecurityContext() {
+                AccessToken token = new AccessToken();
+
+                token.subject("sub");
+                token.setPreferredUsername("username");
+                token.getOtherClaims().put("custom_claim", Arrays.asList("param-other-claims-value1", "param-other-claims-value2"));
+
+                IDToken idToken = new IDToken();
+
+                idToken.subject("sub");
+                idToken.setPreferredUsername("username");
+                idToken.getOtherClaims().put("custom_claim", Arrays.asList("param-other-claims-value1", "param-other-claims-value2"));
+
+                return new KeycloakSecurityContext("tokenString", token, "idTokenString", idToken);
+            }
+
+            @Override
+            public Request getRequest() {
+                if (request == null) {
+                    request = createHttpRequest(headers, requestBody);
+                }
+                return request;
+            }
+
+            @Override
+            public Response getResponse() {
+                return createHttpResponse();
+            }
+
+            @Override
+            public X509Certificate[] getCertificateChain() {
+                return new X509Certificate[0];
+            }
+        };
+    }
+
+    private HttpFacade createHttpFacade() {
+        return createHttpFacade(new HashMap<>(), null);
+    }
+
+    private Response createHttpResponse() {
+        return new Response() {
+            @Override
+            public void setStatus(int status) {
+
+            }
+
+            @Override
+            public void addHeader(String name, String value) {
+
+            }
+
+            @Override
+            public void setHeader(String name, String value) {
+
+            }
+
+            @Override
+            public void resetCookie(String name, String path) {
+
+            }
+
+            @Override
+            public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
+
+            }
+
+            @Override
+            public OutputStream getOutputStream() {
+                return null;
+            }
+
+            @Override
+            public void sendError(int code) {
+
+            }
+
+            @Override
+            public void sendError(int code, String message) {
+
+            }
+
+            @Override
+            public void end() {
+
+            }
+        };
+    }
+
+    private Request createHttpRequest(Map<String, List<String>> headers, InputStream requestBody) {
+        Map<String, List<String>> queryParameter = new HashMap<>();
+
+        queryParameter.put("a", Arrays.asList("parameter-a"));
+
+        headers.put("b", Arrays.asList("header-b"));
+
+        Map<String, Cookie> cookies = new HashMap<>();
+
+        cookies.put("c", new Cookie("c", "cookie-c", 1, "localhost", "/"));
+
+        return new Request() {
+
+            private InputStream inputStream;
+
+            @Override
+            public String getMethod() {
+                return "GET";
+            }
+
+            @Override
+            public String getURI() {
+                return "/app/request-uri";
+            }
+
+            @Override
+            public String getRelativePath() {
+                return "/request-relative-path";
+            }
+
+            @Override
+            public boolean isSecure() {
+                return true;
+            }
+
+            @Override
+            public String getFirstParam(String param) {
+                List<String> values = queryParameter.getOrDefault(param, Collections.emptyList());
+
+                if (!values.isEmpty()) {
+                    return values.get(0);
+                }
+
+                return null;
+            }
+
+            @Override
+            public String getQueryParamValue(String param) {
+                return getFirstParam(param);
+            }
+
+            @Override
+            public Cookie getCookie(String cookieName) {
+                return cookies.get(cookieName);
+            }
+
+            @Override
+            public String getHeader(String name) {
+                List<String> headers = getHeaders(name);
+
+                if (!headers.isEmpty()) {
+                    return headers.get(0);
+                }
+
+                return null;
+            }
+
+            @Override
+            public List<String> getHeaders(String name) {
+                return headers.getOrDefault(name, Collections.emptyList());
+            }
+
+            @Override
+            public InputStream getInputStream() {
+                return getInputStream(false);
+            }
+
+            @Override
+            public InputStream getInputStream(boolean buffer) {
+                if (requestBody == null) {
+                    return new ByteArrayInputStream(new byte[] {});
+                }
+
+                if (inputStream != null) {
+                    return inputStream;
+                }
+
+                if (buffer) {
+                    return inputStream = new BufferedInputStream(requestBody);
+                }
+
+                return requestBody;
+            }
+
+            @Override
+            public String getRemoteAddr() {
+                return "user-remote-addr";
+            }
+
+            @Override
+            public void setError(AuthenticationError error) {
+
+            }
+
+            @Override
+            public void setError(LogoutError error) {
+
+            }
+        };
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/EnforcerConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/EnforcerConfigTest.java
index 081bb79..327b733 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/EnforcerConfigTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/EnforcerConfigTest.java
@@ -22,6 +22,7 @@ import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
 import org.keycloak.adapters.authorization.PolicyEnforcer;
 import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
+import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.ProfileAssume;
@@ -30,6 +31,8 @@ import java.util.List;
 import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.keycloak.testsuite.util.IOUtil.loadRealm;
 
 /**
@@ -47,11 +50,34 @@ public class EnforcerConfigTest extends AbstractKeycloakTest {
     }
 
     @Test
-    public void testMultiplePathsWithSameName() throws Exception{
+    public void testMultiplePathsWithSameName() {
         KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/authorization-test/enforcer-config-paths-same-name.json"));
         PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
         Map<String, PolicyEnforcerConfig.PathConfig> paths = policyEnforcer.getPaths();
         assertEquals(1, paths.size());
         assertEquals(4, paths.values().iterator().next().getMethods().size());
     }
+
+    @Test
+    public void testPathConfigClaimInformationPoint() {
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/authorization-test/enforcer-config-path-cip.json"));
+        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+        Map<String, PolicyEnforcerConfig.PathConfig> paths = policyEnforcer.getPaths();
+
+        assertEquals(1, paths.size());
+
+        PathConfig pathConfig = paths.values().iterator().next();
+        Map<String, Map<String, Object>> cipConfig = pathConfig.getClaimInformationPointConfig();
+
+        assertEquals(1, cipConfig.size());
+
+        Map<String, Object> claims = cipConfig.get("claims");
+
+        assertNotNull(claims);
+
+        assertEquals(3, claims.size());
+        assertEquals("{request.parameter['a']}", claims.get("claim-a"));
+        assertEquals("{request.header['b']}", claims.get("claim-b"));
+        assertEquals("{request.cookie['c']}", claims.get("claim-c"));
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
new file mode 100644
index 0000000..81f2ffd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2018 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.admin.client.authorization;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.security.cert.X509Certificate;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.keycloak.AuthorizationContext;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.HttpFacade.Cookie;
+import org.keycloak.adapters.spi.HttpFacade.Request;
+import org.keycloak.adapters.spi.HttpFacade.Response;
+import org.keycloak.adapters.spi.LogoutError;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.Configuration;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.AuthorizationResponse;
+import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ScopeRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.ProfileAssume;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.RolesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class PolicyEnforcerTest extends AbstractKeycloakTest {
+
+    protected static final String REALM_NAME = "authz-test";
+
+    @BeforeClass
+    public static void onBeforeClass() {
+        ProfileAssume.assumePreview();
+    }
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        testRealms.add(RealmBuilder.create().name(REALM_NAME)
+                .roles(RolesBuilder.create()
+                        .realmRole(RoleBuilder.create().name("uma_authorization").build())
+                        .realmRole(RoleBuilder.create().name("uma_protection").build())
+                )
+                .user(UserBuilder.create().username("marta").password("password")
+                        .addRoles("uma_authorization", "uma_protection")
+                        .role("resource-server-test", "uma_protection"))
+                .user(UserBuilder.create().username("kolo").password("password"))
+                .client(ClientBuilder.create().clientId("resource-server-uma-test")
+                        .secret("secret")
+                        .authorizationServicesEnabled(true)
+                        .redirectUris("http://localhost/resource-server-uma-test")
+                        .defaultRoles("uma_protection")
+                        .directAccessGrants())
+                .client(ClientBuilder.create().clientId("resource-server-test")
+                        .secret("secret")
+                        .authorizationServicesEnabled(true)
+                        .redirectUris("http://localhost/resource-server-test")
+                        .defaultRoles("uma_protection")
+                        .directAccessGrants())
+                .build());
+    }
+
+    @Test
+    public void testEnforceUMAAccessWithClaimsUsingBearerToken() {
+        initAuthorizationSettings(getClientResource("resource-server-uma-test"));
+
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-uma-claims-test.json"));
+        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+        HashMap<String, List<String>> headers = new HashMap<>();
+        HashMap<String, List<String>> parameters = new HashMap<>();
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        AuthzClient authzClient = getAuthzClient("enforcer-uma-claims-test.json");
+        String token = authzClient.obtainAccessToken("marta", "password").getToken();
+
+        headers.put("Authorization", Arrays.asList("Bearer " + token));
+
+        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        AuthorizationRequest request = new AuthorizationRequest();
+
+        request.setTicket(extractTicket(headers));
+
+        AuthorizationResponse response = authzClient.authorization("marta", "password").authorize(request);
+        token = response.getToken();
+
+        assertNotNull(token);
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("200"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("10"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+
+        request = new AuthorizationRequest();
+
+        request.setTicket(extractTicket(headers));
+
+        response = authzClient.authorization("marta", "password").authorize(request);
+        token = response.getToken();
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+    }
+
+    @Test
+    public void testEnforceEntitlementAccessWithClaimsWithoutBearerToken() {
+        initAuthorizationSettings(getClientResource("resource-server-test"));
+
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
+        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+        HashMap<String, List<String>> headers = new HashMap<>();
+        HashMap<String, List<String>> parameters = new HashMap<>();
+
+        AuthzClient authzClient = getAuthzClient("enforcer-entitlement-claims-test.json");
+        String token = authzClient.obtainAccessToken("marta", "password").getToken();
+
+        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("200"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("10"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+
+        assertTrue(context.isGranted());
+    }
+
+    @Test
+    public void testEnforceEntitlementAccessWithClaimsWithBearerToken() {
+        initAuthorizationSettings(getClientResource("resource-server-test"));
+
+        KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-entitlement-claims-test.json"));
+        PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+        HashMap<String, List<String>> headers = new HashMap<>();
+        HashMap<String, List<String>> parameters = new HashMap<>();
+
+        AuthzClient authzClient = getAuthzClient("enforcer-entitlement-claims-test.json");
+        String token = authzClient.obtainAccessToken("marta", "password").getToken();
+
+        headers.put("Authorization", Arrays.asList("Bearer " + token));
+
+        AuthorizationContext context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("200"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertFalse(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("50"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+        assertTrue(context.isGranted());
+
+        parameters.put("withdrawal.amount", Arrays.asList("10"));
+
+        context = policyEnforcer.enforce(createHttpFacade("/api/bank/account/1/withdrawal", token, headers, parameters));
+
+        assertTrue(context.isGranted());
+    }
+
+    private String extractTicket(HashMap<String, List<String>> headers) {
+        List<String> wwwAuthenticateHeader = headers.get("WWW-Authenticate");
+
+        assertNotNull(wwwAuthenticateHeader);
+        assertFalse(wwwAuthenticateHeader.isEmpty());
+
+        String wwwAuthenticate = wwwAuthenticateHeader.get(0);
+        return wwwAuthenticate.substring(wwwAuthenticate.indexOf("ticket=") + "ticket=\"".length(), wwwAuthenticate.lastIndexOf('"'));
+    }
+
+    private void initAuthorizationSettings(ClientResource clientResource) {
+        if (clientResource.authorization().resources().findByName("Bank Account").isEmpty()) {
+            JSPolicyRepresentation policy = new JSPolicyRepresentation();
+
+            policy.setName("Withdrawal Limit Policy");
+
+            StringBuilder code = new StringBuilder();
+
+            code.append("var context = $evaluation.getContext();");
+            code.append("var attributes = context.getAttributes();");
+            code.append("var withdrawalAmount = attributes.getValue('withdrawal.amount');");
+            code.append("if (withdrawalAmount && withdrawalAmount.asDouble(0) <= 100) {");
+            code.append("   $evaluation.grant();");
+            code.append("}");
+
+            policy.setCode(code.toString());
+
+            clientResource.authorization().policies().js().create(policy);
+
+            createResource(clientResource, "Bank Account", "/api/bank/account/{id}/withdrawal", "withdrawal");
+
+            ScopePermissionRepresentation permission = new ScopePermissionRepresentation();
+
+            permission.setName("Withdrawal Permission");
+            permission.addScope("withdrawal");
+            permission.addPolicy(policy.getName());
+
+            clientResource.authorization().permissions().scope().create(permission);
+        }
+    }
+
+    private InputStream getAdapterConfiguration(String fileName) {
+        return getClass().getResourceAsStream("/authorization-test/" + fileName);
+    }
+
+    private ResourceRepresentation createResource(ClientResource clientResource, String name, String uri, String... scopes) {
+        ResourceRepresentation representation = new ResourceRepresentation();
+
+        representation.setName(name);
+        representation.setUri(uri);
+        representation.setScopes(Arrays.asList(scopes).stream().map(ScopeRepresentation::new).collect(Collectors.toSet()));
+
+        javax.ws.rs.core.Response response = clientResource.authorization().resources().create(representation);
+
+        representation.setId(response.readEntity(ResourceRepresentation.class).getId());
+
+        return representation;
+    }
+
+    private ClientResource getClientResource(String name) {
+        ClientsResource clients = realmsResouce().realm(REALM_NAME).clients();
+        ClientRepresentation representation = clients.findByClientId(name).get(0);
+        return clients.get(representation.getId());
+    }
+
+    private OIDCHttpFacade createHttpFacade(String path, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
+        return new OIDCHttpFacade() {
+            Request request;
+            Response response;
+
+            @Override
+            public KeycloakSecurityContext getSecurityContext() {
+                AccessToken accessToken;
+                try {
+                    accessToken = new JWSInput(token).readJsonContent(AccessToken.class);
+                } catch (JWSInputException cause) {
+                    throw new RuntimeException(cause);
+                }
+                return new KeycloakSecurityContext(token, accessToken, null, null);
+            }
+
+            @Override
+            public Request getRequest() {
+                if (request == null) {
+                    request = createHttpRequest(path, headers, parameters, requestBody);
+                }
+                return request;
+            }
+
+            @Override
+            public Response getResponse() {
+                if (response == null) {
+                    response = createHttpResponse(headers);
+                }
+                return response;
+            }
+
+            @Override
+            public X509Certificate[] getCertificateChain() {
+                return new X509Certificate[0];
+            }
+        };
+    }
+
+    private OIDCHttpFacade createHttpFacade(String path, String token, Map<String, List<String>> headers, Map<String, List<String>> parameters) {
+        return createHttpFacade(path, token, headers, parameters, null);
+    }
+
+    private Response createHttpResponse(Map<String, List<String>> headers) {
+        return new Response() {
+
+            private int status;
+
+            @Override
+            public void setStatus(int status) {
+                this.status = status;
+            }
+
+            @Override
+            public void addHeader(String name, String value) {
+                setHeader(name, value);
+            }
+
+            @Override
+            public void setHeader(String name, String value) {
+                headers.put(name, Arrays.asList(value));
+            }
+
+            @Override
+            public void resetCookie(String name, String path) {
+
+            }
+
+            @Override
+            public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
+
+            }
+
+            @Override
+            public OutputStream getOutputStream() {
+                return null;
+            }
+
+            @Override
+            public void sendError(int code) {
+
+            }
+
+            @Override
+            public void sendError(int code, String message) {
+
+            }
+
+            @Override
+            public void end() {
+
+            }
+        };
+    }
+
+    private Request createHttpRequest(String path, Map<String, List<String>> headers, Map<String, List<String>> parameters, InputStream requestBody) {
+        return new Request() {
+
+            private InputStream inputStream;
+
+            @Override
+            public String getMethod() {
+                return "GET";
+            }
+
+            @Override
+            public String getURI() {
+                return path;
+            }
+
+            @Override
+            public String getRelativePath() {
+                return path;
+            }
+
+            @Override
+            public boolean isSecure() {
+                return true;
+            }
+
+            @Override
+            public String getFirstParam(String param) {
+                List<String> values = parameters.getOrDefault(param, Collections.emptyList());
+
+                if (!values.isEmpty()) {
+                    return values.get(0);
+                }
+
+                return null;
+            }
+
+            @Override
+            public String getQueryParamValue(String param) {
+                return getFirstParam(param);
+            }
+
+            @Override
+            public Cookie getCookie(String cookieName) {
+                return null;
+            }
+
+            @Override
+            public String getHeader(String name) {
+                List<String> headers = getHeaders(name);
+
+                if (!headers.isEmpty()) {
+                    return headers.get(0);
+                }
+
+                return null;
+            }
+
+            @Override
+            public List<String> getHeaders(String name) {
+                return headers.getOrDefault(name, Collections.emptyList());
+            }
+
+            @Override
+            public InputStream getInputStream() {
+                return getInputStream(false);
+            }
+
+            @Override
+            public InputStream getInputStream(boolean buffer) {
+                if (requestBody == null) {
+                    return new ByteArrayInputStream(new byte[] {});
+                }
+
+                if (inputStream != null) {
+                    return inputStream;
+                }
+
+                if (buffer) {
+                    return inputStream = new BufferedInputStream(requestBody);
+                }
+
+                return requestBody;
+            }
+
+            @Override
+            public String getRemoteAddr() {
+                return "user-remote-addr";
+            }
+
+            @Override
+            public void setError(AuthenticationError error) {
+
+            }
+
+            @Override
+            public void setError(LogoutError error) {
+
+            }
+        };
+    }
+
+    protected AuthzClient getAuthzClient(String fileName) {
+        try {
+            return AuthzClient.create(JsonSerialization.readValue(getAdapterConfiguration(fileName), Configuration.class));
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to create authz client", cause);
+        }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
index a7fab87..167761f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
@@ -20,19 +20,25 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.function.Supplier;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.admin.client.resource.AuthorizationResource;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.ClientsResource;
 import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.authorization.client.AuthorizationDeniedException;
 import org.keycloak.authorization.client.AuthzClient;
 import org.keycloak.authorization.client.Configuration;
+import org.keycloak.authorization.client.util.HttpResponseException;
+import org.keycloak.common.util.Base64Url;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.authorization.AuthorizationRequest;
@@ -61,6 +67,8 @@ public class EntitlementAPITest extends AbstractAuthzTest {
     private static final String PAIRWISE_RESOURCE_SERVER_TEST = "pairwise-resource-server-test";
     private static final String PAIRWISE_TEST_CLIENT = "test-client-pairwise";
     private static final String PAIRWISE_AUTHZ_CLIENT_CONFIG = "default-keycloak-pairwise.json";
+    private static final String PUBLIC_TEST_CLIENT = "test-public-client";
+    private static final String PUBLIC_TEST_CLIENT_CONFIG = "default-keycloak-public-client.json";
 
     private AuthzClient authzClient;
 
@@ -94,6 +102,10 @@ public class EntitlementAPITest extends AbstractAuthzTest {
                         .redirectUris("http://localhost/test-client")
                         .pairwise("http://pairwise.com")
                         .directAccessGrants())
+                .client(ClientBuilder.create().clientId(PUBLIC_TEST_CLIENT)
+                        .secret("secret")
+                        .redirectUris("http://localhost:8180/auth/realms/master/app/auth/*")
+                        .publicClient())
                 .build());
     }
 
@@ -164,6 +176,65 @@ public class EntitlementAPITest extends AbstractAuthzTest {
         testRptRequestWithResourceName(PAIRWISE_AUTHZ_CLIENT_CONFIG);
     }
 
+    @Test
+    public void testInvalidRequestWithClaimsFromConfidentialClient() throws IOException {
+        AuthorizationRequest request = new AuthorizationRequest();
+
+        request.addPermission("Resource 13");
+        HashMap<Object, Object> obj = new HashMap<>();
+
+        obj.put("claim-a", "claim-a");
+
+        request.setClaimToken(Base64Url.encode(JsonSerialization.writeValueAsBytes(obj)));
+
+        assertResponse(new Metadata(), () -> getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization("marta", "password").authorize(request));
+    }
+
+    @Test
+    public void testInvalidRequestWithClaimsFromPublicClient() throws IOException {
+        oauth.realm("authz-test");
+        oauth.clientId(PUBLIC_TEST_CLIENT);
+
+        oauth.doLogin("marta", "password");
+
+        // Token request
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
+
+        AuthorizationRequest request = new AuthorizationRequest();
+
+        request.addPermission("Resource 13");
+        HashMap<Object, Object> obj = new HashMap<>();
+
+        obj.put("claim-a", "claim-a");
+
+        request.setClaimToken(Base64Url.encode(JsonSerialization.writeValueAsBytes(obj)));
+
+        try {
+            getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization(response.getAccessToken()).authorize(request);
+        } catch (AuthorizationDeniedException expected) {
+            assertEquals(403, HttpResponseException.class.cast(expected.getCause()).getStatusCode());
+            assertTrue(HttpResponseException.class.cast(expected.getCause()).toString().contains("Public clients are not allowed to send claims"));
+        }
+    }
+
+    @Test
+    public void testRequestWithoutClaimsFromPublicClient() {
+        oauth.realm("authz-test");
+        oauth.clientId(PUBLIC_TEST_CLIENT);
+
+        oauth.doLogin("marta", "password");
+
+        // Token request
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
+
+        AuthorizationRequest request = new AuthorizationRequest();
+
+        request.addPermission("Resource 13");
+
+        assertResponse(new Metadata(), () -> getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization(response.getAccessToken()).authorize(request));
+    }
 
     public void testRptRequestWithResourceName(String configFile) {
         Metadata metadata = new Metadata();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java
index f488d32..9d47f8d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionManagementTest.java
@@ -219,7 +219,7 @@ public class PermissionManagementTest extends AbstractResourceServerTest {
         try {
             authzClient.authorization().authorize(request);
         } catch (Exception e) {
-
+            e.printStackTrace();
         }
 
         List permissions = authzClient.protection().permission().findByResource(resource.getId());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaPermissionTicketPushedClaimsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaPermissionTicketPushedClaimsTest.java
new file mode 100644
index 0000000..0d36913
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaPermissionTicketPushedClaimsTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2018 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.authz;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.AuthorizationResponse;
+import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.PermissionRequest;
+import org.keycloak.representations.idm.authorization.PermissionResponse;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class UmaPermissionTicketPushedClaimsTest extends AbstractResourceServerTest {
+
+    @Test
+    public void testEvaluatePermissionsWithPushedClaims() throws Exception {
+        ResourceRepresentation resource = addResource("Bank Account", "withdraw");
+        JSPolicyRepresentation policy = new JSPolicyRepresentation();
+
+        policy.setName("Withdraw Limit Policy");
+
+        StringBuilder code = new StringBuilder();
+
+        code.append("var context = $evaluation.getContext();");
+        code.append("var attributes = context.getAttributes();");
+        code.append("var withdrawValue = attributes.getValue('my.bank.account.withdraw.value');");
+        code.append("if (withdrawValue && withdrawValue.asDouble(0) <= 100) {");
+        code.append("   $evaluation.grant();");
+        code.append("}");
+
+        policy.setCode(code.toString());
+
+        AuthorizationResource authorization = getClient(getRealm()).authorization();
+
+        authorization.policies().js().create(policy);
+
+        ScopePermissionRepresentation representation = new ScopePermissionRepresentation();
+
+        representation.setName("Withdraw Permission");
+        representation.addScope("withdraw");
+        representation.addPolicy(policy.getName());
+
+        authorization.permissions().scope().create(representation);
+
+        AuthzClient authzClient = getAuthzClient();
+        PermissionRequest permissionRequest = new PermissionRequest(resource.getId());
+
+        permissionRequest.addScope("withdraw");
+        permissionRequest.setClaim("my.bank.account.withdraw.value", "50.5");
+
+        PermissionResponse response = authzClient.protection("marta", "password").permission().create(permissionRequest);
+        AuthorizationRequest request = new AuthorizationRequest();
+
+        request.setTicket(response.getTicket());
+        request.setClaimToken(authzClient.obtainAccessToken("marta", "password").getToken());
+
+        AuthorizationResponse authorizationResponse = authzClient.authorization().authorize(request);
+
+        assertNotNull(authorizationResponse);
+        assertNotNull(authorizationResponse.getToken());
+
+        permissionRequest.setClaim("my.bank.account.withdraw.value", "100.5");
+
+        response = authzClient.protection("marta", "password").permission().create(permissionRequest);
+        request = new AuthorizationRequest();
+
+        request.setTicket(response.getTicket());
+        request.setClaimToken(authzClient.obtainAccessToken("marta", "password").getToken());
+
+        try {
+            authorizationResponse = authzClient.authorization().authorize(request);
+            fail("Access should be denied");
+        } catch (Exception ignore) {
+
+        }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/default-keycloak-public-client.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/default-keycloak-public-client.json
new file mode 100644
index 0000000..b04b57f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/default-keycloak-public-client.json
@@ -0,0 +1,5 @@
+{
+    "realm": "authz-test",
+    "auth-server-url" : "http://localhost:8180/auth",
+    "resource" : "test-public-client"
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-claims-provider.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-claims-provider.json
new file mode 100644
index 0000000..9da5dae
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-claims-provider.json
@@ -0,0 +1,89 @@
+{
+  "realm": "test-realm-authz",
+  "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url": "http://localhost:8180/auth",
+  "ssl-required": "external",
+  "resource": "test-app-authz",
+  "bearer-only": true,
+  "credentials": {
+    "secret": "secret"
+  },
+  "policy-enforcer": {
+    "paths": [
+      {
+        "path": "/claims-provider",
+        "methods": [
+          {
+            "method": "POST",
+            "scopes": [
+              "create"
+            ]
+          }
+        ],
+        "claim-information-point": {
+          "claims": {
+            "claim-from-request-parameter": "{request.parameter['a']}",
+            "claim-from-header": "{request.header['b']}",
+            "claim-from-cookie": "{request.cookie['c']}",
+            "claim-from-remoteAddr": "{request.remoteAddr}",
+            "claim-from-method": "{request.method}",
+            "claim-from-uri": "{request.uri}",
+            "claim-from-relativePath": "{request.relativePath}",
+            "claim-from-secure": "{request.secure}",
+            "claim-from-json-body-object": "{request.body['/a/b/c']}",
+            "claim-from-json-body-array": "{request.body['/d/1']}",
+            "claim-from-body": "{request.body}",
+            "claim-from-static-value": "static value",
+            "claim-from-multiple-static-value": ["static", "value"],
+            "param-replace-multiple-placeholder": "Test {keycloak.access_token['/custom_claim/0']} and {request.parameter['a']} "
+          }
+        }
+      },
+      {
+        "path": "/http-post-claim-provider",
+        "claim-information-point": {
+          "http": {
+            "claims": {
+              "claim-a": "/a",
+              "claim-d": "/d",
+              "claim-d0": "/d/0",
+              "claim-d-all": ["/d/0", "/d/1"]
+            },
+            "url": "http://localhost:8989/post-claim-information-provider",
+            "method": "POST",
+            "headers": {
+              "Content-Type": "application/x-www-form-urlencoded",
+              "header-b": ["header-b-value1", "header-b-value2"],
+              "Authorization": "Bearer {keycloak.access_token}"
+            },
+            "parameters": {
+              "param-a": ["param-a-value1", "param-a-value2"],
+              "param-subject": "{keycloak.access_token['/sub']}",
+              "param-user-name": "{keycloak.access_token['/preferred_username']}",
+              "param-other-claims": "{keycloak.access_token['/custom_claim']}"
+            }
+          }
+        }
+      },
+      {
+        "path": "/http-get-claim-provider",
+        "claim-information-point": {
+          "http": {
+            "url": "http://localhost:8989/get-claim-information-provider",
+            "method": "get",
+            "headers": {
+              "Content-Type": "application/x-www-form-urlencoded",
+              "header-b": ["header-b-value1", "header-b-value2"],
+              "Authorization": "Bearer {keycloak.id_token}"
+            },
+            "parameters": {
+              "param-a": ["param-a-value1", "param-a-value2"],
+              "param-subject": "{keycloak.id_token['/sub']}",
+              "param-user-name": "{keycloak.id_token['/preferred_username']}"
+            }
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-path-cip.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-path-cip.json
new file mode 100644
index 0000000..78fd7c7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-path-cip.json
@@ -0,0 +1,33 @@
+{
+  "realm": "test-realm-authz",
+  "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url": "http://localhost:8180/auth",
+  "ssl-required": "external",
+  "resource": "test-app-authz",
+  "bearer-only": true,
+  "credentials": {
+    "secret": "secret"
+  },
+  "policy-enforcer": {
+    "paths": [
+      {
+        "path": "/v1/product/*",
+        "methods": [
+          {
+            "method": "POST",
+            "scopes": [
+              "create"
+            ]
+          }
+        ],
+        "claim-information-point": {
+          "claims": {
+            "claim-a": "{request.parameter['a']}",
+            "claim-b": "{request.header['b']}",
+            "claim-c": "{request.cookie['c']}"
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-entitlement-claims-test.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-entitlement-claims-test.json
new file mode 100644
index 0000000..b01909d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-entitlement-claims-test.json
@@ -0,0 +1,29 @@
+{
+  "realm": "authz-test",
+  "auth-server-url": "http://localhost:8180/auth",
+  "ssl-required": "external",
+  "resource": "resource-server-test",
+  "credentials": {
+    "secret": "secret"
+  },
+  "policy-enforcer": {
+    "paths": [
+      {
+        "path": "/api/bank/account/{id}/withdrawal",
+        "methods": [
+          {
+            "method": "POST",
+            "scopes": [
+              "withdrawal"
+            ]
+          }
+        ],
+        "claim-information-point": {
+          "claims": {
+            "withdrawal.amount": "{request.parameter['withdrawal.amount']}"
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-uma-claims-test.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-uma-claims-test.json
new file mode 100644
index 0000000..9729103
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-uma-claims-test.json
@@ -0,0 +1,31 @@
+{
+  "realm": "authz-test",
+  "auth-server-url": "http://localhost:8180/auth",
+  "ssl-required": "external",
+  "resource": "resource-server-uma-test",
+  "bearer-only": true,
+  "credentials": {
+    "secret": "secret"
+  },
+  "policy-enforcer": {
+    "user-managed-access": {},
+    "paths": [
+      {
+        "path": "/api/bank/account/{id}/withdrawal",
+        "methods": [
+          {
+            "method": "POST",
+            "scopes": [
+              "withdrawal"
+            ]
+          }
+        ],
+        "claim-information-point": {
+          "claims": {
+            "withdrawal.amount": "{request.parameter['withdrawal.amount']}"
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzCIPAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzCIPAdapterTest.java
new file mode 100644
index 0000000..d59955d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyServletAuthzCIPAdapterTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 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.example.authorization;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@RunAsClient
+@AppServerContainer("app-server-wildfly")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyServletAuthzCIPAdapterTest extends AbstractServletAuthzCIPAdapterTest {
+
+}