keycloak-uncached
Changes
services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java 20(+11 -9)
services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java 163(+105 -58)
services/src/main/java/org/keycloak/protocol/oidc/mappers/AllowedWebOriginsProtocolMapper.java 98(+98 -0)
services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceResolveProtocolMapper.java 100(+100 -0)
services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java 9(+8 -1)
services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java 87(+39 -48)
services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java 18(+14 -4)
services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java 1(+1 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java 10(+9 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java 12(+7 -5)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java 4(+4 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java 26(+23 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java 8(+4 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java 161(+154 -7)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java 7(+6 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AudienceTest.java 37(+26 -11)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCScopeTest.java 6(+3 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java 4(+3 -1)
Details
diff --git a/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java
index 22192f6..205fb2b 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java
@@ -28,6 +28,7 @@ public class ProtocolMapperTypeRepresentation {
     protected String name;
     protected String category;
     protected String helpText;
+    protected int priority;
 
     protected List<ConfigPropertyRepresentation> properties;
 
@@ -63,6 +64,14 @@ public class ProtocolMapperTypeRepresentation {
         this.helpText = helpText;
     }
 
+    public int getPriority() {
+        return priority;
+    }
+
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
     public List<ConfigPropertyRepresentation> getProperties() {
         return properties;
     }
                diff --git a/server-spi/src/main/java/org/keycloak/models/ClientScopeModel.java b/server-spi/src/main/java/org/keycloak/models/ClientScopeModel.java
index 1384271..c2c4c02 100755
--- a/server-spi/src/main/java/org/keycloak/models/ClientScopeModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/ClientScopeModel.java
@@ -49,6 +49,7 @@ public interface ClientScopeModel extends ProtocolMapperContainerModel, ScopeCon
     String DISPLAY_ON_CONSENT_SCREEN = "display.on.consent.screen";
     String CONSENT_SCREEN_TEXT = "consent.screen.text";
     String GUI_ORDER = "gui.order";
+    String INCLUDE_IN_TOKEN_SCOPE = "include.in.token.scope";
 
     default boolean isDisplayOnConsentScreen() {
         String displayVal = getAttribute(DISPLAY_ON_CONSENT_SCREEN);
@@ -81,5 +82,14 @@ public interface ClientScopeModel extends ProtocolMapperContainerModel, ScopeCon
         setAttribute(GUI_ORDER, guiOrder);
     }
 
+    default boolean isIncludeInTokenScope() {
+        String includeInTokenScope = getAttribute(INCLUDE_IN_TOKEN_SCOPE);
+        return includeInTokenScope==null ? true : Boolean.parseBoolean(includeInTokenScope);
+    }
+
+    default void setIncludeInTokenScope(boolean includeInTokenScope) {
+        setAttribute(INCLUDE_IN_TOKEN_SCOPE, String.valueOf(includeInTokenScope));
+    }
+
 
 }
                diff --git a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
index 1ffc8fe..10ddab4 100755
--- a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -40,6 +40,7 @@ import org.keycloak.migration.migrators.MigrateTo3_4_1;
 import org.keycloak.migration.migrators.MigrateTo3_4_2;
 import org.keycloak.migration.migrators.MigrateTo4_0_0;
 import org.keycloak.migration.migrators.MigrateTo4_2_0;
+import org.keycloak.migration.migrators.MigrateTo4_6_0;
 import org.keycloak.migration.migrators.Migration;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -74,7 +75,8 @@ public class MigrationModelManager {
             new MigrateTo3_4_1(),
             new MigrateTo3_4_2(),
             new MigrateTo4_0_0(),
-            new MigrateTo4_2_0()
+            new MigrateTo4_2_0(),
+            new MigrateTo4_6_0()
     };
 
     public static void migrate(KeycloakSession session) {
                diff --git a/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java
index 0fcbd63..3cb7e79 100755
--- a/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.migration;
 
+import org.keycloak.models.ClientScopeModel;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.provider.Provider;
@@ -42,4 +43,22 @@ public interface MigrationProvider extends Provider {
 
     void setupAdminCli(RealmModel realm);
 
+
+    /**
+     * Add 'roles' client scope or return it if already exists
+     *
+     * @param realm
+     * @return created or already existing client scope 'roles'
+     */
+    ClientScopeModel addOIDCRolesClientScope(RealmModel realm);
+
+
+    /**
+     * Add 'web-origins' client scope or return it if already exists
+     *
+     * @param realm
+     * @return created or already existing client scope 'web-origins'
+     */
+    ClientScopeModel addOIDCWebOriginsClientScope(RealmModel realm);
+
 }
                diff --git a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo4_6_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo4_6_0.java
new file mode 100644
index 0000000..717c2ed
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo4_6_0.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.migration.migrators;
+
+import org.jboss.logging.Logger;
+import org.keycloak.migration.MigrationProvider;
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientScopeModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.RealmRepresentation;
+
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MigrateTo4_6_0 implements Migration {
+
+    public static final ModelVersion VERSION = new ModelVersion("4.6.0");
+
+    private static final Logger LOG = Logger.getLogger(MigrateTo4_6_0.class);
+
+    @Override
+    public ModelVersion getVersion() {
+        return VERSION;
+    }
+
+    @Override
+    public void migrate(KeycloakSession session) {
+        session.realms().getRealms().stream().forEach(r -> {
+            migrateRealm(session, r, false);
+        });
+    }
+
+    @Override
+    public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) {
+        migrateRealm(session, realm, true);
+    }
+
+    protected void migrateRealm(KeycloakSession session, RealmModel realm, boolean json) {
+        MigrationProvider migrationProvider = session.getProvider(MigrationProvider.class);
+
+        // Create "roles" and "web-origins" clientScopes
+        ClientScopeModel rolesScope = migrationProvider.addOIDCRolesClientScope(realm);
+        ClientScopeModel webOriginsScope = migrationProvider.addOIDCWebOriginsClientScope(realm);
+
+        LOG.debugf("Added '%s' and '%s' default client scopes", rolesScope.getName(), webOriginsScope.getName());
+
+        // Assign "roles" and "web-origins" clientScopes to all the OIDC clients
+        for (ClientModel client : realm.getClients()) {
+            if ((client.getProtocol()==null || "openid-connect".equals(client.getProtocol())) && (!client.isBearerOnly())) {
+                client.addClientScope(rolesScope, true);
+                client.addClientScope(webOriginsScope, true);
+            }
+        }
+
+        LOG.debugf("Client scope '%s' assigned to all the clients", rolesScope.getName());
+    }
+}
                diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapper.java b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapper.java
index 8055fae..8ee0600 100755
--- a/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapper.java
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapper.java
@@ -35,6 +35,14 @@ public interface ProtocolMapper extends Provider, ProviderFactory<ProtocolMapper
     String getDisplayType();
 
     /**
+     * Priority of this protocolMapper implementation. Lower goes first.
+     * @return
+     */
+    default int getPriority() {
+        return 0;
+    }
+
+    /**
      * Called when instance of mapperModel is created/updated for this protocolMapper through admin endpoint
      *
      * @param session
                diff --git a/services/src/main/java/org/keycloak/protocol/docker/DockerAuthV2Protocol.java b/services/src/main/java/org/keycloak/protocol/docker/DockerAuthV2Protocol.java
index fc42d8d..8c3c5bd 100644
--- a/services/src/main/java/org/keycloak/protocol/docker/DockerAuthV2Protocol.java
+++ b/services/src/main/java/org/keycloak/protocol/docker/DockerAuthV2Protocol.java
@@ -16,6 +16,7 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.protocol.docker.mapper.DockerAuthV2AttributeMapper;
 import org.keycloak.representations.docker.DockerResponse;
 import org.keycloak.representations.docker.DockerResponseToken;
@@ -30,6 +31,7 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.Map;
 import java.util.Set;
 
 public class DockerAuthV2Protocol implements LoginProtocol {
@@ -110,9 +112,11 @@ public class DockerAuthV2Protocol implements LoginProtocol {
                 .expiration(responseToken.getIssuedAt() + accessTokenLifespan);
 
         // Next, allow mappers to decorate the token to add/remove scopes as appropriate
-        final Set<ProtocolMapperModel> mappings = clientSessionCtx.getProtocolMappers();
-        for (final ProtocolMapperModel mapping : mappings) {
-            final ProtocolMapper mapper = (ProtocolMapper) session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
+
+        for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
+            ProtocolMapperModel mapping = entry.getKey();
+            ProtocolMapper mapper = entry.getValue();
+
             if (mapper instanceof DockerAuthV2AttributeMapper) {
                 final DockerAuthV2AttributeMapper dockerAttributeMapper = (DockerAuthV2AttributeMapper) mapper;
                 if (dockerAttributeMapper.appliesTo(responseToken)) {
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java
index a7eeb5d..b78d553 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java
@@ -18,7 +18,7 @@
 package org.keycloak.protocol.oidc.mappers;
 
 import org.keycloak.Config;
-import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.ProtocolMapperModel;
@@ -61,35 +61,35 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper {
     }
 
     public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                              UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+                                              UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
 
         if (!OIDCAttributeMapperHelper.includeInUserInfo(mappingModel)) {
             return token;
         }
 
-        setClaim(token, mappingModel, userSession, session);
+        setClaim(token, mappingModel, userSession, session, clientSessionCtx);
         return token;
     }
 
     public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                            UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+                                            UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
 
         if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)){
             return token;
         }
 
-        setClaim(token, mappingModel, userSession, session);
+        setClaim(token, mappingModel, userSession, session, clientSessionCtx);
         return token;
     }
 
     public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                    UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+                                    UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
 
         if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)){
             return token;
         }
 
-        setClaim(token, mappingModel, userSession, session);
+        setClaim(token, mappingModel, userSession, session, clientSessionCtx);
         return token;
     }
 
@@ -99,7 +99,7 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper {
      * @param mappingModel
      * @param userSession
      *
-     * @deprecated override {@link #setClaim(IDToken, ProtocolMapperModel, UserSessionModel, KeycloakSession)} instead.
+     * @deprecated override {@link #setClaim(IDToken, ProtocolMapperModel, UserSessionModel, KeycloakSession, ClientSessionContext)} instead.
      */
     @Deprecated
     protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
@@ -111,8 +111,10 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper {
      * @param mappingModel
      * @param userSession
      * @param keycloakSession
+     * @param clientSessionCtx
      */
-    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession) {
+    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession,
+                            ClientSessionContext clientSessionCtx) {
         // we delegate to the old #setClaim(...) method for backwards compatibility
         setClaim(token, mappingModel, userSession);
     }
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
index 3f124e2..fdc3433 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
@@ -2,6 +2,7 @@ package org.keycloak.protocol.oidc.mappers;
 
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperContainerModel;
 import org.keycloak.models.ProtocolMapperModel;
@@ -64,20 +65,20 @@ public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapp
     }
 
     @Override
-    public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
-        setIDTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+    public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+        setIDTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSessionCtx.getClientSession().getClient(), mappingModel), userSession.getUser().getId()));
         return token;
     }
 
     @Override
-    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
-        setAccessTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+        setAccessTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSessionCtx.getClientSession().getClient(), mappingModel), userSession.getUser().getId()));
         return token;
     }
 
     @Override
-    public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
-        setUserInfoTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+    public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+        setUserInfoTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSessionCtx.getClientSession().getClient(), mappingModel), userSession.getUser().getId()));
         return token;
     }
 
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
index d239951..e933337 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
@@ -17,20 +17,18 @@
 
 package org.keycloak.protocol.oidc.mappers;
 
-import org.keycloak.models.GroupModel;
 import org.keycloak.models.ProtocolMapperModel;
-import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.utils.RoleUtils;
 import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.IDToken;
 
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
-import java.util.function.Predicate;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * Base class for mapping of user role mappings to an ID and Access Token claim.
@@ -39,38 +37,11 @@ import java.util.stream.Stream;
  */
 abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
 
-    /**
-     * Returns a stream with roles that come from:
-     * <ul>
-     * <li>Direct assignment of the role to the user</li>
-     * <li>Direct assignment of the role to any group of the user or any of its parent group</li>
-     * <li>Composite roles are expanded recursively, the composite role itself is also contained in the returned stream</li>
-     * </ul>
-     * @param user User to enumerate the roles for
-     * @return
-     */
-    public static Stream<RoleModel> getAllUserRolesStream(UserModel user) {
-        return Stream.concat(
-          user.getRoleMappings().stream(),
-          user.getGroups().stream()
-            .flatMap(g -> groupAndItsParentsStream(g))
-            .flatMap(g -> g.getRoleMappings().stream()))
-          .flatMap(RoleUtils::expandCompositeRolesStream);
+    @Override
+    public int getPriority() {
+        return ProtocolMapperUtils.PRIORITY_ROLE_MAPPER;
     }
 
-    /**
-     * Returns stream of the given group and its parents (recursively).
-     * @param group
-     * @return
-     */
-    private static Stream<GroupModel> groupAndItsParentsStream(GroupModel group) {
-        Stream.Builder<GroupModel> sb = Stream.builder();
-        while (group != null) {
-            sb.add(group);
-            group = group.getParent();
-        }
-        return sb.build();
-    }
 
     /**
      * Retrieves all roles of the current user based on direct roles set to the user, its groups and their parent groups.
@@ -81,31 +52,22 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper 
      *
      * @param token
      * @param mappingModel
-     * @param userSession
-     * @param restriction
+     * @param rolesToAdd
+     * @param clientId
      * @param prefix
      */
-    protected static void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession,
-      Predicate<RoleModel> restriction, String prefix) {
-        String rolePrefix = prefix == null ? "" : prefix;
-        UserModel user = userSession.getUser();
-
-        // get a set of all realm roles assigned to the user or its group
-        Stream<RoleModel> clientUserRoles = getAllUserRolesStream(user).filter(restriction);
+    protected static void setClaim(IDToken token, ProtocolMapperModel mappingModel, Set<String> rolesToAdd,
+                                   String clientId, String prefix) {
 
-        boolean dontLimitScope = userSession.getAuthenticatedClientSessions().values().stream().anyMatch(cs -> cs.getClient().isFullScopeAllowed());
-        if (! dontLimitScope) {
-            Set<RoleModel> clientRoles = userSession.getAuthenticatedClientSessions().values().stream()
-              .flatMap(cs -> cs.getClient().getScopeMappings().stream())
-              .collect(Collectors.toSet());
-
-            clientUserRoles = clientUserRoles.filter(clientRoles::contains);
+        Set<String> realmRoleNames;
+        if (prefix != null && !prefix.isEmpty()) {
+            realmRoleNames = rolesToAdd.stream()
+                    .map(roleName -> prefix + roleName)
+                    .collect(Collectors.toSet());
+        } else {
+            realmRoleNames = rolesToAdd;
         }
 
-        List<String> realmRoleNames = clientUserRoles
-          .map(m -> rolePrefix + m.getName())
-          .collect(Collectors.toList());
-
         Object claimValue = realmRoleNames;
 
         boolean multiValued = "true".equals(mappingModel.getConfig().get(ProtocolMapperUtils.MULTIVALUED));
@@ -113,6 +75,91 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper 
             claimValue = realmRoleNames.toString();
         }
 
-        OIDCAttributeMapperHelper.mapClaim(token, mappingModel, claimValue);
+        //OIDCAttributeMapperHelper.mapClaim(token, mappingModel, claimValue);
+        mapClaim(token, mappingModel, claimValue, clientId);
+    }
+
+
+    private static final Pattern CLIENT_ID_PATTERN = Pattern.compile("\\$\\{client_id\\}");
+
+    private static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Object attributeValue, String clientId) {
+        attributeValue = OIDCAttributeMapperHelper.mapAttributeValue(mappingModel, attributeValue);
+        if (attributeValue == null) return;
+
+        String protocolClaim = mappingModel.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+        if (protocolClaim == null) {
+            return;
+        }
+
+        if (clientId != null) {
+            protocolClaim = CLIENT_ID_PATTERN.matcher(protocolClaim).replaceAll(clientId);
+        }
+
+        List<String> split = OIDCAttributeMapperHelper.splitClaimPath(protocolClaim);
+
+        // Special case
+        if (checkAccessToken(token, split, attributeValue)) {
+            return;
+        }
+
+        final int length = split.size();
+        int i = 0;
+        Map<String, Object> jsonObject = token.getOtherClaims();
+        for (String component : split) {
+            i++;
+            if (i == length) {
+                // Case when we want to add to existing set of roles
+                Object last = jsonObject.get(component);
+                if (last != null && last instanceof Collection && attributeValue instanceof Collection) {
+                    ((Collection) last).addAll((Collection) attributeValue);
+                } else {
+                    jsonObject.put(component, attributeValue);
+                }
+
+            } else {
+                Map<String, Object> nested = (Map<String, Object>)jsonObject.get(component);
+
+                if (nested == null) {
+                    nested = new HashMap<>();
+                    jsonObject.put(component, nested);
+                }
+
+                jsonObject = nested;
+            }
+        }
+    }
+
+
+    // Special case when roles are put to the access token via "realmAcces, resourceAccess" properties
+    private static boolean checkAccessToken(IDToken idToken, List<String> path, Object attributeValue) {
+        if (!(idToken instanceof AccessToken)) {
+            return false;
+        }
+
+        if (!(attributeValue instanceof Collection)) {
+            return false;
+        }
+
+        Collection<String> roles = (Collection<String>) attributeValue;
+
+        AccessToken token = (AccessToken) idToken;
+        AccessToken.Access access = null;
+        if (path.size() == 2 && "realm_access".equals(path.get(0)) && "roles".equals(path.get(1))) {
+            access = token.getRealmAccess();
+            if (access == null) {
+                access = new AccessToken.Access();
+                token.setRealmAccess(access);
+            }
+        } else if (path.size() == 3 && "resource_access".equals(path.get(0)) && "roles".equals(path.get(2))) {
+            String clientId = path.get(1);
+            access = token.addAccess(clientId);
+        } else {
+            return false;
+        }
+
+        for (String role : roles) {
+            access.addRole(role);
+        }
+        return true;
     }
 }
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AllowedWebOriginsProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AllowedWebOriginsProtocolMapper.java
new file mode 100644
index 0000000..65c0044
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AllowedWebOriginsProtocolMapper.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.protocol.oidc.mappers;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.UriInfo;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionContext;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.AccessToken;
+
+/**
+ * Protocol mapper to add allowed web origins to the access token to the 'allowed-origins' claim
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AllowedWebOriginsProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+
+    public static final String PROVIDER_ID = "oidc-allowed-origins-mapper";
+
+
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Allowed Web Origins";
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return TOKEN_MAPPER_CATEGORY;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Adds all allowed web origins to the 'allowed-origins' claim in the token";
+    }
+
+    @Override
+    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
+                                            UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+        ClientModel client = clientSessionCtx.getClientSession().getClient();
+        UriInfo uriInfo = session.getContext().getUri();
+
+        Set<String> allowedOrigins = client.getWebOrigins();
+        if (allowedOrigins != null && !allowedOrigins.isEmpty()) {
+            token.setAllowedOrigins(WebOriginsUtils.resolveValidWebOrigins(uriInfo, client));
+        }
+
+        return token;
+    }
+
+
+    public static ProtocolMapperModel createClaimMapper(String name) {
+        ProtocolMapperModel mapper = new ProtocolMapperModel();
+        mapper.setName(name);
+        mapper.setProtocolMapper(PROVIDER_ID);
+        mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        mapper.setConfig(Collections.emptyMap());
+        return mapper;
+    }
+
+}
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java
index bf63858..dcd0f98 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserSessionModel;
@@ -98,7 +99,8 @@ public class AudienceProtocolMapper extends AbstractOIDCProtocolMapper implement
         return "Add specified audience to the audience (aud) field of token";
     }
 
-    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession) {
+    @Override
+    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) {
         String audienceValue = mappingModel.getConfig().get(INCLUDED_CLIENT_AUDIENCE);
 
         if (audienceValue == null) {
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceResolveProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceResolveProtocolMapper.java
new file mode 100644
index 0000000..a50861c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceResolveProtocolMapper.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.protocol.oidc.mappers;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.keycloak.models.ClientSessionContext;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.utils.RoleResolveUtil;
+
+/**
+ * Protocol mapper, which adds all client_ids of "allowed" clients to the audience field of the token. Allowed client means the client
+ * for which user has at least one client role
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AudienceResolveProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+
+    public static final String PROVIDER_ID = "oidc-audience-resolve-mapper";
+
+
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Audience Resolve";
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return TOKEN_MAPPER_CATEGORY;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Adds all client_ids of \"allowed\" clients to the audience field of the token. Allowed client means the client\n" +
+                " for which user has at least one client role";
+    }
+
+    @Override
+    public int getPriority() {
+        return ProtocolMapperUtils.PRIORITY_AUDIENCE_RESOLVE_MAPPER;
+    }
+
+    @Override
+    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
+                                            UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
+        for (Map.Entry<String, AccessToken.Access> entry : RoleResolveUtil.getAllResolvedClientRoles(session, clientSessionCtx).entrySet()) {
+            AccessToken.Access access = entry.getValue();
+            if (access != null && access.getRoles() != null && !access.getRoles().isEmpty()) {
+                token.addAudience(entry.getKey());
+            }
+        }
+
+        return token;
+    }
+
+    public static ProtocolMapperModel createClaimMapper(String name) {
+        ProtocolMapperModel mapper = new ProtocolMapperModel();
+        mapper.setName(name);
+        mapper.setProtocolMapper(PROVIDER_ID);
+        mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        mapper.setConfig(Collections.emptyMap());
+        return mapper;
+    }
+}
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java
index 19ff925..4ab9eba 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java
@@ -17,14 +17,16 @@
 
 package org.keycloak.protocol.oidc.mappers;
 
-import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.utils.RoleResolveUtil;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -81,21 +83,23 @@ public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAcc
     }
 
     @Override
+    public int getPriority() {
+        return ProtocolMapperUtils.PRIORITY_HARDCODED_ROLE_MAPPER;
+    }
+
+    @Override
     public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                            UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+                                            UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
 
         String role = mappingModel.getConfig().get(ROLE_CONFIG);
         String[] scopedRole = KeycloakModelUtils.parseRole(role);
         String appName = scopedRole[0];
         String roleName = scopedRole[1];
         if (appName != null) {
-            token.addAccess(appName).addRole(roleName);
+            AccessToken.Access access = RoleResolveUtil.getResolvedClientRoles(session, clientSessionCtx, appName, true);
+            access.addRole(roleName);
         } else {
-            AccessToken.Access access = token.getRealmAccess();
-            if (access == null) {
-                access = new AccessToken.Access();
-                token.setRealmAccess(access);
-            }
+            AccessToken.Access access = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, true);
             access.addRole(role);
         }
 
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAccessTokenMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAccessTokenMapper.java
index e7e0b7b..ff11ab9 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAccessTokenMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAccessTokenMapper.java
@@ -18,6 +18,7 @@
 package org.keycloak.protocol.oidc.mappers;
 
 import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserSessionModel;
@@ -30,5 +31,5 @@ import org.keycloak.representations.AccessToken;
 public interface OIDCAccessTokenMapper {
 
     AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                     UserSessionModel userSession, AuthenticatedClientSessionModel clientSession);
+                                     UserSessionModel userSession, ClientSessionContext clientSessionCtx);
 }
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java
index 933654c..2ef4549 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java
@@ -56,8 +56,8 @@ public class OIDCAttributeMapperHelper {
     public static Object mapAttributeValue(ProtocolMapperModel mappingModel, Object attributeValue) {
         if (attributeValue == null) return null;
 
-        if (attributeValue instanceof List) {
-            List<Object> valueAsList = (List<Object>) attributeValue;
+        if (attributeValue instanceof Collection) {
+            Collection<Object> valueAsList = (Collection<Object>) attributeValue;
             if (valueAsList.isEmpty()) return null;
 
             if (isMultivalued(mappingModel)) {
@@ -71,7 +71,7 @@ public class OIDCAttributeMapperHelper {
                     ServicesLogger.LOGGER.multipleValuesForMapper(attributeValue.toString(), mappingModel.getName());
                 }
 
-                attributeValue = valueAsList.get(0);
+                attributeValue = valueAsList.iterator().next();
             }
         }
 
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCIDTokenMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCIDTokenMapper.java
index 54f380b..a69f1db 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCIDTokenMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCIDTokenMapper.java
@@ -18,6 +18,7 @@
 package org.keycloak.protocol.oidc.mappers;
 
 import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserSessionModel;
@@ -30,5 +31,5 @@ import org.keycloak.representations.IDToken;
 public interface OIDCIDTokenMapper {
 
     IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                               UserSessionModel userSession, AuthenticatedClientSessionModel clientSession);
+                               UserSessionModel userSession, ClientSessionContext clientSession);
 }
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java
index d41063b..9a41a29 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java
@@ -17,14 +17,16 @@
 
 package org.keycloak.protocol.oidc.mappers;
 
-import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.utils.RoleResolveUtil;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -88,8 +90,13 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
     }
 
     @Override
+    public int getPriority() {
+        return ProtocolMapperUtils.PRIORITY_ROLE_NAMES_MAPPER;
+    }
+
+    @Override
     public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                            UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+                                            UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
         String role = mappingModel.getConfig().get(ROLE_CONFIG);
         String newName = mappingModel.getConfig().get(NEW_ROLE_NAME);
 
@@ -98,12 +105,12 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
         String appName = scopedRole[0];
         String roleName = scopedRole[1];
         if (appName != null) {
-            AccessToken.Access access = token.getResourceAccess(appName);
+            AccessToken.Access access = RoleResolveUtil.getResolvedClientRoles(session, clientSessionCtx, appName, false);
             if (access == null) return token;
             if (!access.getRoles().contains(roleName)) return token;
             access.getRoles().remove(roleName);
         } else {
-            AccessToken.Access access = token.getRealmAccess();
+            AccessToken.Access access = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, false);
             if (access == null || !access.getRoles().contains(roleName)) return token;
             access.getRoles().remove(roleName);
         }
@@ -112,13 +119,9 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
         String newRoleName = newScopedRole[1];
         AccessToken.Access access = null;
         if (newAppName == null) {
-            access = token.getRealmAccess();
-            if (access == null) {
-                access = new AccessToken.Access();
-                token.setRealmAccess(access);
-            }
+            access = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, true);
         } else {
-            access = token.addAccess(newAppName);
+            access = RoleResolveUtil.getResolvedClientRoles(session, clientSessionCtx, newAppName, true);
         }
 
         access.addRole(newRoleName);
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java
index 72e833b..bee4b88 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java
@@ -19,6 +19,7 @@ package org.keycloak.protocol.oidc.mappers;
 
 import org.jboss.logging.Logger;
 import org.keycloak.common.Profile;
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperContainerModel;
 import org.keycloak.models.ProtocolMapperModel;
@@ -120,7 +121,13 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im
     return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS);
   }
 
-  protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession) {
+  @Override
+  public int getPriority() {
+    return ProtocolMapperUtils.PRIORITY_SCRIPT_MAPPER;
+  }
+
+  @Override
+  protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) {
 
     UserModel user = userSession.getUser();
     String scriptSource = mappingModel.getConfig().get(SCRIPT);
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java
index db064fa..ca57bd6 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java
@@ -17,24 +17,19 @@
 
 package org.keycloak.protocol.oidc.mappers;
 
-import org.keycloak.OAuth2Constants;
-import org.keycloak.models.AuthenticatedClientSessionModel;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientScopeModel;
+import org.keycloak.models.ClientSessionContext;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.ProtocolMapperUtils;
-import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.IDToken;
+import org.keycloak.utils.RoleResolveUtil;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
+import java.util.Map;
 
 /**
  * Allows mapping of user client role mappings to an ID and Access Token claim.
@@ -45,6 +40,8 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
 
     public static final String PROVIDER_ID = "oidc-usermodel-client-role-mapper";
 
+    private static final String TOKEN_CLAIM_NAME_TOOLTIP = "usermodel.clientRoleMapping.tokenClaimName.tooltip";
+
     private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
 
     static {
@@ -68,10 +65,17 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
         multiValued.setLabel(ProtocolMapperUtils.MULTIVALUED_LABEL);
         multiValued.setHelpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT);
         multiValued.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        multiValued.setDefaultValue(false);
+        multiValued.setDefaultValue("true");
         CONFIG_PROPERTIES.add(multiValued);
 
         OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserClientRoleMappingMapper.class);
+
+        // Alternative tooltip for the 'Token Claim Name'
+        for (ProviderConfigProperty prop : CONFIG_PROPERTIES) {
+            if (OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME.equals(prop.getName())) {
+                prop.setHelpText(TOKEN_CLAIM_NAME_TOOLTIP);
+            }
+        }
     }
 
     @Override
@@ -100,48 +104,34 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
     }
 
     @Override
-    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
+    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession session, ClientSessionContext clientSessionCtx) {
         String clientId = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID);
         String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX);
 
-        setClaim(token, mappingModel, userSession, getClientRoleFilter(clientId, userSession), rolePrefix);
-    }
-
-    private static Predicate<RoleModel> getClientRoleFilter(String clientId, UserSessionModel userSession) {
-        if (clientId == null) {
-            return RoleModel::isClientRole;
-        }
-
-        RealmModel clientRealm = userSession.getRealm();
-        ClientModel client = clientRealm.getClientByClientId(clientId.trim());
-
-        if (client == null) {
-            return RoleModel::isClientRole;
-        }
-
-        boolean fullScopeAllowed = client.isFullScopeAllowed();
-        Set<RoleModel> clientRoleMappings = client.getRoles();
-        if (fullScopeAllowed) {
-            return clientRoleMappings::contains;
+        if (clientId != null && !clientId.isEmpty()) {
+            AccessToken.Access access = RoleResolveUtil.getResolvedClientRoles(session, clientSessionCtx, clientId, false);
+            if (access == null) {
+                return;
+            }
+
+            AbstractUserRoleMappingMapper.setClaim(token, mappingModel, access.getRoles(), clientId, rolePrefix);
+        } else {
+            // If clientId is not specified, we consider all clients
+            Map<String, AccessToken.Access> allAccess = RoleResolveUtil.getAllResolvedClientRoles(session, clientSessionCtx);
+
+            for (Map.Entry<String, AccessToken.Access> entry : allAccess.entrySet()) {
+                String currClientId = entry.getKey();
+                AccessToken.Access access = entry.getValue();
+                if (access == null) {
+                    continue;
+                }
+
+                AbstractUserRoleMappingMapper.setClaim(token, mappingModel, access.getRoles(), currClientId, rolePrefix);
+            }
         }
-
-        Set<RoleModel> scopeMappings = new HashSet<>();
-
-        // Add scope mappings of current client + all clientScopes of this client (including optional scopes if scope parameter matches)
-        String scopeParam = null;
-        AuthenticatedClientSessionModel authClientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
-        if (authClientSession != null) {
-            scopeParam = authClientSession.getNote(OAuth2Constants.SCOPE);
-        }
-
-        Set<ClientScopeModel> clientScopes = TokenManager.getRequestedClientScopes(scopeParam, client);
-        for (ClientScopeModel clientScope : clientScopes) {
-            scopeMappings.addAll(clientScope.getScopeMappings());
-        }
-
-        return role -> clientRoleMappings.contains(role) && scopeMappings.contains(role);
     }
 
+
     public static ProtocolMapperModel create(String clientId, String clientRolePrefix,
                                              String name,
                                              String tokenClaimName,
@@ -156,7 +146,7 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
                                              boolean accessToken, boolean idToken, boolean multiValued) {
         ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo",
                 tokenClaimName, "String",
-                accessToken, idToken,
+                accessToken, idToken, false,
                 PROVIDER_ID);
 
         mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, String.valueOf(multiValued));
@@ -164,4 +154,5 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper {
         mapper.getConfig().put(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX, clientRolePrefix);
         return mapper;
     }
+
 }
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserInfoTokenMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserInfoTokenMapper.java
index e1fc17e..d3f80cb 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserInfoTokenMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserInfoTokenMapper.java
@@ -18,6 +18,7 @@
 package org.keycloak.protocol.oidc.mappers;
 
 import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserSessionModel;
@@ -29,5 +30,5 @@ import org.keycloak.representations.AccessToken;
 public interface UserInfoTokenMapper {
 
     AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
-                                               UserSessionModel userSession, AuthenticatedClientSessionModel clientSession);
+                                               UserSessionModel userSession, ClientSessionContext clientSessionCtx);
 }
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java
index 59eaf4b..1f817d6 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java
@@ -17,11 +17,15 @@
 
 package org.keycloak.protocol.oidc.mappers;
 
+import org.keycloak.models.ClientSessionContext;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.IDToken;
+import org.keycloak.utils.RoleResolveUtil;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -51,7 +55,7 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
         multiValued.setLabel(ProtocolMapperUtils.MULTIVALUED_LABEL);
         multiValued.setHelpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT);
         multiValued.setType(ProviderConfigProperty.BOOLEAN_TYPE);
-        multiValued.setDefaultValue(false);
+        multiValued.setDefaultValue("true");
         CONFIG_PROPERTIES.add(multiValued);
 
         OIDCAttributeMapperHelper.addAttributeConfig(CONFIG_PROPERTIES, UserRealmRoleMappingMapper.class);
@@ -83,9 +87,15 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
     }
 
     @Override
-    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
+    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession session, ClientSessionContext clientSessionCtx) {
         String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX);
-        AbstractUserRoleMappingMapper.setClaim(token, mappingModel, userSession, role -> ! role.isClientRole(), rolePrefix);
+
+        AccessToken.Access access = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, false);
+        if (access == null) {
+            return;
+        }
+
+        AbstractUserRoleMappingMapper.setClaim(token, mappingModel, access.getRoles(),null, rolePrefix);
     }
 
     public static ProtocolMapperModel create(String realmRolePrefix,
@@ -100,7 +110,7 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper {
                                              String tokenClaimName, boolean accessToken, boolean idToken, boolean multiValued) {
         ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo",
           tokenClaimName, "String",
-          accessToken, idToken,
+          accessToken, idToken, false,
           PROVIDER_ID);
 
         mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, String.valueOf(multiValued));
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
index ca4ddc4..f5371e3 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
@@ -33,22 +33,20 @@ import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.AbstractLoginProtocolFactory;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.oidc.mappers.AddressMapper;
+import org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper;
+import org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper;
 import org.keycloak.protocol.oidc.mappers.FullNameMapper;
-import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
 import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
+import org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper;
 import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
+import org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper;
 import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
 import org.keycloak.representations.IDToken;
 import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.ClientScopeRepresentation;
 import org.keycloak.services.ServicesLogger;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -78,12 +76,20 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
     public static final String ADDRESS = "address";
     public static final String PHONE_NUMBER = "phone number";
     public static final String PHONE_NUMBER_VERIFIED = "phone number verified";
+    public static final String REALM_ROLES = "realm roles";
+    public static final String CLIENT_ROLES = "client roles";
+    public static final String AUDIENCE_RESOLVE = "audience resolve";
+    public static final String ALLOWED_WEB_ORIGINS = "allowed web origins";
+
+    public static final String ROLES_SCOPE = "roles";
+    public static final String WEB_ORIGINS_SCOPE = "web-origins";
 
     public static final String PROFILE_SCOPE_CONSENT_TEXT = "${profileScopeConsentText}";
     public static final String EMAIL_SCOPE_CONSENT_TEXT = "${emailScopeConsentText}";
     public static final String ADDRESS_SCOPE_CONSENT_TEXT = "${addressScopeConsentText}";
     public static final String PHONE_SCOPE_CONSENT_TEXT = "${phoneScopeConsentText}";
     public static final String OFFLINE_ACCESS_SCOPE_CONSENT_TEXT = Constants.OFFLINE_ACCESS_SCOPE_CONSENT_TEXT;
+    public static final String ROLES_SCOPE_CONSENT_TEXT = "${rolesScopeConsentText}";
 
 
     @Override
@@ -155,6 +161,18 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
                 KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
                 true, false);
         builtins.put(KerberosConstants.GSS_DELEGATION_CREDENTIAL, model);
+
+        model = UserRealmRoleMappingMapper.create(null, REALM_ROLES, "realm_access.roles", true, false, true);
+        builtins.put(REALM_ROLES, model);
+
+        model = UserClientRoleMappingMapper.create(null, null, CLIENT_ROLES, "resource_access.${client_id}.roles", true, false, true);
+        builtins.put(CLIENT_ROLES, model);
+
+        model = AudienceResolveProtocolMapper.createClaimMapper(AUDIENCE_RESOLVE);
+        builtins.put(AUDIENCE_RESOLVE, model);
+
+        model = AllowedWebOriginsProtocolMapper.createClaimMapper(ALLOWED_WEB_ORIGINS);
+        builtins.put(ALLOWED_WEB_ORIGINS, model);
     }
 
     private static void createUserAttributeMapper(String name, String attrName, String claimName, String type) {
@@ -172,6 +190,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
         profileScope.setDescription("OpenID Connect built-in scope: profile");
         profileScope.setDisplayOnConsentScreen(true);
         profileScope.setConsentScreenText(PROFILE_SCOPE_CONSENT_TEXT);
+        profileScope.setIncludeInTokenScope(true);
         profileScope.setProtocol(getId());
         profileScope.addProtocolMapper(builtins.get(FULL_NAME));
         profileScope.addProtocolMapper(builtins.get(FAMILY_NAME));
@@ -192,6 +211,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
         emailScope.setDescription("OpenID Connect built-in scope: email");
         emailScope.setDisplayOnConsentScreen(true);
         emailScope.setConsentScreenText(EMAIL_SCOPE_CONSENT_TEXT);
+        emailScope.setIncludeInTokenScope(true);
         emailScope.setProtocol(getId());
         emailScope.addProtocolMapper(builtins.get(EMAIL));
         emailScope.addProtocolMapper(builtins.get(EMAIL_VERIFIED));
@@ -200,6 +220,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
         addressScope.setDescription("OpenID Connect built-in scope: address");
         addressScope.setDisplayOnConsentScreen(true);
         addressScope.setConsentScreenText(ADDRESS_SCOPE_CONSENT_TEXT);
+        addressScope.setIncludeInTokenScope(true);
         addressScope.setProtocol(getId());
         addressScope.addProtocolMapper(builtins.get(ADDRESS));
 
@@ -207,6 +228,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
         phoneScope.setDescription("OpenID Connect built-in scope: phone");
         phoneScope.setDisplayOnConsentScreen(true);
         phoneScope.setConsentScreenText(PHONE_SCOPE_CONSENT_TEXT);
+        phoneScope.setIncludeInTokenScope(true);
         phoneScope.setProtocol(getId());
         phoneScope.addProtocolMapper(builtins.get(PHONE_NUMBER));
         phoneScope.addProtocolMapper(builtins.get(PHONE_NUMBER_VERIFIED));
@@ -224,8 +246,56 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
                 DefaultClientScopes.createOfflineAccessClientScope(newRealm, offlineRole);
             }
         }
+
+        addRolesClientScope(newRealm);
+        addWebOriginsClientScope(newRealm);
+    }
+
+
+    public static ClientScopeModel addRolesClientScope(RealmModel newRealm) {
+        ClientScopeModel rolesScope = KeycloakModelUtils.getClientScopeByName(newRealm, ROLES_SCOPE);
+        if (rolesScope == null) {
+            rolesScope = newRealm.addClientScope(ROLES_SCOPE);
+            rolesScope.setDescription("OpenID Connect scope for add user roles to the access token");
+            rolesScope.setDisplayOnConsentScreen(true);
+            rolesScope.setConsentScreenText(ROLES_SCOPE_CONSENT_TEXT);
+            rolesScope.setIncludeInTokenScope(false);
+            rolesScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+            rolesScope.addProtocolMapper(builtins.get(REALM_ROLES));
+            rolesScope.addProtocolMapper(builtins.get(CLIENT_ROLES));
+            rolesScope.addProtocolMapper(builtins.get(AUDIENCE_RESOLVE));
+
+            // 'roles' will be default client scope
+            newRealm.addDefaultClientScope(rolesScope, true);
+        } else {
+            logger.debugf("Client scope '%s' already exists in realm '%s'. Skip creating it.", ROLES_SCOPE, newRealm.getName());
+        }
+
+        return rolesScope;
+    }
+
+
+    public static ClientScopeModel addWebOriginsClientScope(RealmModel newRealm) {
+        ClientScopeModel originsScope = KeycloakModelUtils.getClientScopeByName(newRealm, WEB_ORIGINS_SCOPE);
+        if (originsScope == null) {
+            originsScope = newRealm.addClientScope(WEB_ORIGINS_SCOPE);
+            originsScope.setDescription("OpenID Connect scope for add allowed web origins to the access token");
+            originsScope.setDisplayOnConsentScreen(false); // No requesting consent from user for this. It is rather the permission of client
+            originsScope.setConsentScreenText("");
+            originsScope.setIncludeInTokenScope(false);
+            originsScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+            originsScope.addProtocolMapper(builtins.get(ALLOWED_WEB_ORIGINS));
+
+            // 'web-origins' will be default client scope
+            newRealm.addDefaultClientScope(originsScope, true);
+        } else {
+            logger.debugf("Client scope '%s' already exists in realm '%s'. Skip creating it.", WEB_ORIGINS_SCOPE, newRealm.getName());
+        }
+
+        return originsScope;
     }
 
+
     @Override
     protected void addDefaults(ClientModel client) {
     }
                diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 9ff893c..d5ee3e9 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -47,6 +47,7 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
 import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
 import org.keycloak.protocol.oidc.mappers.UserInfoTokenMapper;
@@ -414,11 +415,7 @@ public class TokenManager {
 
     public AccessToken createClientAccessToken(KeycloakSession session, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession,
                                                ClientSessionContext clientSessionCtx) {
-        Set<RoleModel> requestedRoles = clientSessionCtx.getRoles();
         AccessToken token = initToken(realm, client, user, userSession, clientSessionCtx, session.getContext().getUri());
-        for (RoleModel role : requestedRoles) {
-            addComposites(token, role);
-        }
         token = transformAccessToken(session, token, userSession, clientSessionCtx);
         return token;
     }
@@ -597,13 +594,12 @@ public class TokenManager {
 
     public AccessToken transformAccessToken(KeycloakSession session, AccessToken token,
                                             UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
-        Set<ProtocolMapperModel> mappings = clientSessionCtx.getProtocolMappers();
-        KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-        for (ProtocolMapperModel mapping : mappings) {
 
-            ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
+        for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
+            ProtocolMapperModel mapping = entry.getKey();
+            ProtocolMapper mapper = entry.getValue();
             if (mapper instanceof OIDCAccessTokenMapper) {
-                token = ((OIDCAccessTokenMapper) mapper).transformAccessToken(token, mapping, session, userSession, clientSessionCtx.getClientSession());
+                token = ((OIDCAccessTokenMapper) mapper).transformAccessToken(token, mapping, session, userSession, clientSessionCtx);
             }
         }
 
@@ -612,13 +608,13 @@ public class TokenManager {
 
     public AccessToken transformUserInfoAccessToken(KeycloakSession session, AccessToken token,
                                             UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
-        Set<ProtocolMapperModel> mappings = clientSessionCtx.getProtocolMappers();
-        KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-        for (ProtocolMapperModel mapping : mappings) {
 
-            ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
+        for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
+            ProtocolMapperModel mapping = entry.getKey();
+            ProtocolMapper mapper = entry.getValue();
+
             if (mapper instanceof UserInfoTokenMapper) {
-                token = ((UserInfoTokenMapper) mapper).transformUserInfoToken(token, mapping, session, userSession, clientSessionCtx.getClientSession());
+                token = ((UserInfoTokenMapper) mapper).transformUserInfoToken(token, mapping, session, userSession, clientSessionCtx);
             }
         }
 
@@ -627,13 +623,13 @@ public class TokenManager {
 
     public void transformIDToken(KeycloakSession session, IDToken token,
                                       UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
-        Set<ProtocolMapperModel> mappings = clientSessionCtx.getProtocolMappers();
-        KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-        for (ProtocolMapperModel mapping : mappings) {
 
-            ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
+        for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
+            ProtocolMapperModel mapping = entry.getKey();
+            ProtocolMapper mapper = entry.getValue();
+
             if (mapper instanceof OIDCIDTokenMapper) {
-                token = ((OIDCIDTokenMapper) mapper).transformIDToken(token, mapping, session, userSession, clientSessionCtx.getClientSession());
+                token = ((OIDCIDTokenMapper) mapper).transformIDToken(token, mapping, session, userSession, clientSessionCtx);
             }
         }
     }
@@ -667,10 +663,6 @@ public class TokenManager {
         token.setSessionState(session.getId());
         token.expiration(getTokenExpiration(realm, client, session, clientSession));
 
-        Set<String> allowedOrigins = client.getWebOrigins();
-        if (allowedOrigins != null) {
-            token.setAllowedOrigins(WebOriginsUtils.resolveValidWebOrigins(uriInfo, client));
-        }
         return token;
     }
 
@@ -709,33 +701,6 @@ public class TokenManager {
         return expiration;
     }
 
-    protected void addComposites(AccessToken token, RoleModel role) {
-        AccessToken.Access access = null;
-        if (role.getContainer() instanceof RealmModel) {
-            access = token.getRealmAccess();
-            if (token.getRealmAccess() == null) {
-                access = new AccessToken.Access();
-                token.setRealmAccess(access);
-            } else if (token.getRealmAccess().getRoles() != null && token.getRealmAccess().isUserInRole(role.getName()))
-                return;
-
-        } else {
-            ClientModel app = (ClientModel) role.getContainer();
-            access = token.getResourceAccess(app.getClientId());
-            if (access == null) {
-                access = token.addAccess(app.getClientId());
-                if (app.isSurrogateAuthRequired()) access.verifyCaller(true);
-            } else if (access.isUserInRole(role.getName())) return;
-
-        }
-        access.addRole(role.getName());
-        if (!role.isComposite()) return;
-
-        for (RoleModel composite : role.getComposites()) {
-            addComposites(token, composite);
-        }
-
-    }
 
     public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session,
                                                       UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
                diff --git a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java
index 48c595c..4db5393 100755
--- a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java
@@ -17,7 +17,9 @@
 
 package org.keycloak.protocol;
 
+import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -25,6 +27,12 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
 import org.keycloak.provider.ProviderFactory;
 
 import java.lang.reflect.Method;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -58,6 +66,21 @@ public class ProtocolMapperUtils {
     public static final String MULTIVALUED_LABEL = "multivalued.label";
     public static final String MULTIVALUED_HELP_TEXT = "multivalued.tooltip";
 
+    // Role name mapper can move some roles to different positions
+    public static final int PRIORITY_ROLE_NAMES_MAPPER = 10;
+
+    // Hardcoded role mapper can be used to add some roles
+    public static final int PRIORITY_HARDCODED_ROLE_MAPPER = 20;
+
+    // Audiences can be resolved once all the roles are correctly set
+    public static final int PRIORITY_AUDIENCE_RESOLVE_MAPPER = 30;
+
+    // Add roles to tokens finally
+    public static final int PRIORITY_ROLE_MAPPER = 40;
+
+    // Script mapper goes last, so it can access the roles in the token
+    public static final int PRIORITY_SCRIPT_MAPPER = 50;
+
     public static String getUserModelValue(UserModel user, String propertyName) {
 
         String methodName = "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
@@ -95,4 +118,31 @@ public class ProtocolMapperUtils {
         }
         return null;
     }
+
+
+    public static List<Map.Entry<ProtocolMapperModel, ProtocolMapper>> getSortedProtocolMappers(KeycloakSession session, ClientSessionContext ctx) {
+        Set<ProtocolMapperModel> mapperModels = ctx.getProtocolMappers();
+        Map<ProtocolMapperModel, ProtocolMapper> result = new HashMap<>();
+
+        KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+        for (ProtocolMapperModel mapperModel : mapperModels) {
+            ProtocolMapper mapper = (ProtocolMapper) sessionFactory.getProviderFactory(ProtocolMapper.class, mapperModel.getProtocolMapper());
+            if (mapper == null) {
+                continue;
+            }
+
+            result.put(mapperModel, mapper);
+        }
+
+        return result.entrySet()
+                .stream()
+                .sorted(Comparator.comparing(ProtocolMapperUtils::compare))
+                .collect(Collectors.toList());
+    }
+
+    public static int compare(Map.Entry<ProtocolMapperModel, ProtocolMapper> entry) {
+        int priority = entry.getValue().getPriority();
+        return priority;
+    }
+
 }
                diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
index 835c24c..dd70b53 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
@@ -26,6 +26,7 @@ import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.RoleUtils;
 import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.protocol.saml.SamlProtocol;
 import org.keycloak.provider.ProviderConfigProperty;
 
@@ -114,13 +115,11 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
         boolean singleAttribute = Boolean.parseBoolean(single);
 
         List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>();
-        KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
         AttributeType singleAttributeType = null;
-        Set<ProtocolMapperModel> requestedProtocolMappers = clientSessionCtx.getProtocolMappers();
-        for (ProtocolMapperModel mapping : requestedProtocolMappers) {
 
-            ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
-            if (mapper == null) continue;
+        for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
+            ProtocolMapperModel mapping = entry.getKey();
+            ProtocolMapper mapper = entry.getValue();
 
             if (mapper instanceof SAMLRoleNameMapper) {
                 roleNameMappers.add(new SamlProtocol.ProtocolMapperProcessor<>((SAMLRoleNameMapper) mapper,mapping));
                diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 52d271a..65a6059 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -43,6 +43,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.protocol.saml.mappers.SAMLAttributeStatementMapper;
 import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper;
 import org.keycloak.protocol.saml.mappers.SAMLRoleListMapper;
@@ -407,12 +408,10 @@ public class SamlProtocol implements LoginProtocol {
         List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> loginResponseMappers = new LinkedList<>();
         ProtocolMapperProcessor<SAMLRoleListMapper> roleListMapper = null;
 
-        Set<ProtocolMapperModel> mappings = clientSessionCtx.getProtocolMappers();
-        for (ProtocolMapperModel mapping : mappings) {
+        for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
+            ProtocolMapperModel mapping = entry.getKey();
+            ProtocolMapper mapper = entry.getValue();
 
-            ProtocolMapper mapper = (ProtocolMapper) session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
-            if (mapper == null)
-                continue;
             if (mapper instanceof SAMLAttributeStatementMapper) {
                 attributeStatementMappers.add(new ProtocolMapperProcessor<SAMLAttributeStatementMapper>((SAMLAttributeStatementMapper) mapper, mapping));
             }
                diff --git a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java
index 747bdad..0c2e5f5 100755
--- a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java
@@ -19,6 +19,7 @@ package org.keycloak.services.migration;
 
 import org.keycloak.migration.MigrationProvider;
 import org.keycloak.models.ClaimMask;
+import org.keycloak.models.ClientScopeModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -83,6 +84,19 @@ public class DefaultMigrationProvider implements MigrationProvider {
         new RealmManager(session).setupAdminCli(realm);
     }
 
+
+    @Override
+    public ClientScopeModel addOIDCRolesClientScope(RealmModel realm) {
+        return OIDCLoginProtocolFactory.addRolesClientScope(realm);
+    }
+
+
+    @Override
+    public ClientScopeModel addOIDCWebOriginsClientScope(RealmModel realm) {
+        return OIDCLoginProtocolFactory.addWebOriginsClientScope(realm);
+    }
+
+
     @Override
     public void close() {
     }
                diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopesResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopesResource.java
index 91fb49a..1b948e8 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopesResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopesResource.java
@@ -156,6 +156,8 @@ public class ClientScopesResource {
             consentText = consentText.substring(0, 1).toUpperCase() + consentText.substring(1);
             clientScopeModel.setConsentScreenText(consentText);
 
+            clientScopeModel.setIncludeInTokenScope(true);
+
             // Add audience protocol mapper
             ProtocolMapperModel audienceMapper = AudienceProtocolMapper.createClaimMapper("Audience for " + clientId, clientId, null,true, false);
             clientScopeModel.addProtocolMapper(audienceMapper);
                diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
index 297bf97..fc10663 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
@@ -257,6 +257,7 @@ public class ServerInfoAdminResource {
             rep.setName(mapper.getDisplayType());
             rep.setHelpText(mapper.getHelpText());
             rep.setCategory(mapper.getDisplayCategory());
+            rep.setPriority(mapper.getPriority());
             rep.setProperties(new LinkedList<ConfigPropertyRepresentation>());
             List<ProviderConfigProperty> configProperties = mapper.getConfigProperties();
             rep.setProperties(ModelToRepresentation.toRepresentation(configProperties));
                diff --git a/services/src/main/java/org/keycloak/services/util/DefaultClientSessionContext.java b/services/src/main/java/org/keycloak/services/util/DefaultClientSessionContext.java
index 8211698..debd700 100644
--- a/services/src/main/java/org/keycloak/services/util/DefaultClientSessionContext.java
+++ b/services/src/main/java/org/keycloak/services/util/DefaultClientSessionContext.java
@@ -141,6 +141,10 @@ public class DefaultClientSessionContext implements ClientSessionContext {
                 continue;
             }
 
+            if (!clientScope.isIncludeInTokenScope()) {
+                continue;
+            }
+
             if (first) {
                 first = false;
             } else {
                diff --git a/services/src/main/java/org/keycloak/utils/RoleResolveUtil.java b/services/src/main/java/org/keycloak/utils/RoleResolveUtil.java
new file mode 100644
index 0000000..5f095ec
--- /dev/null
+++ b/services/src/main/java/org/keycloak/utils/RoleResolveUtil.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.utils;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionContext;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.services.util.DefaultClientSessionContext;
+
+/**
+ * Helper class to ensure that all the user's permitted roles (including composite roles) are loaded just once per request.
+ * Then all underlying protocolMappers can consume them.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RoleResolveUtil {
+
+    private static final String RESOLVED_ROLES_ATTR = "RESOLVED_ROLES";
+
+
+    /**
+     * Object (possibly null) containing all the user's realm roles. Including user's groups roles. Composite roles are expanded.
+     * Just the roles, which current client has role-scope-mapping for (or it's clientScopes) are included.
+     * Current client means the client corresponding to specified clientSessionCtx.
+     *
+     * @param session
+     * @param clientSessionCtx
+     * @param createIfMissing
+     * @return can return null (just in case that createIfMissing is false)
+     */
+    public static AccessToken.Access getResolvedRealmRoles(KeycloakSession session, ClientSessionContext clientSessionCtx, boolean createIfMissing) {
+        AccessToken rolesToken = getAllCompositeRoles(session, clientSessionCtx);
+        AccessToken.Access access = rolesToken.getRealmAccess();
+        if (access == null && createIfMissing) {
+            access = new AccessToken.Access();
+            rolesToken.setRealmAccess(access);
+        }
+
+        return access;
+    }
+
+
+    /**
+     * Object (possibly null) containing all the user's client roles of client specified by clientId. Including user's groups roles.
+     * Composite roles are expanded. Just the roles, which current client has role-scope-mapping for (or it's clientScopes) are included.
+     * Current client means the client corresponding to specified clientSessionCtx.
+     *
+     * @param session
+     * @param clientSessionCtx
+     * @param clientId
+     * @param createIfMissing
+     * @return can return null (just in case that createIfMissing is false)
+     */
+    public static AccessToken.Access getResolvedClientRoles(KeycloakSession session, ClientSessionContext clientSessionCtx, String clientId, boolean createIfMissing) {
+        AccessToken rolesToken = getAllCompositeRoles(session, clientSessionCtx);
+        AccessToken.Access access = rolesToken.getResourceAccess(clientId);
+
+        if (access == null && createIfMissing) {
+            access = rolesToken.addAccess(clientId);
+        }
+
+        return access;
+    }
+
+
+    /**
+     * Object (but can be empty map) containing all the user's client roles of all clients. Including user's groups roles. Composite roles are expanded.
+     * Just the roles, which current client has role-scope-mapping for (or it's clientScopes) are included.
+     * Current client means the client corresponding to specified clientSessionCtx.
+     *
+     * @param session
+     * @param clientSessionCtx
+     * @return not-null object (can return empty map)
+     */
+    public static Map<String, AccessToken.Access> getAllResolvedClientRoles(KeycloakSession session, ClientSessionContext clientSessionCtx) {
+        return getAllCompositeRoles(session, clientSessionCtx).getResourceAccess();
+    }
+
+
+    private static AccessToken getAllCompositeRoles(KeycloakSession session, ClientSessionContext clientSessionCtx) {
+        AccessToken resolvedRoles = session.getAttribute(RESOLVED_ROLES_ATTR, AccessToken.class);
+        if (resolvedRoles == null) {
+            resolvedRoles = loadCompositeRoles(session, clientSessionCtx);
+            session.setAttribute(RESOLVED_ROLES_ATTR, resolvedRoles);
+        }
+
+        return resolvedRoles;
+    }
+
+
+    private static AccessToken loadCompositeRoles(KeycloakSession session, ClientSessionContext clientSessionCtx) {
+        Set<RoleModel> requestedRoles = clientSessionCtx.getRoles();
+        AccessToken token = new AccessToken();
+        for (RoleModel role : requestedRoles) {
+            addComposites(token, role);
+        }
+        return token;
+    }
+
+
+    private static void addComposites(AccessToken token, RoleModel role) {
+        AccessToken.Access access = null;
+        if (role.getContainer() instanceof RealmModel) {
+            access = token.getRealmAccess();
+            if (token.getRealmAccess() == null) {
+                access = new AccessToken.Access();
+                token.setRealmAccess(access);
+            } else if (token.getRealmAccess().getRoles() != null && token.getRealmAccess().isUserInRole(role.getName()))
+                return;
+
+        } else {
+            ClientModel app = (ClientModel) role.getContainer();
+            access = token.getResourceAccess(app.getClientId());
+            if (access == null) {
+                access = token.addAccess(app.getClientId());
+                if (app.isSurrogateAuthRequired()) access.verifyCaller(true);
+            } else if (access.isUserInRole(role.getName())) return;
+
+        }
+        access.addRole(role.getName());
+        if (!role.isComposite()) return;
+
+        for (RoleModel composite : role.getComposites()) {
+            addComposites(token, composite);
+        }
+
+    }
+
+}
                diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
index a2ededd..cf68931 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
@@ -25,6 +25,8 @@ org.keycloak.protocol.oidc.mappers.RoleNameMapper
 org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper
 org.keycloak.protocol.oidc.mappers.GroupMembershipMapper
 org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper
+org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper
+org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper
 org.keycloak.protocol.saml.mappers.RoleListMapper
 org.keycloak.protocol.saml.mappers.RoleNameMapper
 org.keycloak.protocol.saml.mappers.HardcodedRole
                diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java
index e53124a..a1e9edf 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java
@@ -24,16 +24,19 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
 import org.keycloak.storage.StorageId;
 import org.keycloak.storage.client.AbstractReadOnlyClientStorageAdapter;
 import org.keycloak.storage.client.ClientLookupProvider;
 import org.keycloak.storage.client.ClientStorageProvider;
 import org.keycloak.storage.client.ClientStorageProviderModel;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -219,7 +222,12 @@ public class HardcodedClientStorageProvider implements ClientStorageProvider, Cl
         @Override
         public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol) {
             if (defaultScope) {
-                return Collections.emptyMap();
+                ClientScopeModel rolesScope = KeycloakModelUtils.getClientScopeByName(realm, OIDCLoginProtocolFactory.ROLES_SCOPE);
+                ClientScopeModel webOriginsScope = KeycloakModelUtils.getClientScopeByName(realm, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE);
+                return Arrays.asList(rolesScope, webOriginsScope)
+                        .stream()
+                        .collect(Collectors.toMap(ClientScopeModel::getName, clientScope -> clientScope));
+
             } else {
                 ClientScopeModel offlineScope = KeycloakModelUtils.getClientScopeByName(realm, "offline_access");
                 return Collections.singletonMap("offline_access", offlineScope);
                diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
index a64df88..9bdf28a 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
@@ -16,9 +16,11 @@
  */
 package org.keycloak.testsuite.pages;
 
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
@@ -35,6 +37,7 @@ public class OAuthGrantPage extends LanguageComboboxAwarePage {
     public static final String ADDRESS_CONSENT_TEXT = "Address";
     public static final String PHONE_CONSENT_TEXT = "Phone number";
     public static final String OFFLINE_ACCESS_CONSENT_TEXT = "Offline Access";
+    public static final String ROLES_CONSENT_TEXT = "User roles";
 
     @FindBy(css = "input[name=\"accept\"]")
     private WebElement acceptButton;
@@ -70,12 +73,11 @@ public class OAuthGrantPage extends LanguageComboboxAwarePage {
     }
 
 
-    public void assertGrants(String... grants) {
+    public void assertGrants(String... expectedGrants) {
         List<String> displayed = getDisplayedGrants();
-        Assert.assertEquals(displayed.size(), grants.length);
-        for (String grant : grants) {
-            Assert.assertTrue("Requested grant " + grant + " not present. Displayed grants: " + displayed, displayed.contains(grant));
-        }
+        List<String> expected = Arrays.asList(expectedGrants);
+        Assert.assertTrue("Not matched grants. Displayed grants: " + displayed + ", expected grants: " + expected,
+                displayed.containsAll(expected) && expected.containsAll(displayed));
     }
 
 }
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
index 5496790..17cd9b6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
@@ -642,6 +642,8 @@ public class ExportImportUtil {
         org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OAuth2Constants.SCOPE_ADDRESS));
         org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OAuth2Constants.SCOPE_PHONE));
         org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OAuth2Constants.OFFLINE_ACCESS));
+        org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OIDCLoginProtocolFactory.ROLES_SCOPE));
+        org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE));
         org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(SamlProtocolFactory.SCOPE_ROLE_LIST));
 
         // Check content of some client scopes
@@ -659,6 +661,8 @@ public class ExportImportUtil {
                 .stream().collect(Collectors.toMap(clientScope -> clientScope.getName(), clientScope -> clientScope));
         org.keycloak.testsuite.Assert.assertTrue(defaultClientScopes.containsKey(OAuth2Constants.SCOPE_PROFILE));
         org.keycloak.testsuite.Assert.assertTrue(defaultClientScopes.containsKey(OAuth2Constants.SCOPE_EMAIL));
+        org.keycloak.testsuite.Assert.assertTrue(defaultClientScopes.containsKey(OIDCLoginProtocolFactory.ROLES_SCOPE));
+        org.keycloak.testsuite.Assert.assertTrue(defaultClientScopes.containsKey(OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE));
 
         Map<String, ClientScopeRepresentation> optionalClientScopes = realm.getDefaultOptionalClientScopes()
                 .stream().collect(Collectors.toMap(clientScope -> clientScope.getName(), clientScope -> clientScope));
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java
index 4017b2d..ecd5b63 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java
@@ -30,6 +30,7 @@ import org.keycloak.models.Constants;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
 import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
@@ -58,10 +59,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
+
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -69,12 +70,10 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import org.keycloak.common.Profile;
 import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
 import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT_LINKS;
 import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
 import static org.keycloak.testsuite.Assert.assertNames;
-import org.keycloak.testsuite.ProfileAssume;
 import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
 
 /**
@@ -227,6 +226,9 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
         if (supportsAuthzService && checkMigrationData) {
             testGroupPolicyTypeFineGrainedAdminPermission();
         }
+
+        // NOTE: Fact that 'roles' and 'web-origins' scope were added was tested in testMigrationTo4_0_0 already
+        testRolesAndWebOriginsScopesAddedToClient();
     }
 
     private void testGroupPolicyTypeFineGrainedAdminPermission() {
@@ -512,6 +514,24 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
 
     }
 
+    private void testRolesAndWebOriginsScopesAddedToClient() {
+        log.infof("Testing roles and web-origins default scopes present in realm %s for client migration-test-client", migrationRealm.toRepresentation().getRealm());
+
+        List<ClientScopeRepresentation> defaultClientScopes = ApiUtil.findClientByClientId(this.migrationRealm, "migration-test-client").getDefaultClientScopes();
+
+        Set<String> defaultClientScopeNames = defaultClientScopes.stream()
+                .map(ClientScopeRepresentation::getName)
+                .collect(Collectors.toSet());
+
+        if (!defaultClientScopeNames.contains(OIDCLoginProtocolFactory.ROLES_SCOPE)) {
+            Assert.fail("Client scope 'roles' not found as default scope of client migration-test-client");
+        }
+        if (!defaultClientScopeNames.contains(OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE)) {
+            Assert.fail("Client scope 'web-origins' not found as default scope of client migration-test-client");
+        }
+
+    }
+
     private void testRequiredActionsPriority(RealmResource... realms) {
         log.info("testing required action's priority");
         for (RealmResource realm : realms) {
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
index e956c20..e644acc 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
@@ -99,7 +99,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
         oauth.doLoginGrant("test-user@localhost", "password");
 
         grantPage.assertCurrent();
-        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT);
+        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT);
 
         grantPage.accept();
 
@@ -148,7 +148,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
         oauth.doLoginGrant("test-user@localhost", "password");
 
         grantPage.assertCurrent();
-        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT);
+        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT);
 
         grantPage.cancel();
 
@@ -202,7 +202,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
         // Open login form again and assert grant Page is shown
         oauth.openLoginForm();
         grantPage.assertCurrent();
-        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT);
+        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT);
     }
 
     @Test
@@ -346,7 +346,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
         oauth.clientId(THIRD_PARTY_APP);
         oauth.doLoginGrant("test-user@localhost", "password");
         grantPage.assertCurrent();
-        grantPage.assertGrants(OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.PROFILE_CONSENT_TEXT, "foo-addr");
+        grantPage.assertGrants(OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT, "foo-addr");
         grantPage.accept();
 
         events.expectLogin()
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
index 4e19606..eafcfcd 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
@@ -22,15 +22,21 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientScopeResource;
 import org.keycloak.admin.client.resource.ProtocolMappersResource;
+import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.common.util.UriUtils;
+import org.keycloak.models.AccountRoles;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
 import org.keycloak.protocol.oidc.mappers.AddressMapper;
+import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AddressClaimSet;
 import org.keycloak.representations.IDToken;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ClientScopeRepresentation;
 import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
@@ -207,11 +213,19 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
             assertTrue(departments.contains("finance") && departments.contains("development"));
             assertTrue(accessToken.getRealmAccess().getRoles().contains("hardcoded"));
             assertTrue(accessToken.getRealmAccess().getRoles().contains("realm-user"));
-            Assert.assertFalse(accessToken.getResourceAccess("test-app").getRoles().contains("customer-user"));
+            Assert.assertNull(accessToken.getResourceAccess("test-app"));
             assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded"));
 
             assertEquals("hello_test-user@localhost", accessToken.getOtherClaims().get("computed-via-script"));
             assertEquals(Arrays.asList("A","B"), accessToken.getOtherClaims().get("multiValued-via-script"));
+
+            // Assert audiences added through AudienceResolve mapper
+            Assert.assertThat(accessToken.getAudience(), arrayContainingInAnyOrder("test-app", "app", "account"));
+
+            // Assert allowed origins
+            String expectedOrigin = UriUtils.getOrigin(oauth.getRedirectUri());
+            Assert.assertNames(accessToken.getAllowedOrigins(), expectedOrigin);
+
             oauth.openLogout();
         }
 
@@ -344,6 +358,87 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         deleteMappers(protocolMappers);
     }
 
+
+    // Test to update protocolMappers to not have roles on the default position (realm_access and resource_access properties)
+    @Test
+    public void testUserRolesMovedFromAccessTokenProperties() throws Exception {
+        RealmResource realm = adminClient.realm("test");
+        ClientScopeResource rolesScope = ApiUtil.findClientScopeByName(realm, OIDCLoginProtocolFactory.ROLES_SCOPE);
+
+        // Update builtin protocolMappers to put roles to different position (claim "custom.roles") for both realm and client roles
+        ProtocolMapperRepresentation realmRolesMapper = null;
+        ProtocolMapperRepresentation clientRolesMapper = null;
+        for (ProtocolMapperRepresentation rep : rolesScope.getProtocolMappers().getMappers()) {
+            if (OIDCLoginProtocolFactory.REALM_ROLES.equals(rep.getName())) {
+                realmRolesMapper = rep;
+            } else if (OIDCLoginProtocolFactory.CLIENT_ROLES.equals(rep.getName())) {
+                clientRolesMapper = rep;
+            }
+        }
+
+        String realmRolesTokenClaimOrig = realmRolesMapper.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+        String clientRolesTokenClaimOrig = clientRolesMapper.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+
+        realmRolesMapper.getConfig().put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "custom.roles");
+        rolesScope.getProtocolMappers().update(realmRolesMapper.getId(), realmRolesMapper);
+        clientRolesMapper.getConfig().put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "custom.roles");
+        rolesScope.getProtocolMappers().update(clientRolesMapper.getId(), clientRolesMapper);
+
+        // Create some hardcoded role mapper
+        Response resp = rolesScope.getProtocolMappers().createMapper(createHardcodedRole("hard-realm", "hardcoded"));
+        String hardcodedMapperId = ApiUtil.getCreatedId(resp);
+        resp.close();
+
+        try {
+            OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
+            AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+
+            // Assert roles are not on their original positions
+            Assert.assertNull(accessToken.getRealmAccess());
+            Assert.assertTrue(accessToken.getResourceAccess().isEmpty());
+
+            // Assert both realm and client roles on the new position. Hardcoded role should be here as well
+            Map<String, Object> cst1 = (Map<String, Object>) accessToken.getOtherClaims().get("custom");
+            List<String> roles = (List<String>) cst1.get("roles");
+            Assert.assertNames(roles, "offline_access", "user", "customer-user", "hardcoded", AccountRoles.VIEW_PROFILE, AccountRoles.MANAGE_ACCOUNT, AccountRoles.MANAGE_ACCOUNT_LINKS);
+
+            // Assert audience
+            Assert.assertNames(Arrays.asList(accessToken.getAudience()), "account", "test-app");
+        } finally {
+            // Revert
+            rolesScope.getProtocolMappers().delete(hardcodedMapperId);
+
+            realmRolesMapper.getConfig().put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, realmRolesTokenClaimOrig);
+            rolesScope.getProtocolMappers().update(realmRolesMapper.getId(), realmRolesMapper);
+            clientRolesMapper.getConfig().put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, clientRolesTokenClaimOrig);
+            rolesScope.getProtocolMappers().update(clientRolesMapper.getId(), clientRolesMapper);
+        }
+    }
+
+
+    @Test
+    public void testAllowedOriginsRemovedFromAccessToken() throws Exception {
+        RealmResource realm = adminClient.realm("test");
+        ClientScopeRepresentation allowedOriginsScope = ApiUtil.findClientScopeByName(realm, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE).toRepresentation();
+
+        // Remove 'web-origins' scope from the client
+        ClientResource testApp = ApiUtil.findClientByClientId(realm, "test-app");
+        testApp.removeDefaultClientScope(allowedOriginsScope.getId());
+
+        try {
+            OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
+            AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+
+            // Assert web origins are not in the token
+            Assert.assertNull(accessToken.getAllowedOrigins());
+
+        } finally {
+            // Revert
+            testApp.addDefaultClientScope(allowedOriginsScope.getId());
+        }
+    }
+
+
     /**
      * KEYCLOAK-4205
      * @throws Exception
@@ -381,6 +476,55 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         deleteMappers(protocolMappers);
     }
 
+
+    /**
+     * KEYCLOAK-5259
+     * @throws Exception
+     */
+    @Test
+    public void testUserRoleToAttributeMappersWithFullScopeDisabled() throws Exception {
+        // Add mapper for realm roles
+        ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, true);
+        ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper("test-app", null, "Client roles mapper", "roles-custom.test-app", true, true, true);
+
+        ClientResource client = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), "test-app");
+
+        // Disable full-scope-allowed
+        ClientRepresentation rep = client.toRepresentation();
+        rep.setFullScopeAllowed(false);
+        client.update(rep);
+
+        ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers();
+        protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper));
+
+        // Login user
+        OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
+        IDToken idToken = oauth.verifyIDToken(response.getIdToken());
+
+        // Verify attribute is filled
+        Map<String, Object> roleMappings = (Map<String, Object>)idToken.getOtherClaims().get("roles-custom");
+        Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", "test-app"));
+        Assert.assertThat(roleMappings.get("realm"), CoreMatchers.instanceOf(List.class));
+        Assert.assertThat(roleMappings.get("test-app"), CoreMatchers.instanceOf(List.class));
+
+        List<String> realmRoleMappings = (List<String>) roleMappings.get("realm");
+        List<String> testAppMappings = (List<String>) roleMappings.get("test-app");
+        assertRoles(realmRoleMappings,
+                "pref.user"                      // from direct assignment in user definition
+        );
+        assertRoles(testAppMappings,
+                "customer-user"                   // from direct assignment in user definition
+        );
+
+        // Revert
+        deleteMappers(protocolMappers);
+
+        rep = client.toRepresentation();
+        rep.setFullScopeAllowed(true);
+        client.update(rep);
+    }
+
+
     @Test
     public void testUserGroupRoleToAttributeMappers() throws Exception {
         // Add mapper for realm roles
@@ -442,7 +586,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
 
         // Verify attribute is filled
         Map<String, Object> roleMappings = (Map<String, Object>)idToken.getOtherClaims().get("roles-custom");
-        Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", clientId));
+        Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm"));
         String realmRoleMappings = (String) roleMappings.get("realm");
         String testAppAuthzMappings = (String) roleMappings.get(clientId);
         assertRolesString(realmRoleMappings,
@@ -452,7 +596,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
           "pref.realm-composite-role",      // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
           "pref.sample-realm-role"          // from realm role realm-composite-role
         );
-        assertRolesString(testAppAuthzMappings);  // There is no client role defined for test-app-authz
+        assertNull(testAppAuthzMappings);  // There is no client role defined for test-app-authz
 
         // Revert
         deleteMappers(protocolMappers);
@@ -480,10 +624,12 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         String testAppScopeMappings = (String) roleMappings.get(clientId);
         assertRolesString(realmRoleMappings,
           "pref.admin",                     // from direct assignment to /roleRichGroup/level2group
-          "pref.user"                       // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
+          "pref.user",                       // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
+          "pref.customer-user-premium"
         );
         assertRolesString(testAppScopeMappings,
-          "test-app-allowed-by-scope"       // from direct assignment to roleRichUser, present as scope allows it
+          "test-app-allowed-by-scope",       // from direct assignment to roleRichUser, present as scope allows it
+                "test-app-disallowed-by-scope"
         );
 
         // Revert
@@ -512,11 +658,12 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
         String testAppScopeMappings = (String) roleMappings.get(clientId);
         assertRolesString(realmRoleMappings,
           "pref.admin",                     // from direct assignment to /roleRichGroup/level2group
-          "pref.user"                       // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
+          "pref.user",  // from parent group of /roleRichGroup/level2group, i.e. from /roleRichGroup
+          "pref.customer-user-premium"
         );
         assertRolesString(testAppScopeMappings,
           "test-app-allowed-by-scope",      // from direct assignment to roleRichUser, present as scope allows it
-          "customer-admin-composite-role"   // from direct assignment to /roleRichGroup/level2group, present as scope allows it
+          "test-app-disallowed-by-scope"   // from direct assignment to /roleRichGroup/level2group, present as scope allows it
         );
 
         // Revert
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java
index 2c77962..ee3b3b4 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java
@@ -18,6 +18,7 @@ package org.keycloak.testsuite.oauth;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
@@ -118,7 +119,11 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
         assertEquals(jsonNode.get("iat").asInt(), rep.getIssuedAt());
         assertEquals(jsonNode.get("nbf").asInt(), rep.getNotBefore());
         assertEquals(jsonNode.get("sub").asText(), rep.getSubject());
-        assertEquals(jsonNode.get("aud").asText(), rep.getAudience()[0]);
+
+        List<String> audiences = new ArrayList<>();
+        jsonNode.get("aud").forEach(childNode -> audiences.add(childNode.asText()));
+        Assert.assertNames(audiences, rep.getAudience());
+
         assertEquals(jsonNode.get("iss").asText(), rep.getIssuer());
         assertEquals(jsonNode.get("jti").asText(), rep.getId());
     }
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AudienceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AudienceTest.java
index ba9186b..a810f5d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AudienceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AudienceTest.java
@@ -24,6 +24,7 @@ import javax.ws.rs.core.Response;
 
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.ClientScopeResource;
@@ -74,16 +75,10 @@ public class AudienceTest extends AbstractOIDCScopeTest {
         role1.setName("role1");
         testRealm.getRoles().getClient().put("service-client", Arrays.asList(role1));
 
-        // Create client scope 'audience-scope' and add as optional scope to the 'test-app' client
-        ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
-        clientScope.setName("audience-scope");
-        clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
-        testRealm.setClientScopes(Arrays.asList(clientScope));
-
+        // Disable FullScopeAllowed for the 'test-app' client
         ClientRepresentation testApp = testRealm.getClients().stream().filter((ClientRepresentation client) -> {
             return "test-app".equals(client.getClientId());
         }).findFirst().get();
-        testApp.setOptionalClientScopes(Arrays.asList("audience-scope"));
 
         testApp.setFullScopeAllowed(false);
 
@@ -103,6 +98,26 @@ public class AudienceTest extends AbstractOIDCScopeTest {
         testRealm.getUsers().add(user);
     }
 
+    @Before
+    public void beforeTest() {
+        // Check if already exists
+        ClientScopeResource clientScopeRes = ApiUtil.findClientScopeByName(testRealm(), "audience-scope");
+        if (clientScopeRes != null) {
+            return;
+        }
+
+        // Create client scope 'audience-scope' and add as optional scope to the 'test-app' client
+        ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
+        clientScope.setName("audience-scope");
+        clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        Response resp = testRealm().clientScopes().create(clientScope);
+        String clientScopeId = ApiUtil.getCreatedId(resp);
+        resp.close();
+
+        ClientResource client = ApiUtil.findClientByClientId(testRealm(), "test-app");
+        client.addOptionalClientScope(clientScopeId);
+    }
+
 
     @Test
     public void testAudienceProtocolMapperWithClientAudience() throws Exception {
@@ -120,7 +135,7 @@ public class AudienceTest extends AbstractOIDCScopeTest {
         EventRepresentation loginEvent = events.expectLogin()
                 .user(userId)
                 .assertEvent();
-        Tokens tokens = sendTokenRequest(loginEvent, userId,"openid audience-scope", "test-app");
+        Tokens tokens = sendTokenRequest(loginEvent, userId,"openid profile email audience-scope", "test-app");
         // TODO: Frontend client itself should not be in the audiences of access token. Will be fixed in the future
         assertAudiences(tokens.accessToken, "test-app", "service-client");
         assertAudiences(tokens.idToken, "test-app");
@@ -152,7 +167,7 @@ public class AudienceTest extends AbstractOIDCScopeTest {
         EventRepresentation loginEvent = events.expectLogin()
                 .user(userId)
                 .assertEvent();
-        Tokens tokens = sendTokenRequest(loginEvent, userId,"openid audience-scope", "test-app");
+        Tokens tokens = sendTokenRequest(loginEvent, userId,"openid profile email audience-scope", "test-app");
         // TODO: Frontend client itself should not be in the audiences of access token. Will be fixed in the future
         assertAudiences(tokens.accessToken, "test-app", "http://host/service/ctx1", "http://host/service/ctx2");
         assertAudiences(tokens.idToken, "test-app", "http://host/service/ctx2");
@@ -176,7 +191,7 @@ public class AudienceTest extends AbstractOIDCScopeTest {
         EventRepresentation loginEvent = events.expectLogin()
                 .user(userId)
                 .assertEvent();
-        Tokens tokens = sendTokenRequest(loginEvent, userId,"openid", "test-app");
+        Tokens tokens = sendTokenRequest(loginEvent, userId,"openid profile email", "test-app");
         assertAudiences(tokens.accessToken, "test-app");
         assertAudiences(tokens.idToken, "test-app");
         Assert.assertFalse(tokens.accessToken.getResourceAccess().containsKey("service-client"));
@@ -199,7 +214,7 @@ public class AudienceTest extends AbstractOIDCScopeTest {
         loginEvent = events.expectLogin()
                 .user(userId)
                 .assertEvent();
-        tokens = sendTokenRequest(loginEvent, userId,"openid service-client", "test-app");
+        tokens = sendTokenRequest(loginEvent, userId,"openid profile email service-client", "test-app");
         assertAudiences(tokens.accessToken, "test-app", "service-client");
         assertAudiences(tokens.idToken, "test-app");
         Assert.assertTrue(tokens.accessToken.getResourceAccess().containsKey("service-client"));
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCScopeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCScopeTest.java
index b4a64b8..1c1a590 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCScopeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCScopeTest.java
@@ -274,7 +274,7 @@ public class OIDCScopeTest extends AbstractOIDCScopeTest {
         oauth.doLoginGrant("john", "password");
 
         grantPage.assertCurrent();
-        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT);
+        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT);
         grantPage.accept();
 
         EventRepresentation loginEvent = events.expectLogin()
@@ -339,7 +339,7 @@ public class OIDCScopeTest extends AbstractOIDCScopeTest {
         oauth.doLoginGrant("john", "password");
 
         grantPage.assertCurrent();
-        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, "ThirdParty permissions");
+        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT, "ThirdParty permissions");
         grantPage.accept();
 
         EventRepresentation loginEvent = events.expectLogin()
@@ -369,7 +369,7 @@ public class OIDCScopeTest extends AbstractOIDCScopeTest {
         oauth.doLoginGrant("john", "password");
 
         grantPage.assertCurrent();
-        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT);
+        grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT);
         grantPage.accept();
 
         EventRepresentation loginEvent = events.expectLogin()
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
index c8084e0..26a740e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
@@ -27,6 +27,7 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.crypto.Algorithm;
 import org.keycloak.jose.jwk.JSONWebKeySet;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory;
 import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
@@ -139,7 +140,8 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
 
             // Scopes supported
             Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS,
-                    OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS);
+                    OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS,
+                    OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE);
 
             // Request and Request_Uri
             Assert.assertTrue(oidcConfig.getRequestParameterSupported());
                diff --git a/testsuite/integration-deprecated/src/test/resources/broker-test/realm-with-oidc-property-mappers.json b/testsuite/integration-deprecated/src/test/resources/broker-test/realm-with-oidc-property-mappers.json
index 2ed192c..dbe824d 100755
--- a/testsuite/integration-deprecated/src/test/resources/broker-test/realm-with-oidc-property-mappers.json
+++ b/testsuite/integration-deprecated/src/test/resources/broker-test/realm-with-oidc-property-mappers.json
@@ -13,7 +13,7 @@
       "enabled": true,
       "fullScopeAllowed": true,
       "secret": "secret",
-      "defaultClientScopes": [],
+      "defaultClientScopes": [ "roles", "web-origins" ],
       "redirectUris": [
         "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-oidc-idp-property-mappers/endpoint/*"
       ],
                diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
index d2acee2..275c248 100755
--- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -60,6 +60,7 @@ addressScopeConsentText=Address
 phoneScopeConsentText=Phone number
 offlineAccessScopeConsentText=Offline Access
 samlRoleListScopeConsentText=My Roles
+rolesScopeConsentText=User roles
 
 role_admin=Admin
 role_realm-admin=Realm Admin
                diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 17c5a62..7876630 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -219,9 +219,10 @@ includeInAccessToken.tooltip=Should the claim be added to the access token?
 includeInUserInfo.label=Add to userinfo
 includeInUserInfo.tooltip=Should the claim be added to the userinfo?
 usermodel.clientRoleMapping.clientId.label=Client ID
-usermodel.clientRoleMapping.clientId.tooltip=Client ID for role mappings
+usermodel.clientRoleMapping.clientId.tooltip=Client ID for role mappings. Just client roles of this client will be added to the token. If this is unset, then client roles of all clients will be added to the token.
 usermodel.clientRoleMapping.rolePrefix.label=Client Role prefix
 usermodel.clientRoleMapping.rolePrefix.tooltip=A prefix for each client role (optional).
+usermodel.clientRoleMapping.tokenClaimName.tooltip=Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created. To prevent nesting and use dot literally, escape the dot with backslash (\\.). The special token ${client_id} can be used and this will be replaced by the actual client ID. Example usage is 'resource_access.${client_id}.roles'. This is useful especialy when you are adding roles from all the clients (Hence 'Client ID' switch is unset) and you want client roles of each client in separate place.
 usermodel.realmRoleMapping.rolePrefix.label=Realm Role prefix
 usermodel.realmRoleMapping.rolePrefix.tooltip=A prefix for each Realm Role (optional).
 sectorIdentifierUri.label=Sector Identifier URI
@@ -432,6 +433,7 @@ client.associated-roles.tooltip=Client roles associated with this composite role
 add-builtin=Add Builtin
 category=Category
 type=Type
+priority-order=Priority Order
 no-mappers-available=No mappers available
 add-builtin-protocol-mappers=Add Builtin Protocol Mappers
 add-builtin-protocol-mapper=Add Builtin Protocol Mapper
@@ -850,6 +852,8 @@ client-scope.consent-screen-text=Consent Screen Text
 client-scope.consent-screen-text.tooltip=Text, which will be shown on consent screen when this client scope is added to some client with consent required. Defaults to name of client scope if it's not filled
 client-scope.gui-order=GUI order
 client-scope.gui-order.tooltip=Specify order of the provider in GUI (e.g. in Consent page) as integer
+client-scope.include-in-token-scope=Include In Token Scope
+client-scope.include-in-token-scope.tooltip=If on, then the name of this client scope will be added to the access token property 'scope' as well as to the Token Introspection Endpoint response. If off, then this client scope will be ommitted from the token and from the Token Introspection Endpoint response.
 
 add-user-federation-provider=Add user federation provider
 add-user-storage-provider=Add user storage provider
                diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index b8ffc66..a1c1172 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -1898,6 +1898,10 @@ module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client
         });
     };
 
+    $scope.sortMappersByPriority = function(mapper) {
+        return $scope.mapperTypes[mapper.protocolMapper].priority;
+    }
+
     var updateMappers = function() {
         $scope.mappers = ClientProtocolMappersByProtocol.query({realm : realm.realm, client : client.id, protocol : client.protocol});
     };
@@ -2391,6 +2395,10 @@ module.controller('ClientClientScopesEvaluateCtrl', function($scope, Realm, User
         return $scope.selectedTab === 3;
     }
 
+    $scope.sortMappersByPriority = function(mapper) {
+        return $scope.mapperTypes[mapper.protocolMapper].priority;
+    }
+
 
     // Roles
 
@@ -2693,6 +2701,16 @@ module.controller('ClientScopeDetailCtrl', function($scope, realm, clientScope, 
         } else {
             $scope.displayOnConsentScreen = true;
         }
+
+        if ($scope.clientScope.attributes["include.in.token.scope"]) {
+            if ($scope.clientScope.attributes["include.in.token.scope"] == "true") {
+                $scope.includeInTokenScope = true;
+            } else {
+                $scope.includeInTokenScope = false;
+            }
+        } else {
+            $scope.includeInTokenScope = true;
+        }
     }
 
     if (!$scope.create) {
@@ -2742,6 +2760,12 @@ module.controller('ClientScopeDetailCtrl', function($scope, realm, clientScope, 
             $scope.clientScope.attributes["display.on.consent.screen"] = "false";
         }
 
+        if ($scope.includeInTokenScope == true) {
+            $scope.clientScope.attributes["include.in.token.scope"] = "true";
+        } else {
+            $scope.clientScope.attributes["include.in.token.scope"] = "false";
+        }
+
         if ($scope.create) {
             ClientScope.save({
                 realm: realm.realm,
@@ -2801,6 +2825,10 @@ module.controller('ClientScopeProtocolMapperListCtrl', function($scope, realm, c
         });
     };
 
+    $scope.sortMappersByPriority = function(mapper) {
+        return $scope.mapperTypes[mapper.protocolMapper].priority;
+    }
+
     var updateMappers = function() {
         $scope.mappers = ClientScopeProtocolMappersByProtocol.query({realm : realm.realm, clientScope : clientScope.id, protocol : clientScope.protocol});
     };
                diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html
index f600b08..83123c2 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html
@@ -32,14 +32,16 @@
             <th>{{:: 'name' | translate}}</th>
             <th>{{:: 'category' | translate}}</th>
             <th>{{:: 'type' | translate}}</th>
+            <th>{{:: 'priority-order' | translate}}</th>
             <th colspan="2">{{:: 'actions' | translate}}</th>
         </tr>
         </thead>
         <tbody>
-        <tr ng-repeat="mapper in mappers | filter:search">
+        <tr ng-repeat="mapper in mappers | filter:search | orderBy:sortMappersByPriority">
             <td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
             <td>{{mapperTypes[mapper.protocolMapper].category}}</td>
             <td>{{mapperTypes[mapper.protocolMapper].name}}</td>
+            <td>{{mapperTypes[mapper.protocolMapper].priority}}</td>
             <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</td>
             <td class="kc-action-cell" data-ng-show="client.access.manage" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</td>
         </tr>
                diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-detail.html
index 8daee59..2299993 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-detail.html
@@ -53,6 +53,13 @@
                 </div>
                 <kc-tooltip>{{:: 'client-scope.consent-screen-text.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group clearfix block" data-ng-show="protocol == 'openid-connect'">
+                <label class="col-md-2 control-label" for="includeInTokenScope">{{:: 'client-scope.include-in-token-scope' | translate}}</label>
+                <div class="col-sm-6">
+                    <input ng-model="includeInTokenScope" ng-click="switchChange()" name="displayOnConsentScreen" id="includeInTokenScope" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+                </div>
+                <kc-tooltip>{{:: 'client-scope.include-in-token-scope.tooltip' | translate}}</kc-tooltip>
+            </div>
             <div class="form-group">
                 <label class="col-md-2 control-label" for="guiOrder">{{:: 'client-scope.gui-order' | translate}} </label>
                 <div class="col-sm-6">
                diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappers.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappers.html
index 661be83..3b95910 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappers.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappers.html
@@ -32,14 +32,16 @@
             <th>{{:: 'name' | translate}}</th>
             <th>{{:: 'category' | translate}}</th>
             <th>{{:: 'type' | translate}}</th>
+            <th>{{:: 'priority-order' | translate}}</th>
             <th colspan="2">{{:: 'actions' | translate}}</th>
         </tr>
         </thead>
         <tbody>
-        <tr ng-repeat="mapper in mappers | filter:search">
+        <tr ng-repeat="mapper in mappers | filter:search | orderBy:sortMappersByPriority">
             <td><a href="#/realms/{{realm.realm}}/client-scopes/{{clientScope.id}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
             <td>{{mapperTypes[mapper.protocolMapper].category}}</td>
             <td>{{mapperTypes[mapper.protocolMapper].name}}</td>
+            <td>{{mapperTypes[mapper.protocolMapper].priority}}</td>
             <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/client-scopes/{{clientScope.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</td>
             <td class="kc-action-cell" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</td>
         </tr>
                diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scopes-evaluate.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scopes-evaluate.html
index 34b8971..8b221b8 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scopes-evaluate.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scopes-evaluate.html
@@ -145,14 +145,16 @@
             <th>{{:: 'parent-client-scope' | translate}}</th>
             <th>{{:: 'category' | translate}}</th>
             <th>{{:: 'type' | translate}}</th>
+            <th>{{:: 'priority-order' | translate}}</th>
         </tr>
         </thead>
         <tbody>
-        <tr ng-repeat="mapper in protocolMappers | filter:search">
+        <tr ng-repeat="mapper in protocolMappers | filter:search | orderBy:sortMappersByPriority">
             <td><a href="#/realms/{{realm.realm}}/{{mapper.containerType}}s/{{mapper.containerId}}/mappers/{{mapper.mapperId}}">{{mapper.mapperName}}</a></td>
             <td><a href="#/realms/{{realm.realm}}/{{mapper.containerType}}s/{{mapper.containerId}}">{{mapper.containerName}}</a></td>
             <td>{{mapperTypes[mapper.protocolMapper].category}}</td>
             <td>{{mapperTypes[mapper.protocolMapper].name}}</td>
+            <td>{{mapperTypes[mapper.protocolMapper].priority}}</td>
         </tr>
         <tr data-ng-show="mappers.length == 0">
             <td>{{:: 'no-mappers-available' | translate}}</td>
                diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 2ca0d70..c1472b7 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -79,6 +79,7 @@ addressScopeConsentText=Address
 phoneScopeConsentText=Phone number
 offlineAccessScopeConsentText=Offline Access
 samlRoleListScopeConsentText=My Roles
+rolesScopeConsentText=User roles
 
 loginTotpIntro=You are required to set up a One Time Password generator to access this account
 loginTotpStep1=Install one of the following applications on your mobile