Repository: gravitee-io/graviteeio-access-management (java)

Issue:
[oidc] at_hash ID Token claim is required for Implicit and Hybrid flow

Pull request description:
fix(oidc): at_hash ID token claim is required for Implicit and Hybrid flow
fixes #396

Patch:
diff --git a/docker/README.adoc b/docker/README.adoc
index 2926cbf926..76d22c8755 100644
--- a/docker/README.adoc
+++ b/docker/README.adoc
@@ -4,7 +4,7 @@ ifdef::env-github[]
 image:https://badges.gitter.im/Join Chat.svg["Gitter", link="https://gitter.im/gravitee-io/gravitee-io?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
 endif::[]
 
-Hosts all Dockerfiles to build Gravitee.io access MAnagement images.
+Hosts all Dockerfiles to build Gravitee.io access Management images.
 
 == How to launch AM environment
 You must have 
diff --git a/docker/compose/.env b/docker/compose/.env
new file mode 100644
index 0000000000..fbcfc696fa
--- /dev/null
+++ b/docker/compose/.env
@@ -0,0 +1,2 @@
+GIO_AM_VERSION=2
+NGINX_PORT=80
\ No newline at end of file
diff --git a/docker/compose/config/constants-standalone.json b/docker/compose/config/constants-standalone.json
deleted file mode 100644
index 331e857f83..0000000000
--- a/docker/compose/config/constants-standalone.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "portalTitle": "Access Management",
-  "baseURL": "http://localhost:#PORT/am/management",
-  "authentication": {
-    "domainId": "admin",
-    "authorize": "http://localhost:#PORT/am/admin/authorize",
-    "redirectUri": "http://localhost:#PORT/am/ui/login/callback",
-    "logoutUri": "http://localhost:#PORT/am/admin/logout?target_url=http://localhost:#PORT/am/ui/logout/callback"
-  }
-}
\ No newline at end of file
diff --git a/docker/compose/config/constants.json b/docker/compose/config/constants.json
deleted file mode 100644
index 3275bd4555..0000000000
--- a/docker/compose/config/constants.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "portalTitle": "Access Management",
-  "baseURL": "http://localhost:18000/am/management",
-  "authentication": {
-    "domainId": "admin",
-    "authorize": "http://localhost:18000/am/admin/authorize",
-    "redirectUri": "http://localhost:18000/am/ui/login/callback",
-    "logoutUri": "http://localhost:18000/am/admin/logout?target_url=http://localhost:18000/am/ui/logout/callback"
-  }
-}
\ No newline at end of file
diff --git a/docker/compose/config/nginx-standalone.conf b/docker/compose/config/nginx-standalone.conf
deleted file mode 100644
index 7b55e961f5..0000000000
--- a/docker/compose/config/nginx-standalone.conf
+++ /dev/null
@@ -1,72 +0,0 @@
-worker_processes 4;
-
-events { worker_connections 1024; }
-
-http {
-        include /etc/nginx/mime.types;
-
-        resolver 127.0.0.11 ipv6=off;
-
-        upstream management {
-            server management:8093;
-        }
-
-        upstream gateway {
-            server gateway:8092;
-        }
-
-        upstream webui {
-            server webui:80;
-        }
-
-        server {
-            listen #PORT;
-
-              location /am/ui/ {
-                    proxy_pass http://webui/;
-                    proxy_set_header   Host $host;
-                    proxy_set_header   X-Real-IP $remote_addr;
-                    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
-                    proxy_set_header   X-Forwarded-Host $server_name;
-                    sub_filter '<base href="/"' '<base href="/am/ui/"';
-                    sub_filter_once on;
-              }
-
-              location /am/management/ {
-                    proxy_pass http://management/management/;
-                    proxy_redirect http://$host:$server_port/am/ui/ /am/ui/;
-                    proxy_redirect http://$host:$server_port/management/ /am/management/;
-                    proxy_cookie_path /management /am/management;
-                    proxy_set_header   Host $host;
-                    proxy_set_header   X-Real-IP $remote_addr;
-                    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
-                    proxy_set_header   X-Forwarded-Host $server_name;
-              }
-
-              location /am/admin/ {
-                    proxy_pass http://management/admin/;
-                    proxy_redirect http://$http_host/admin/ http://$http_host/am/admin/;
-                    proxy_cookie_path /admin /am/admin;
-                    proxy_set_header   Host $http_host;
-                    proxy_set_header   X-Real-IP $remote_addr;
-                    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
-                    proxy_set_header   X-Forwarded-Host $server_name;
-              }
-
-              location /am/ {
-                    proxy_pass http://gateway/;
-              	    proxy_redirect http://$host/am/ui/ /am/ui/;
-              	    proxy_redirect http://$host/ /am/;
-                    proxy_cookie_path / /am;
-                    proxy_set_header   Host $host;
-                    proxy_set_header   X-Real-IP $remote_addr;
-                    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
-                    proxy_set_header   X-Forwarded-Host $server_name;
-              }
-
-              error_page   500 502 503 504  /50x.html;
-              location = /50x.html {
-                    root /usr/share/nginx/html;
-              }
-        }
-}
\ No newline at end of file
diff --git a/docker/compose/docker-compose-standalone.yml b/docker/compose/docker-compose-standalone.yml
deleted file mode 100644
index 63a16a9b2c..0000000000
--- a/docker/compose/docker-compose-standalone.yml
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# Copyright (C) 2015 The Gravitee team (http://gravitee.io)
-#
-# 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.
-#
-
-version: '2'
-
-volumes:
-  datamongo: {}
-
-services:
-  nginx:
-    image: nginx:1.15-alpine
-    volumes:
-      - ./config/nginx.conf:/etc/nginx/nginx.conf
-    ports:
-      - "${PORT}:80"
-
-  mongodb:
-    image: mongo:3.4
-    volumes:
-      - datamongo:/data/db
-      - ./logs/am-mongodb:/var/log/mongodb
-
-  gateway:
-    image: graviteeio/am-gateway:2
-    links:
-      - mongodb
-    depends_on:
-      - mongodb
-    volumes:
-      - ./logs/am-gateway:/etc/gravitee.io/log
-    environment:
-      - gravitee_management_mongodb_uri=mongodb://mongodb:27017/graviteeam?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000
-      - gravitee_oauth2_mongodb_uri=mongodb://mongodb:27017/graviteeam?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000
-
-  management:
-    image: graviteeio/am-management-api:2
-    links:
-      - mongodb
-    depends_on:
-      - mongodb
-    volumes:
-      - ./logs/am-management-api:/etc/gravitee.io/log
-    environment:
-      - gravitee_management_mongodb_uri=mongodb://mongodb:27017/graviteeam?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000
-      - gravitee_oauth2_mongodb_uri=mongodb://mongodb:27017/graviteeam?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000
-
-  webui:
-    image: graviteeio/am-management-ui:2
-    depends_on:
-      - management
-    volumes:
-      - ./logs/am-webui:/var/log/httpd
-      - ./config/constants.json:/var/www/html/constants.json
\ No newline at end of file
diff --git a/docker/compose/docker-compose.yml b/docker/compose/docker-compose.yml
index 63a16a9b2c..764ed99b40 100644
--- a/docker/compose/docker-compose.yml
+++ b/docker/compose/docker-compose.yml
@@ -22,19 +22,29 @@ volumes:
 services:
   nginx:
     image: nginx:1.15-alpine
+    container_name: gio_am_nginx
+    restart: always
     volumes:
       - ./config/nginx.conf:/etc/nginx/nginx.conf
     ports:
-      - "${PORT}:80"
+      - ${NGINX_PORT}:80
+    depends_on:
+      - management
+      - gateway
+      - webui
 
   mongodb:
     image: mongo:3.4
+    container_name: gio_am_mongodb
+    restart: always
     volumes:
       - datamongo:/data/db
       - ./logs/am-mongodb:/var/log/mongodb
 
   gateway:
-    image: graviteeio/am-gateway:2
+    image: graviteeio/am-gateway:${GIO_AM_VERSION}
+    container_name: gio_am_gateway
+    restart: always
     links:
       - mongodb
     depends_on:
@@ -46,7 +56,9 @@ services:
       - gravitee_oauth2_mongodb_uri=mongodb://mongodb:27017/graviteeam?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000
 
   management:
-    image: graviteeio/am-management-api:2
+    image: graviteeio/am-management-api:${GIO_AM_VERSION}
+    container_name: gio_am_management
+    restart: always
     links:
       - mongodb
     depends_on:
@@ -58,9 +70,13 @@ services:
       - gravitee_oauth2_mongodb_uri=mongodb://mongodb:27017/graviteeam?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000
 
   webui:
-    image: graviteeio/am-management-ui:2
+    image: graviteeio/am-management-ui:${GIO_AM_VERSION}
+    container_name: gio_am_webui
+    restart: always
     depends_on:
       - management
+    environment:
+      - MGMT_API_URL=http://localhost:${NGINX_PORT}/am/
+      - MGMT_UI_URL=http://localhost:${NGINX_PORT}/am/ui/
     volumes:
-      - ./logs/am-webui:/var/log/httpd
-      - ./config/constants.json:/var/www/html/constants.json
\ No newline at end of file
+      - ./logs/am-webui:/var/log/httpd
\ No newline at end of file
diff --git a/docker/launch.sh b/docker/launch.sh
index 6701b068c6..d86cd4bbc5 100755
--- a/docker/launch.sh
+++ b/docker/launch.sh
@@ -89,13 +89,9 @@ main() {
     pushd $WORKDIR > /dev/null
         echo "Download required files ..."
         mkdir -p config
-        curl -L https://raw.githubusercontent.com/gravitee-io/graviteeio-access-management/master/docker/compose/docker-compose-standalone.yml -o "docker-compose.yml"
-        cd config && { curl -L https://raw.githubusercontent.com/gravitee-io/graviteeio-access-management/master/docker/compose/config/constants-standalone.json -o "constants.json" ; cd -; }
-        cd config && { curl -L https://raw.githubusercontent.com/gravitee-io/graviteeio-access-management/master/docker/compose/config/nginx-standalone.conf -o "nginx.conf" ; cd -; }
-	sed -i.bak "s/\${PORT}/$PORT/g" docker-compose.yml
-        sed -i.bak "s/#PORT/$PORT/g" config/constants.json
-        sed -i.bak "s/#PORT/$PORT/g" config/nginx.conf
-        rm docker-compose.yml.bak config/constants.json.bak config/nginx.conf.bak
+        curl -L https://raw.githubusercontent.com/gravitee-io/graviteeio-access-management/master/docker/compose/docker-compose.yml -o "docker-compose.yml"
+        curl -L https://raw.githubusercontent.com/gravitee-io/graviteeio-access-management/master/docker/compose/.env -o ".env"
+        cd config && { curl -L https://raw.githubusercontent.com/gravitee-io/graviteeio-access-management/master/docker/compose/config/nginx.conf -o "nginx.conf" ; cd -; }
         echo
         echo "Launch Gravitee.io Access Management ..."
         $dc_exec
@@ -104,9 +100,9 @@ main() {
 
 # init port from input parameter
 if [ "$1" != "" ]; then
-    export PORT=$1
+    export NGINX_PORT=$1
 else
-    export PORT=18000
+    export NGINX_PORT=80
 fi
 
 main
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/request/OAuth2Request.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/request/OAuth2Request.java
index 08a8af9e68..764ffd19c8 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/request/OAuth2Request.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/request/OAuth2Request.java
@@ -87,4 +87,10 @@ public class OAuth2Request extends BaseRequest {
         }
         return false;
     }
+
+    public boolean isSupportAtHashValue() {
+        return getResponseType() != null
+                && (ResponseType.ID_TOKEN_TOKEN.equals(getResponseType())
+                || ResponseType.CODE_ID_TOKEN_TOKEN.equals(getResponseType()));
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/token/impl/TokenEnhancerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/token/impl/TokenEnhancerImpl.java
index d9a8ea4f1a..1631222ed0 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/token/impl/TokenEnhancerImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/token/impl/TokenEnhancerImpl.java
@@ -19,6 +19,7 @@ import io.gravitee.am.gateway.handler.oauth2.request.OAuth2Request;
 import io.gravitee.am.gateway.handler.oauth2.token.TokenEnhancer;
 import io.gravitee.am.gateway.handler.oauth2.utils.OAuth2Constants;
 import io.gravitee.am.gateway.handler.oidc.idtoken.IDTokenService;
+import io.gravitee.am.gateway.handler.oidc.utils.OIDCClaims;
 import io.gravitee.am.model.Client;
 import io.gravitee.am.model.User;
 import io.gravitee.am.repository.oauth2.model.AccessToken;
@@ -48,6 +49,9 @@ public class TokenEnhancerImpl implements TokenEnhancer {
     }
 
     private Single<AccessToken> enhanceIDToken(AccessToken accessToken, Client client, User user, OAuth2Request oAuth2Request) {
+        if (oAuth2Request.isSupportAtHashValue()) {
+            oAuth2Request.getContext().put(OIDCClaims.at_hash, accessToken.getToken());
+        }
         return idTokenService.create(oAuth2Request, client, user)
                 .flatMap(idToken -> {
                     Map<String, Object> additionalInformation = new HashMap<>(accessToken.getAdditionalInformation());
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/idtoken/IDToken.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/idtoken/IDToken.java
index d474b2a9a9..c043dcc380 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/idtoken/IDToken.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/idtoken/IDToken.java
@@ -113,7 +113,7 @@ public class IDToken extends HashMap<String, Object> {
     }
 
     public void addAdditionalClaim(String claimName, Object claimValue) {
-        put(claimName, claimValue);
+        putIfAbsent(claimName, claimValue);
     }
 
     public void setAdditionalClaims(Map<String, Object> additionalClaims) {
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/idtoken/impl/IDTokenServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/idtoken/impl/IDTokenServiceImpl.java
index 2711c72c33..417af03f68 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/idtoken/impl/IDTokenServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/idtoken/impl/IDTokenServiceImpl.java
@@ -113,7 +113,7 @@ public class IDTokenServiceImpl implements IDTokenService, InitializingBean {
             if (requestedParameters != null && requestedParameters.getFirst(OIDCParameters.CLAIMS) != null) {
                 requestForSpecificClaims = processClaimsRequest(requestedParameters.getFirst(OIDCParameters.CLAIMS), userAdditionalInformation, idToken);
             // 3. If not present, check if the client has enabled the ID token mapping claims.
-            } else if (client.getIdTokenCustomClaims() != null) {
+            } else if (client.getIdTokenCustomClaims() != null && !client.getIdTokenCustomClaims().isEmpty()) {
                 client.getIdTokenCustomClaims().forEach((key, value) -> {
                     if (userAdditionalInformation.get(value) != null) {
                         idToken.addAdditionalClaim(key, userAdditionalInformation.get(value));
@@ -123,7 +123,7 @@ public class IDTokenServiceImpl implements IDTokenService, InitializingBean {
             }
             // 4. Else send all user claims
             if (!requestForSpecificClaims) {
-                idToken.setAdditionalClaims(userAdditionalInformation);
+                userAdditionalInformation.forEach((k, v) -> idToken.addAdditionalClaim(k, v));
             }
         }
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationRequestParseHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationRequestParseHandler.java
index 47a7b0b34f..aa632ae9d2 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationRequestParseHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oauth2/endpoint/authorization/AuthorizationRequestParseHandler.java
@@ -250,8 +250,8 @@ public class AuthorizationRequestParseHandler implements Handler<RoutingContext>
         String nonce = context.request().getParam(OIDCParameters.NONCE);
         String responseType = context.request().getParam(OAuth2Constants.RESPONSE_TYPE);
         // nonce parameter is required for the Hybrid flow
-        if (nonce == null && isHybridFlow(responseType)) {
-            throw new InvalidRequestException("Missing parameter: nonce is required for Hybrid Flow");
+        if (nonce == null && (isHybridFlow(responseType) || isImplicitFlow(responseType))) {
+            throw new InvalidRequestException("Missing parameter: nonce is required for Implicit and Hybrid Flow");
         }
     }
 
@@ -269,6 +269,10 @@ public class AuthorizationRequestParseHandler implements Handler<RoutingContext>
         return (ResponseType.CODE_ID_TOKEN.equals(responseType) || ResponseType.CODE_TOKEN.equals(responseType) || ResponseType.CODE_ID_TOKEN_TOKEN.equals(responseType));
     }
 
+    private boolean isImplicitFlow(String responseType) {
+        return (ResponseType.ID_TOKEN.equals(responseType) || ResponseType.ID_TOKEN_TOKEN.equals(responseType));
+    }
+
     public static AuthorizationRequestParseHandler create(Domain domain, OpenIDDiscoveryService openIDDiscoveryService) {
         return new AuthorizationRequestParseHandler(domain, openIDDiscoveryService);
     }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/InlineIdentityProviderMapper.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/InlineIdentityProviderMapper.java
index 59ef2d774b..2c9d4d968b 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/InlineIdentityProviderMapper.java
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/InlineIdentityProviderMapper.java
@@ -25,14 +25,16 @@ import java.util.Map;
  */
 public class InlineIdentityProviderMapper implements IdentityProviderMapper {
 
+    private Map<String, String> mappers;
+
     @Override
     public Map<String, String> getMappers() {
-        return null;
+        return mappers;
     }
 
     @Override
     public void setMappers(Map<String, String> mappers) {
-        // nothing to do
+        this.mappers = mappers;
     }
 
 
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/authentication/InlineAuthenticationProvider.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/authentication/InlineAuthenticationProvider.java
index bf0067426a..c4eb91895b 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/authentication/InlineAuthenticationProvider.java
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/authentication/InlineAuthenticationProvider.java
@@ -21,6 +21,7 @@ import io.gravitee.am.identityprovider.api.AuthenticationProvider;
 import io.gravitee.am.identityprovider.api.DefaultUser;
 import io.gravitee.am.identityprovider.api.User;
 import io.gravitee.am.identityprovider.inline.InlineIdentityProviderConfiguration;
+import io.gravitee.am.identityprovider.inline.InlineIdentityProviderMapper;
 import io.gravitee.am.identityprovider.inline.InlineIdentityProviderRoleMapper;
 import io.gravitee.am.identityprovider.inline.authentication.provisioning.InlineInMemoryUserDetailsManager;
 import io.gravitee.am.service.authentication.crypto.password.PasswordEncoder;
@@ -57,6 +58,9 @@ public class InlineAuthenticationProvider implements AuthenticationProvider, Ini
     @Autowired
     private InlineIdentityProviderRoleMapper roleMapper;
 
+    @Autowired
+    private InlineIdentityProviderMapper mapper;
+
     @Override
     public void afterPropertiesSet() {
         for(io.gravitee.am.identityprovider.inline.model.User user : configuration.getUsers()) {
@@ -107,12 +111,27 @@ public class InlineAuthenticationProvider implements AuthenticationProvider, Ini
     private User createUser(io.gravitee.am.identityprovider.inline.model.User inlineUser) {
         DefaultUser user = new DefaultUser(inlineUser.getUsername());
 
+        // add additional information
         Map<String, Object> claims = new HashMap<>();
         claims.put(StandardClaims.SUB, inlineUser.getUsername());
-        claims.put(StandardClaims.NAME, inlineUser.getFirstname() + " " + inlineUser.getLastname());
-        claims.put(StandardClaims.GIVEN_NAME, inlineUser.getFirstname());
-        claims.put(StandardClaims.FAMILY_NAME, inlineUser.getLastname());
-        claims.put(StandardClaims.PREFERRED_USERNAME, inlineUser.getUsername());
+
+        if (mapper != null && mapper.getMappers() != null && !mapper.getMappers().isEmpty()) {
+            mapper.getMappers().forEach((k, v) -> {
+                Object attributeValue = inlineUser.getAttributeValue(v);
+                if (attributeValue != null) {
+                    claims.put(k, attributeValue);
+                }
+            });
+        } else {
+            // default values
+            claims.put(StandardClaims.NAME, inlineUser.getFirstname() + " " + inlineUser.getLastname());
+            claims.put(StandardClaims.GIVEN_NAME, inlineUser.getFirstname());
+            claims.put(StandardClaims.FAMILY_NAME, inlineUser.getLastname());
+            claims.put(StandardClaims.PREFERRED_USERNAME, inlineUser.getUsername());
+            if (inlineUser.getEmail() != null) {
+                claims.put(StandardClaims.EMAIL, inlineUser.getEmail());
+            }
+        }
         user.setAdditonalInformation(claims);
 
         // set user roles
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/model/User.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/model/User.java
index 81803a5fbf..21a943c0e6 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/model/User.java
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/java/io/gravitee/am/identityprovider/inline/model/User.java
@@ -21,10 +21,16 @@ package io.gravitee.am.identityprovider.inline.model;
  */
 public class User {
 
+    private static final String FIRSTNAME = "firstname";
+    private static final String LASTNAME = "lastname";
+    private static final String USERNAME = "username";
+    private static final String EMAIL = "email";
+
     private String firstname;
     private String lastname;
     private String username;
     private String password;
+    private String email;
     private String [] roles;
 
     public String getFirstname() {
@@ -59,6 +65,14 @@ public class User {
         this.password = password;
     }
 
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
     public String[] getRoles() {
         return roles;
     }
@@ -66,4 +80,19 @@ public class User {
     public void setRoles(String[] roles) {
         this.roles = roles;
     }
+
+    public Object getAttributeValue(String attributeKey) {
+        switch (attributeKey) {
+            case FIRSTNAME:
+                return getFirstname();
+            case LASTNAME:
+                return getLastname();
+            case USERNAME:
+                return getUsername();
+            case EMAIL:
+                return getEmail();
+            default:
+                return null;
+        }
+    }
 }
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/resources/schemas/schema-form.json b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/resources/schemas/schema-form.json
index 5070f620a3..32c0cc941e 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/resources/schemas/schema-form.json
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-inline/src/main/resources/schemas/schema-form.json
@@ -25,6 +25,11 @@
             "description": "Username",
             "type" : "string"
           },
+          "email" : {
+            "title": "Email",
+            "description": "Email",
+            "type" : "string"
+          },
           "password" : {
             "title": "Password",
             "description": "Password",


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/AuthorizationEndpointHandlerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/AuthorizationEndpointHandlerTest.java
index 345cdb6ffc..b68885f5a9 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/AuthorizationEndpointHandlerTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/AuthorizationEndpointHandlerTest.java
@@ -728,6 +728,38 @@ public class AuthorizationEndpointHandlerTest  extends RxWebTestBase {
                 HttpStatusCode.FOUND_302, "Found", null);
     }
 
+    @Test
+    public void shouldNotInvokeAuthorizationEndpoint_implicitFlow_nonceMissing() throws Exception {
+        when(domain.getPath()).thenReturn("test");
+
+        testRequest(
+                HttpMethod.GET,
+                "/oauth/authorize?response_type=id_token&client_id=client-id&redirect_uri=http://localhost:9999/callback",
+                null,
+                resp -> {
+                    String location = resp.headers().get("location");
+                    assertNotNull(location);
+                    assertEquals("/test/oauth/error?error=invalid_request&error_description=Missing+parameter%253A+nonce+is+required+for+Implicit+and+Hybrid+Flow", location);
+                },
+                HttpStatusCode.FOUND_302, "Found", null);
+    }
+
+    @Test
+    public void shouldNotInvokeAuthorizationEndpoint_hybridFlow_nonceMissing() throws Exception {
+        when(domain.getPath()).thenReturn("test");
+
+        testRequest(
+                HttpMethod.GET,
+                "/oauth/authorize?response_type=code+id_token&client_id=client-id&redirect_uri=http://localhost:9999/callback",
+                null,
+                resp -> {
+                    String location = resp.headers().get("location");
+                    assertNotNull(location);
+                    assertEquals("/test/oauth/error?error=invalid_request&error_description=Missing+parameter%253A+nonce+is+required+for+Implicit+and+Hybrid+Flow", location);
+                },
+                HttpStatusCode.FOUND_302, "Found", null);
+    }
+
     @Test
     public void shouldInvokeAuthorizationEndpoint_hybridFlow_code_IDToken() throws Exception {
         shouldInvokeAuthorizationEndpoint_hybridFlow(io.gravitee.am.common.oidc.ResponseType.CODE_ID_TOKEN, "code=test-code&id_token=test-id-token", null, "test-id-token");