Repository: gravitee-io/graviteeio-access-management
Pull request #872 (2020-07-16 09:33:11)

feat(facebook-idp): facebook identity provider plugin
Closes gravitee-io/issues#3288


Patch:
diff --git a/Makefile b/Makefile
index 7220771234..48fe15b523 100644
--- a/Makefile
+++ b/Makefile
@@ -283,6 +283,7 @@ pluginsGateway: # Copy plugins to Gateway
 	@cp -fr gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-users/target/gravitee-am-gateway-handler-users-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-discovery/target/gravitee-am-gateway-handler-discovery-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-github/target/gravitee-am-identityprovider-github-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
+	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/target/gravitee-am-identityprovider-facebook-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-inline/target/gravitee-am-identityprovider-inline-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-ldap/target/gravitee-am-identityprovider-ldap-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-mongo/target/gravitee-am-identityprovider-mongo-$(GIO_AM_VERSION).zip $(GIO_AM_GATEWAY_PLUGINS)
@@ -298,6 +299,7 @@ pluginsManagement: # Copy plugins to Management API
 	@cp -fr gravitee-am-extensiongrant/gravitee-am-extensiongrant-jwtbearer/target/gravitee-am-extensiongrant-jwtbearer-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-management-api/gravitee-am-management-api-services/gravitee-am-management-api-services-sync/target/gravitee-am-management-api-services-sync-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-github/target/gravitee-am-identityprovider-github-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
+	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/target/gravitee-am-identityprovider-facebook-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-inline/target/gravitee-am-identityprovider-inline-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-ldap/target/gravitee-am-identityprovider-ldap-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
 	@cp -fr gravitee-am-identityprovider/gravitee-am-identityprovider-mongo/target/gravitee-am-identityprovider-mongo-$(GIO_AM_VERSION).zip $(GIO_AM_MANAGEMENT_API_PLUGINS)
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/resources/webroot/assets/css/login.css b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/resources/webroot/assets/css/login.css
index 2d1cf4ae9d..09831c7c6c 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/resources/webroot/assets/css/login.css
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/resources/webroot/assets/css/login.css
@@ -95,6 +95,7 @@ a, a:hover, a:focus {
 .mdl-social-button {
     display: flex;
     justify-content: center;
+    flex-direction: column;
 }
 
 .mdl-button {
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
index b66cebd6f7..ef0ab31d0e 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
@@ -81,6 +81,14 @@
             <scope>runtime</scope>
         </dependency>
 
+        <dependency>
+            <groupId>io.gravitee.am.identityprovider</groupId>
+            <artifactId>gravitee-am-identityprovider-facebook</artifactId>
+            <version>${project.version}</version>
+            <type>zip</type>
+            <scope>runtime</scope>
+        </dependency>
+
         <dependency>
             <groupId>io.gravitee.am.identityprovider</groupId>
             <artifactId>gravitee-am-identityprovider-oauth2-generic</artifactId>
@@ -294,6 +302,12 @@
                                     <version>${project.version}</version>
                                     <type>zip</type>
                                 </artifactItem>
+                                <artifactItem>
+                                    <groupId>io.gravitee.am.identityprovider</groupId>
+                                    <artifactId>gravitee-am-identityprovider-facebook</artifactId>
+                                    <version>${project.version}</version>
+                                    <type>zip</type>
+                                </artifactItem>
                                 <artifactItem>
                                     <groupId>io.gravitee.am.identityprovider</groupId>
                                     <artifactId>gravitee-am-identityprovider-http</artifactId>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-azure-ad/pom.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-azure-ad/pom.xml
index 848873bb03..96efce9fbf 100644
--- a/gravitee-am-identityprovider/gravitee-am-identityprovider-azure-ad/pom.xml
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-azure-ad/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <artifactId>gravitee-am-identityprovider</artifactId>
         <groupId>io.gravitee.am.identityprovider</groupId>
-        <version>3.1.0-SNAPSHOT</version>
+        <version>3.2.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>gravitee-am-identityprovider-azure-ad</artifactId>
@@ -150,4 +150,4 @@
             </plugin>
         </plugins>
     </build>
-</project>
\ No newline at end of file
+</project>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/pom.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/pom.xml
new file mode 100644
index 0000000000..203b3effb6
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>gravitee-am-identityprovider</artifactId>
+        <groupId>io.gravitee.am.identityprovider</groupId>
+        <version>3.2.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>gravitee-am-identityprovider-facebook</artifactId>
+    <name>Gravitee IO - Access Management - Identity Provider - Facebook</name>
+
+    <dependencies>
+        <!-- Gravitee.io -->
+        <dependency>
+            <groupId>io.gravitee.am.identityprovider</groupId>
+            <artifactId>gravitee-am-identityprovider-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.gravitee.am.service</groupId>
+            <artifactId>gravitee-am-service</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Spring -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Jackson -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Vert.x -->
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-rx-java2</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-web-client</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.tomakehurst</groupId>
+            <artifactId>wiremock</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.3</version>
+                <configuration>
+                    <appendAssemblyId>false</appendAssemblyId>
+                    <descriptors>
+                        <descriptor>src/main/assembly/plugin-assembly.xml</descriptor>
+                    </descriptors>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-plugin-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/assembly/plugin-assembly.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/assembly/plugin-assembly.xml
new file mode 100644
index 0000000000..4de4108175
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/assembly/plugin-assembly.xml
@@ -0,0 +1,58 @@
+<?xml version="1.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.
+
+-->
+<assembly>
+	<id>plugin</id>
+	<formats>
+		<format>zip</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+
+	<!-- Include the main plugin Jar file -->
+	<files>
+		<file>
+			<source>${project.build.directory}/${project.build.finalName}.jar</source>
+		</file>
+	</files>
+
+	<fileSets>
+		<!-- Then include identity provider configuration schemas -->
+		<fileSet>
+			<directory>src/main/resources/schemas</directory>
+			<outputDirectory>schemas</outputDirectory>
+		</fileSet>
+
+		<!-- Create the empty lib directory in case of no libraries is required -->
+		<!-- As there is no maven-assembly-plugin's method do to that, we hack it ourself -->
+		<fileSet>
+			<directory>${project.basedir}/src/assembly</directory>
+			<outputDirectory>lib</outputDirectory>
+			<excludes>
+				<exclude>*</exclude>
+			</excludes>
+		</fileSet>
+	</fileSets>
+
+	<!-- Finally include plugin dependencies -->
+	<dependencySets>
+		<dependencySet>
+			<outputDirectory>lib</outputDirectory>
+			<useProjectArtifact>false</useProjectArtifact>
+		</dependencySet>
+	</dependencySets>
+</assembly>
\ No newline at end of file
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProvider.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProvider.java
new file mode 100644
index 0000000000..7485f3f8e0
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProvider.java
@@ -0,0 +1,50 @@
+/**
+ * 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.
+ */
+package io.gravitee.am.identityprovider.facebook;
+
+import io.gravitee.am.identityprovider.api.AuthenticationProvider;
+import io.gravitee.am.identityprovider.api.IdentityProviderConfiguration;
+import io.gravitee.am.identityprovider.api.IdentityProviderMapper;
+import io.gravitee.am.identityprovider.api.IdentityProviderRoleMapper;
+import io.gravitee.am.identityprovider.api.social.SocialIdentityProvider;
+import io.gravitee.am.identityprovider.facebook.authentication.FacebookAuthenticationProvider;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FacebookIdentityProvider implements SocialIdentityProvider {
+
+    @Override
+    public Class<? extends IdentityProviderConfiguration> configuration() {
+        return FacebookIdentityProviderConfiguration.class;
+    }
+
+    @Override
+    public Class<? extends AuthenticationProvider> authenticationProvider() {
+        return FacebookAuthenticationProvider.class;
+    }
+
+    @Override
+    public Class<? extends IdentityProviderMapper> mapper() {
+        return FacebookIdentityProviderMapper.class;
+    }
+
+    @Override
+    public Class<? extends IdentityProviderRoleMapper> roleMapper() {
+        return FacebookIdentityProviderRoleMapper.class;
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProviderConfiguration.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProviderConfiguration.java
new file mode 100644
index 0000000000..e85b3f0b07
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProviderConfiguration.java
@@ -0,0 +1,99 @@
+/**
+ * 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.
+ */
+package io.gravitee.am.identityprovider.facebook;
+
+import io.gravitee.am.identityprovider.api.IdentityProviderConfiguration;
+
+import java.util.Set;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FacebookIdentityProviderConfiguration implements IdentityProviderConfiguration {
+
+    private static final String USER_AUTHORIZATION_URI = "https://www.facebook.com/v7.0/dialog/oauth";
+    private static final String ACCESS_TOKEN_URI = "https://graph.facebook.com/v7.0/oauth/access_token";
+    private static final String USER_PROFILE_URI = "https://graph.facebook.com/v7.0/me";
+    private static final String CODE_PARAMETER = "code";
+    private static final String RESPONSE_TYPE = "code";
+
+    private String clientId;
+    private String clientSecret;
+    private Set<String> scopes;
+    private Integer connectTimeout = 10000;
+    private Integer maxPoolSize = 200;
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getClientSecret() {
+        return clientSecret;
+    }
+
+    public void setClientSecret(String clientSecret) {
+        this.clientSecret = clientSecret;
+    }
+
+    public Set<String> getScopes() {
+        return scopes;
+    }
+
+    public void setScopes(Set<String> scopes) {
+        this.scopes = scopes;
+    }
+
+    public String getUserAuthorizationUri() {
+        return USER_AUTHORIZATION_URI;
+    }
+
+    public String getAccessTokenUri() {
+        return ACCESS_TOKEN_URI;
+    }
+
+    public String getUserProfileUri() {
+        return USER_PROFILE_URI;
+    }
+
+    public String getCodeParameter() {
+        return CODE_PARAMETER;
+    }
+
+    public String getResponseType() {
+        return RESPONSE_TYPE;
+    }
+
+    public Integer getConnectTimeout() {
+        return connectTimeout;
+    }
+
+    public void setConnectTimeout(Integer connectTimeout) {
+        this.connectTimeout = connectTimeout;
+    }
+
+    public Integer getMaxPoolSize() {
+        return maxPoolSize;
+    }
+
+    public void setMaxPoolSize(Integer maxPoolSize) {
+        this.maxPoolSize = maxPoolSize;
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProviderMapper.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProviderMapper.java
new file mode 100644
index 0000000000..8685994a4a
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProviderMapper.java
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ */
+package io.gravitee.am.identityprovider.facebook;
+
+import io.gravitee.am.identityprovider.api.IdentityProviderMapper;
+
+import java.util.Map;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FacebookIdentityProviderMapper implements IdentityProviderMapper {
+
+    private Map<String, String> mappers;
+
+    public Map<String, String> getMappers() {
+        return this.mappers;
+    }
+
+    public void setMappers(Map<String, String> mappers) {
+        this.mappers = mappers;
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProviderRoleMapper.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProviderRoleMapper.java
new file mode 100644
index 0000000000..ef63584358
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/FacebookIdentityProviderRoleMapper.java
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ */
+package io.gravitee.am.identityprovider.facebook;
+
+import io.gravitee.am.identityprovider.api.IdentityProviderRoleMapper;
+
+import java.util.Map;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FacebookIdentityProviderRoleMapper implements IdentityProviderRoleMapper {
+
+    private Map<String, String[]> roles;
+
+    public Map<String, String[]> getRoles() {
+        return this.roles;
+    }
+
+    public void setRoles(Map<String, String[]> roles) {
+        this.roles = roles;
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/authentication/FacebookAuthenticationProvider.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/authentication/FacebookAuthenticationProvider.java
new file mode 100644
index 0000000000..3d7f142ed0
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/authentication/FacebookAuthenticationProvider.java
@@ -0,0 +1,334 @@
+/**
+ * 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.
+ */
+package io.gravitee.am.identityprovider.facebook.authentication;
+
+import io.gravitee.am.common.exception.authentication.BadCredentialsException;
+import io.gravitee.am.common.oauth2.Parameters;
+import io.gravitee.am.common.oidc.StandardClaims;
+import io.gravitee.am.common.web.UriBuilder;
+import io.gravitee.am.identityprovider.api.Authentication;
+import io.gravitee.am.identityprovider.api.DefaultUser;
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.identityprovider.api.common.Request;
+import io.gravitee.am.identityprovider.api.social.SocialAuthenticationProvider;
+import io.gravitee.am.identityprovider.facebook.FacebookIdentityProviderConfiguration;
+import io.gravitee.am.identityprovider.facebook.FacebookIdentityProviderMapper;
+import io.gravitee.am.identityprovider.facebook.FacebookIdentityProviderRoleMapper;
+import io.gravitee.am.identityprovider.facebook.authentication.spring.FacebookAuthenticationProviderConfiguration;
+import io.gravitee.am.identityprovider.facebook.model.FacebookUser;
+import io.gravitee.common.http.HttpMethod;
+import io.reactivex.Maybe;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import io.vertx.reactivex.core.MultiMap;
+import io.vertx.reactivex.ext.web.client.WebClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Import;
+
+import java.util.*;
+
+import static io.gravitee.am.common.oidc.Scope.SCOPE_DELIMITER;
+import static io.gravitee.am.identityprovider.facebook.model.FacebookUser.*;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Import(FacebookAuthenticationProviderConfiguration.class)
+public class FacebookAuthenticationProvider implements SocialAuthenticationProvider {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FacebookAuthenticationProvider.class);
+    private static final String CLIENT_ID = "client_id";
+    private static final String CLIENT_SECRET = "client_secret";
+    private static final String REDIRECT_URI = "redirect_uri";
+    private static final String CODE = "code";
+    private static final String ACCESS_TOKEN = "access_token";
+    private static final String FIELDS = "fields";
+
+    @Autowired
+    @Qualifier("facebookWebClient")
+    private WebClient client;
+
+    @Autowired
+    private FacebookIdentityProviderConfiguration configuration;
+
+    @Autowired
+    private FacebookIdentityProviderMapper mapper;
+
+    @Autowired
+    private FacebookIdentityProviderRoleMapper roleMapper;
+
+    @Override
+    public Request signInUrl(String redirectUri) {
+        try {
+            UriBuilder builder = UriBuilder.fromHttpUrl(configuration.getUserAuthorizationUri());
+            builder.addParameter(Parameters.CLIENT_ID, configuration.getClientId());
+            builder.addParameter(Parameters.REDIRECT_URI, redirectUri);
+            builder.addParameter(Parameters.RESPONSE_TYPE, configuration.getResponseType());
+            if (configuration.getScopes() != null && !configuration.getScopes().isEmpty()) {
+                builder.addParameter(Parameters.SCOPE, String.join(SCOPE_DELIMITER, configuration.getScopes()));
+            }
+
+            Request request = new Request();
+            request.setMethod(HttpMethod.GET);
+            request.setUri(builder.build().toString());
+            return request;
+        } catch (Exception e) {
+            LOGGER.error("An error occurs while building Facebook Sign In URL", e);
+            return null;
+        }
+    }
+
+    @Override
+    public Maybe<User> loadUserByUsername(Authentication authentication) {
+        return authenticate(authentication)
+                .flatMap(this::profile);
+    }
+
+    @Override
+    public Maybe<User> loadUserByUsername(String username) {
+        return Maybe.empty();
+    }
+
+    private Maybe<String> authenticate(Authentication authentication) {
+
+        // Prepare body request parameters.
+        final String authorizationCode = authentication.getContext().request().parameters().getFirst(configuration.getCodeParameter());
+
+        if (authorizationCode == null || authorizationCode.isEmpty()) {
+            LOGGER.debug("Authorization code is missing, skip authentication");
+            return Maybe.error(new BadCredentialsException("Missing authorization code"));
+        }
+
+        MultiMap form = MultiMap.caseInsensitiveMultiMap()
+                .set(CLIENT_ID, configuration.getClientId())
+                .set(CLIENT_SECRET, configuration.getClientSecret())
+                .set(REDIRECT_URI, (String) authentication.getContext().get(REDIRECT_URI))
+                .set(CODE, authorizationCode);
+
+        return client.postAbs(configuration.getAccessTokenUri())
+                .rxSendForm(form)
+                .toMaybe()
+                .flatMap(httpResponse -> {
+                    if (httpResponse.statusCode() != 200) {
+                        return Maybe.error(new BadCredentialsException(httpResponse.bodyAsString()));
+                    }
+
+                    return Maybe.just(httpResponse.bodyAsJsonObject().getString(ACCESS_TOKEN));
+                });
+    }
+
+    private Maybe<User> profile(String accessToken) {
+
+        return client.postAbs(configuration.getUserProfileUri())
+                .rxSendForm(MultiMap.caseInsensitiveMultiMap()
+                        .set(ACCESS_TOKEN, accessToken)
+                        .set(FIELDS, ALL_FIELDS_PARAM))
+                .toMaybe()
+                .flatMap(httpResponse -> {
+                    if (httpResponse.statusCode() != 200) {
+                       return Maybe.error(new BadCredentialsException(httpResponse.bodyAsString()));
+                    }
+
+                    return Maybe.just(convert(httpResponse.bodyAsJsonObject()));
+                });
+    }
+
+    private User convert(JsonObject facebookUser) {
+
+        DefaultUser user = new DefaultUser(String.valueOf(facebookUser.getString(FacebookUser.ID)));
+        user.setId(facebookUser.getString(FacebookUser.ID));
+
+        // Set additional information.
+        Map<String, Object> additionalInformation = new HashMap<>();
+
+        // Standard claims.
+        convertClaim(FacebookUser.ID, facebookUser, StandardClaims.SUB, additionalInformation);
+        convertClaim(FacebookUser.NAME, facebookUser, StandardClaims.NAME, additionalInformation);
+
+        // There is no way to retrieve username with facebook api. As it is mandatory for AM, set it to facebook ID.
+        convertClaim(FacebookUser.ID, facebookUser, StandardClaims.PREFERRED_USERNAME, additionalInformation);
+
+        // Apply user mapping.
+        additionalInformation.putAll(applyUserMapping(facebookUser));
+
+        // Update username if user mapping has changed.
+        if (additionalInformation.containsKey(StandardClaims.PREFERRED_USERNAME)) {
+            user.setUsername((String) additionalInformation.get(StandardClaims.PREFERRED_USERNAME));
+        }
+
+        user.setAdditionalInformation(additionalInformation);
+
+        // Set user roles.
+        user.setRoles(applyRoleMapping(facebookUser));
+
+        return user;
+    }
+
+    /**
+     * TODO: Should be mutualized across idps.
+     */
+    private Map<String, Object> applyUserMapping(JsonObject attributes) {
+        if (!mappingEnabled()) {
+            return defaultClaims(attributes);
+        }
+
+        Map<String, Object> claims = new HashMap<>();
+        this.mapper.getMappers().forEach((k, v) -> {
+            if (attributes.containsKey(v)) {
+                claims.put(k, attributes.getValue(v));
+            }
+        });
+        return claims;
+    }
+
+    /**
+     * TODO: Should be mutualized across idps.
+     */
+    private List<String> applyRoleMapping(JsonObject attributes) {
+        if (!roleMappingEnabled()) {
+            return Collections.emptyList();
+        }
+
+        Set<String> roles = new HashSet<>();
+        roleMapper.getRoles().forEach((role, users) -> {
+            Arrays.asList(users).forEach(u -> {
+                // role mapping have the following syntax userAttribute=userValue
+                String[] roleMapping = u.split("=", 2);
+                String userAttribute = roleMapping[0];
+                String userValue = roleMapping[1];
+                if (attributes.containsKey(userAttribute)) {
+                    Object attribute = attributes.getValue(userAttribute);
+                    // attribute is a list
+                    if (attribute instanceof Collection && ((Collection) attribute).contains(userValue)) {
+                        roles.add(role);
+                    } else if (userValue.equals(attributes.getValue(userAttribute))) {
+                        roles.add(role);
+                    }
+                }
+            });
+        });
+
+        return new ArrayList<>(roles);
+    }
+
+    private Map<String, Object> defaultClaims(JsonObject attributes) {
+        Map<String, Object> claims = new HashMap<>();
+
+        convertClaim(FacebookUser.ID, attributes, StandardClaims.SUB, claims);
+        convertClaim(FacebookUser.NAME, attributes, StandardClaims.NAME, claims);
+        convertClaim(FacebookUser.FIRST_NAME, attributes, StandardClaims.GIVEN_NAME, claims);
+        convertClaim(FacebookUser.LAST_NAME, attributes, StandardClaims.FAMILY_NAME, claims);
+        convertClaim(FacebookUser.MIDDLE_NAME, attributes, StandardClaims.MIDDLE_NAME, claims);
+
+        // Facebook picture has a specific structure we handle specifically.
+        if (attributes.containsKey(FacebookUser.PICTURE)) {
+            claims.put(StandardClaims.PICTURE, attributes.getJsonObject(FacebookUser.PICTURE).getJsonObject("data").getString("url"));
+        }
+
+        convertClaim(FacebookUser.LINK, attributes, StandardClaims.PROFILE, claims);
+        convertClaim(FacebookUser.EMAIL, attributes, StandardClaims.EMAIL, claims);
+        convertClaim(FacebookUser.GENDER, attributes, StandardClaims.GENDER, claims);
+
+        // Facebook birthday must be parsed and transformed to be oidc compliant.
+        convertBirthDateClaim(attributes, claims);
+
+        // Facebook address must be parsed and transformed to be oidc compliant.
+        convertAddressClaim(attributes, claims);
+
+        // All others custom Facebook claims.
+        for (String field : OTHER_FIELDS_LIST) {
+            convertClaim(field, attributes, field, claims);
+        }
+
+        return claims;
+    }
+
+    /**
+     * TODO: Should be mutualized across idps.
+     */
+    private boolean mappingEnabled() {
+        return this.mapper != null && this.mapper.getMappers() != null && !this.mapper.getMappers().isEmpty();
+    }
+
+    /**
+     * TODO: Should be mutualized across idps.
+     */
+    private boolean roleMappingEnabled() {
+        return this.roleMapper != null && this.roleMapper.getRoles() != null && !this.roleMapper.getRoles().isEmpty();
+    }
+
+    private void convertClaim(String sourceName, JsonObject source, String destName, Map<String, Object> dest) {
+
+        if (dest.containsKey(destName)) {
+            // Skip if already present in the dest.
+            return;
+        }
+
+        Object value = source.getValue(sourceName);
+        if (value != null) {
+            if (value instanceof JsonObject) {
+                dest.put(destName, ((JsonObject) value).getMap());
+            } else if (value instanceof JsonArray) {
+                dest.put(destName, ((JsonArray) value).getList());
+            } else {
+                dest.put(destName, value);
+            }
+        }
+    }
+
+    private void convertBirthDateClaim(JsonObject source, Map<String, Object> dest) {
+
+        if (source.containsKey(FacebookUser.BIRTHDAY)) {
+            // Facebook birthday format can be one of the following: 'MM/DD/YYYY', 'MM/DD', 'YYYY'.
+            String[] split = source.getString(FacebookUser.BIRTHDAY).split("/");
+            String birthdate;
+
+            // OIDC birthdate expected format is ISO8601‑2004 (eg: YYYY-MM-DD). See https://openid.net/specs/openid-connect-core-1_0.html#Claims
+            if (split.length == 3) {
+                // 'MM/DD/YYYY' -> 'YYYY-MM-DD'
+                birthdate = split[2] + "-" + split[0] + "-" + split[1];
+            } else if (split.length == 2) {
+                // 'MM/DD' -> '0000-MM-DD'
+                birthdate = "0000-" + split[0] + "-" + split[1];
+            } else {
+                // 'YYYY -> YYYY'
+                birthdate = split[0];
+            }
+
+            dest.put(StandardClaims.BIRTHDATE, birthdate);
+        }
+    }
+
+    private void convertAddressClaim(JsonObject source, Map<String, Object> dest) {
+
+        if (source.containsKey(FacebookUser.LOCATION)) {
+            JsonObject location = source.getJsonObject(FacebookUser.LOCATION).getJsonObject(LOCATION);
+            Map<String, Object> address = new HashMap<>();
+
+            convertClaim("street", location, "street_address", address);
+            convertClaim("city", location, "locality", address);
+            convertClaim("region", location, "region", address);
+            convertClaim("zip", location, "postal_code", address);
+            convertClaim("country", location, "country", address);
+
+            dest.put(StandardClaims.ADDRESS, address);
+        }
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/authentication/spring/FacebookAuthenticationProviderConfiguration.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/authentication/spring/FacebookAuthenticationProviderConfiguration.java
new file mode 100644
index 0000000000..0cb61cc631
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/authentication/spring/FacebookAuthenticationProviderConfiguration.java
@@ -0,0 +1,53 @@
+/**
+ * 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.
+ */
+package io.gravitee.am.identityprovider.facebook.authentication.spring;
+
+import io.gravitee.am.identityprovider.facebook.FacebookIdentityProviderConfiguration;
+import io.vertx.ext.web.client.WebClientOptions;
+import io.vertx.reactivex.core.Vertx;
+import io.vertx.reactivex.ext.web.client.WebClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Configuration
+public class FacebookAuthenticationProviderConfiguration {
+
+    private static final String DEFAULT_USER_AGENT = "Gravitee.io-AM/3";
+
+    @Autowired
+    private Vertx vertx;
+
+    @Autowired
+    private FacebookIdentityProviderConfiguration configuration;
+
+    @Bean
+    @Qualifier("facebookWebClient")
+    public WebClient httpClient() {
+        WebClientOptions httpClientOptions = new WebClientOptions();
+        httpClientOptions
+                .setUserAgent(DEFAULT_USER_AGENT)
+                .setConnectTimeout(configuration.getConnectTimeout())
+                .setMaxPoolSize(configuration.getMaxPoolSize());
+
+        return WebClient.create(vertx, httpClientOptions);
+    }
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/model/FacebookUser.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/model/FacebookUser.java
new file mode 100644
index 0000000000..146fa59da1
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/java/io/gravitee/am/identityprovider/facebook/model/FacebookUser.java
@@ -0,0 +1,117 @@
+/**
+ * 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.
+ */
+package io.gravitee.am.identityprovider.facebook.model;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Facebook User claims
+ *
+ * See <a href="https://developers.facebook.com/docs/graph-api/reference/user">User reference</a></a>
+ * See <a href="https://developers.facebook.com/docs/permissions/reference>Permission reference</a>
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public abstract class FacebookUser {
+
+    public static final String ID = "id";
+
+    // Default Public Profile fields (see https://developers.facebook.com/docs/facebook-login/permissions/?locale=en_US#reference-default)
+    public static final String FIRST_NAME = "first_name";
+    public static final String LAST_NAME = "last_name";
+    public static final String MIDDLE_NAME = "middle_name";
+    public static final String NAME = "name";
+    public static final String NAME_FORMAT = "name_format";
+    public static final String PICTURE = "picture";
+    public static final String SHORT_NAME = "short_name";
+
+    // Email field (see https://developers.facebook.com/docs/facebook-login/permissions/?locale=en_US#reference-email)
+    public static final String EMAIL = "email";
+
+    // Age range (see https://developers.facebook.com/docs/facebook-login/permissions/?locale=en_US#reference-user_age_range)
+    public static final String AGE_RANGE = "age_range";
+
+    // Birthday field (see https://developers.facebook.com/docs/facebook-login/permissions/?locale=en_US#reference-user_birthday)
+    public static final String BIRTHDAY = "birthday";
+
+    // Gender field (see https://developers.facebook.com/docs/facebook-login/permissions/?locale=en_US#reference-user_gender)
+    public static final String GENDER = "gender";
+
+    // User Link field (see https://developers.facebook.com/docs/facebook-login/permissions/?locale=en_US#reference-user_link)
+    public static final String LINK = "link";
+
+    // Location fields (see https://developers.facebook.com/docs/facebook-login/permissions/?locale=en_US#reference-user_location)
+    public static final String LOCATION = "location";
+
+    // Other fields, but there are many others that can be added if necessary (see https://developers.facebook.com/docs/graph-api/reference/user).
+    public static final String ABOUT = "about";
+    public static final String CAN_REVIEW_MEASUREMENT_REQUEST = "can_review_measurement_request";
+    public static final String INSTALLED = "installed";
+    public static final String INSTALL_TYPE = "install_type";
+    public static final String IS_FAMEDEEPLINKINGUSER = "is_famedeeplinkinguser";
+    public static final String IS_GUEST_USER = "is_guest_user";
+    public static final String IS_SHARED_LOGIN = "is_shared_login";
+    public static final String LANGUAGES = "languages";
+    public static final String PUBLIC_KEY = "public_key";
+    public static final String QUOTES = "quotes";
+    public static final String SECURITY_SETTINGS = "security_settings";
+    public static final String SHARED_LOGIN_UPGRADE_REQUIRED_BY = "shared_login_upgrade_required_by";
+    public static final String SIGNIFICANT_OTHER = "significant_other";
+    public static final String SPORTS = "sports";
+    public static final String SUPPORTS_DONATE_BUTTON_IN_LIVE_VIDEO = "supports_donate_button_in_live_video";
+    public static final String TEST_GROUP = "test_group";
+    public static final String VIEWER_CAN_SEND_GIFT = "viewer_can_send_gift";
+
+    public static final String LOCATION_STREET_FIELD = "street";
+    public static final String LOCATION_CITY_FIELD = "city";
+    public static final String LOCATION_REGION_FIELD = "region";
+    public static final String LOCATION_ZIP_FIELD = "zip";
+    public static final String LOCATION_COUNTRY_FIELD = "country";
+
+    /**
+     * Specific field param used to retrieve nested fields of 'location' field.
+     */
+    public static final String LOCATION_ALL_FIELDS_PARAM = LOCATION + "{" + LOCATION + "{" +
+            String.join(",", LOCATION_STREET_FIELD, LOCATION_CITY_FIELD, LOCATION_REGION_FIELD, LOCATION_ZIP_FIELD, LOCATION_COUNTRY_FIELD) +
+        "}"+"}";
+
+    /**
+     * Contains all useful fields we can retrieve and map to standard OIDC claims.
+     */
+    public static final List<String> BASE_FIELD_LIST = Arrays.asList(ID, FIRST_NAME, LAST_NAME, MIDDLE_NAME, NAME, NAME_FORMAT, PICTURE, SHORT_NAME,
+            EMAIL, AGE_RANGE, BIRTHDAY, GENDER, LINK, LOCATION);
+
+    /**
+     * Contains all others fields we can retrieve and store as additional information.
+     */
+    public static final List<String> OTHER_FIELDS_LIST = Arrays.asList(ABOUT, CAN_REVIEW_MEASUREMENT_REQUEST,
+            INSTALLED, INSTALL_TYPE, IS_FAMEDEEPLINKINGUSER, IS_GUEST_USER, IS_SHARED_LOGIN, LANGUAGES, PUBLIC_KEY, QUOTES, SECURITY_SETTINGS, SHARED_LOGIN_UPGRADE_REQUIRED_BY, SIGNIFICANT_OTHER,
+            SPORTS, SUPPORTS_DONATE_BUTTON_IN_LIVE_VIDEO, TEST_GROUP, VIEWER_CAN_SEND_GIFT);
+
+    /**
+     * Contains all fields we have to retrieve from Facebook api.
+     */
+    public static final List<String> ALL_FIELDS_LIST = Stream.of(BASE_FIELD_LIST, OTHER_FIELDS_LIST).flatMap(Collection::stream).collect(Collectors.toList());
+
+    /**
+     * Contains all field names coma-separated.
+     */
+    public static final String ALL_FIELDS_PARAM = String.join(",", ALL_FIELDS_LIST).replace(LOCATION, LOCATION_ALL_FIELDS_PARAM);
+}
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/resources/plugin.properties b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/resources/plugin.properties
new file mode 100644
index 0000000000..0e89dd8c33
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/resources/plugin.properties
@@ -0,0 +1,6 @@
+id=facebook-am-idp
+name=${project.name}
+version=${project.version}
+description=${project.description}
+class=io.gravitee.am.identityprovider.facebook.FacebookIdentityProvider
+type=identity_provider
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/resources/schemas/schema-form.json b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/resources/schemas/schema-form.json
new file mode 100644
index 0000000000..a6f05cf7cb
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/main/resources/schemas/schema-form.json
@@ -0,0 +1,86 @@
+{
+  "type" : "object",
+  "id" : "urn:jsonschema:io:gravitee:am:identityprovider:facebook:FacebookIdentityProviderConfiguration",
+  "description": "Facebook identity provider, let you register and set up permissions and authorization options for OAuth 2.0 protocol.\n\nhttps://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/ ",
+  "properties" : {
+    "clientId" : {
+      "type" : "string",
+      "title": "Client ID",
+      "description": "Required. The client ID you received from Facebook when you registered."
+    },
+    "clientSecret" : {
+      "type" : "string",
+      "title": "Client Secret",
+      "description": "Required. The client secret you received from Facebook for your Facebook App."
+    },
+    "scopes":{
+      "type": "array",
+      "title": "Scopes",
+      "description": "Scopes let you specify exactly what type of access you need. Scopes limit access for OAuth tokens.",
+      "items":
+        {
+          "type": "string",
+          "enum": [
+            "ads_management",
+            "ads_read",
+            "attribution_read",
+            "catalog_management",
+            "business_management",
+            "email",
+            "groups_access_member_info",
+            "instagram_basic",
+            "instagram_manage_comments",
+            "instagram_manage_insights",
+            "leads_retrieval",
+            "manage_pages",
+            "pages_manage_ads",
+            "pages_manage_cta",
+            "pages_manage_engagement",
+            "pages_manage_instant_articles",
+            "pages_manage_metadata",
+            "pages_manage_posts",
+            "pages_messaging",
+            "pages_read_engagement",
+            "pages_read_user_content",
+            "pages_show_list",
+            "pages_user_gender",
+            "pages_user_locale",
+            "pages_user_timezone",
+            "publish_pages",
+            "publish_to_groups",
+            "publish_video",
+            "read_insights",
+            "user_age_range",
+            "user_birthday",
+            "user_friends",
+            "user_gender",
+            "user_hometown",
+            "user_likes",
+            "user_link",
+            "user_location",
+            "user_photos",
+            "user_posts",
+            "user_videos",
+            "whatsapp_business_management" ]
+        }
+    },
+    "connectTimeout" : {
+      "type" : "integer",
+      "default": 10000,
+      "minimum": 1,
+      "title": "HTTP Client connect timeout",
+      "description": "Duration of time in milliseconds that connects will block. (default 10000 ms)"
+    },
+    "maxPoolSize" : {
+      "type" : "integer",
+      "default": 200,
+      "minimum": 1,
+      "title": "HTTP Client max pool size",
+      "description": "Maximum pool of connections can grow to. (default 200)"
+    }
+  },
+  "required": [
+    "clientId",
+    "clientSecret"
+  ]
+}
diff --git a/gravitee-am-identityprovider/pom.xml b/gravitee-am-identityprovider/pom.xml
index ae5c1a3f27..60f133e35d 100644
--- a/gravitee-am-identityprovider/pom.xml
+++ b/gravitee-am-identityprovider/pom.xml
@@ -41,6 +41,7 @@
         <module>gravitee-am-identityprovider-oauth2-generic</module>
         <module>gravitee-am-identityprovider-http</module>
         <module>gravitee-am-identityprovider-azure-ad</module>
+        <module>gravitee-am-identityprovider-facebook</module>
     </modules>
 
     <dependencies>
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/JWTAuthenticationFilter.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/JWTAuthenticationFilter.java
index 80d8c0edb7..d8a8051e1c 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/JWTAuthenticationFilter.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/JWTAuthenticationFilter.java
@@ -79,6 +79,7 @@ public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFil
         setAuthenticationManager(new NoopAuthenticationManager());
         setAuthenticationSuccessHandler(new JWTAuthenticationSuccessHandler());
         setAuthenticationFailureHandler(new JWTAuthenticationFailureHandler());
+        super.setAllowSessionCreation(false);
     }
 
     @Override
@@ -170,6 +171,12 @@ public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFil
 
     private class JWTAuthenticationFailureHandler implements AuthenticationFailureHandler {
 
+
+        public JWTAuthenticationFailureHandler() {
+            super();
+            setAllowSessionCreation(false);
+        }
+
         @Override
         public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
             http401UnauthorizedEntryPoint.commence(request, response, exception);
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/SocialAuthenticationFilter.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/SocialAuthenticationFilter.java
index 97bcac418b..ae61977ec3 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/SocialAuthenticationFilter.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/SocialAuthenticationFilter.java
@@ -76,7 +76,10 @@ public class SocialAuthenticationFilter extends AbstractAuthenticationProcessing
     public SocialAuthenticationFilter(String defaultFilterProcessesUrl) {
         super(defaultFilterProcessesUrl);
         setAuthenticationManager(new NoopAuthenticationManager());
-        setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(errorPage));
+        SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler(errorPage);
+        failureHandler.setAllowSessionCreation(false);
+        setAuthenticationFailureHandler(failureHandler);
+        setAllowSessionCreation(false);
     }
 
     @Override
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/handler/CustomAuthenticationFailureHandler.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/handler/CustomAuthenticationFailureHandler.java
index edd0633433..8b926b84b3 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/handler/CustomAuthenticationFailureHandler.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/handler/CustomAuthenticationFailureHandler.java
@@ -44,10 +44,6 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
     @Autowired
     private AuditService auditService;
 
-    public CustomAuthenticationFailureHandler() {
-        super();
-    }
-
     public CustomAuthenticationFailureHandler(String defaultFailureUrl) {
         super(defaultFailureUrl);
         super.setAllowSessionCreation(false);
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/resources/assets/css/login.css b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/resources/assets/css/login.css
index 0bd053213d..575bc00cf1 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/resources/assets/css/login.css
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/resources/assets/css/login.css
@@ -109,6 +109,7 @@ a, a:hover, a:focus {
 .mdl-social-button {
     display: flex;
     justify-content: center;
+    flex-direction: column;
 }
 
 .mdl-button {
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
index 7466f609ce..b76bc453fc 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
@@ -76,6 +76,14 @@
             <scope>runtime</scope>
         </dependency>
 
+        <dependency>
+            <groupId>io.gravitee.am.identityprovider</groupId>
+            <artifactId>gravitee-am-identityprovider-facebook</artifactId>
+            <version>${project.version}</version>
+            <type>zip</type>
+            <scope>runtime</scope>
+        </dependency>
+
         <dependency>
             <groupId>io.gravitee.am.identityprovider</groupId>
             <artifactId>gravitee-am-identityprovider-oauth2-generic</artifactId>
@@ -257,6 +265,12 @@
                                     <version>${project.version}</version>
                                     <type>zip</type>
                                 </artifactItem>
+                                <artifactItem>
+                                    <groupId>io.gravitee.am.identityprovider</groupId>
+                                    <artifactId>gravitee-am-identityprovider-facebook</artifactId>
+                                    <version>${project.version}</version>
+                                    <type>zip</type>
+                                </artifactItem>
                                 <artifactItem>
                                     <groupId>io.gravitee.am.identityprovider</groupId>
                                     <artifactId>gravitee-am-identityprovider-http</artifactId>
diff --git a/gravitee-am-ui/src/app/domain/applications/application/idp/idp.component.ts b/gravitee-am-ui/src/app/domain/applications/application/idp/idp.component.ts
index e3c2dd0265..b0d6f2bbb4 100644
--- a/gravitee-am-ui/src/app/domain/applications/application/idp/idp.component.ts
+++ b/gravitee-am-ui/src/app/domain/applications/application/idp/idp.component.ts
@@ -33,7 +33,8 @@ export class ApplicationIdPComponent implements OnInit {
     'inline-am-idp': 'Inline',
     'oauth2-generic-am-idp': 'OpenID Connect',
     'github-am-idp': 'GitHub',
-    'azure-ad-am-idp': 'Azure AD'
+    'azure-ad-am-idp': 'Azure AD',
+    'facebook-am-idp': 'Facebook'
   };
   private identityProviderIcons: any = {
     'ldap-am-idp' : 'device_hub',
@@ -41,7 +42,8 @@ export class ApplicationIdPComponent implements OnInit {
     'inline-am-idp': 'insert_drive_file',
     'oauth2-generic-am-idp': 'cloud_queue',
     'github-am-idp': 'cloud_queue',
-    'azure-ad-am-idp': 'cloud_queue'
+    'azure-ad-am-idp': 'cloud_queue',
+    'facebook-am-idp': 'cloud_queue'
   };
   loadIdentities = true;
   application: any;
diff --git a/gravitee-am-ui/src/app/domain/settings/providers/creation/steps/step1/step1.component.ts b/gravitee-am-ui/src/app/domain/settings/providers/creation/steps/step1/step1.component.ts
index ec390d5939..a27bfce6c1 100644
--- a/gravitee-am-ui/src/app/domain/settings/providers/creation/steps/step1/step1.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/providers/creation/steps/step1/step1.component.ts
@@ -29,6 +29,7 @@ export class ProviderCreationStep1Component implements OnInit {
     'oauth2-generic-am-idp': 'OpenID Connect',
     'azure-ad-am-idp': 'Azure AD',
     'github-am-idp': 'GitHub',
+    'facebook-am-idp': 'Facebook',
     'http-am-idp': 'HTTP'
   };
   @Input() provider;
diff --git a/gravitee-am-ui/src/app/domain/settings/providers/providers.component.ts b/gravitee-am-ui/src/app/domain/settings/providers/providers.component.ts
index 154d1086fe..845f74b5c7 100644
--- a/gravitee-am-ui/src/app/domain/settings/providers/providers.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/providers/providers.component.ts
@@ -35,7 +35,8 @@ export class DomainSettingsProvidersComponent implements OnInit {
     'inline-am-idp': 'Inline',
     'oauth2-generic-am-idp': 'OpenID Connect',
     'github-am-idp': 'GitHub',
-    'azure-ad-am-idp': 'Azure AD'
+    'azure-ad-am-idp': 'Azure AD',
+    'facebook-am-idp': 'Facebook'
   };
   private identityProviderIcons: any = {
     'ldap-am-idp' : 'device_hub',
@@ -43,7 +44,8 @@ export class DomainSettingsProvidersComponent implements OnInit {
     'inline-am-idp': 'insert_drive_file',
     'oauth2-generic-am-idp': 'cloud_queue',
     'github-am-idp': 'cloud_queue',
-    'azure-ad-am-idp': 'cloud_queue'
+    'azure-ad-am-idp': 'cloud_queue',
+    'facebook-am-idp': 'cloud_queue'
   };
   domainId: string;
 


Test patch:
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/test/java/io/gravitee/am/identityprovider/facebook/authentication/FacebookAuthenticationProviderTest.java b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/test/java/io/gravitee/am/identityprovider/facebook/authentication/FacebookAuthenticationProviderTest.java
new file mode 100644
index 0000000000..e5009446da
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/test/java/io/gravitee/am/identityprovider/facebook/authentication/FacebookAuthenticationProviderTest.java
@@ -0,0 +1,262 @@
+/**
+ * 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.
+ */
+package io.gravitee.am.identityprovider.facebook.authentication;
+
+import io.gravitee.am.common.exception.authentication.BadCredentialsException;
+import io.gravitee.am.common.oidc.StandardClaims;
+import io.gravitee.am.identityprovider.api.Authentication;
+import io.gravitee.am.identityprovider.api.AuthenticationContext;
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.identityprovider.api.common.Request;
+import io.gravitee.am.identityprovider.facebook.FacebookIdentityProviderConfiguration;
+import io.gravitee.am.identityprovider.facebook.FacebookIdentityProviderMapper;
+import io.gravitee.am.identityprovider.facebook.FacebookIdentityProviderRoleMapper;
+import io.gravitee.am.identityprovider.facebook.model.FacebookUser;
+import io.gravitee.common.http.HttpMethod;
+import io.gravitee.common.http.HttpStatusCode;
+import io.gravitee.common.util.LinkedMultiValueMap;
+import io.gravitee.common.util.MultiValueMap;
+import io.reactivex.observers.TestObserver;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.client.HttpResponse;
+import io.vertx.ext.web.client.impl.ClientPhase;
+import io.vertx.ext.web.client.impl.WebClientInternal;
+import io.vertx.reactivex.core.Vertx;
+import io.vertx.reactivex.ext.web.client.WebClient;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import static io.gravitee.am.identityprovider.facebook.model.FacebookUser.*;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FacebookAuthenticationProviderTest {
+
+    @Spy
+    protected WebClient client = WebClient.wrap(Vertx.vertx().createHttpClient());
+
+    @Mock
+    protected HttpResponse httpResponse;
+
+    @Mock
+    private FacebookIdentityProviderConfiguration configuration;
+
+    @Mock
+    private FacebookIdentityProviderMapper mapper;
+
+    @Mock
+    private FacebookIdentityProviderRoleMapper roleMapper;
+
+    @InjectMocks
+    private FacebookAuthenticationProvider cut;
+
+
+    @Before
+    public void init() {
+        ((WebClientInternal) client.getDelegate()).addInterceptor(event -> {
+
+            if (event.phase() == ClientPhase.SEND_REQUEST) {
+                // By pass send request and jump directly to dispatch phase with the mocked http response.
+                event.dispatchResponse(httpResponse);
+            }
+
+            event.next();
+        });
+    }
+
+    @Test
+    public void shouldGenerateSignInUrl() {
+
+        when(configuration.getUserAuthorizationUri()).thenReturn("https://www.facebook.com/v7.0/dialog/oauth");
+        when(configuration.getClientId()).thenReturn("testClientId");
+        when(configuration.getResponseType()).thenReturn("code");
+        when(configuration.getScopes()).thenReturn(Collections.emptySet());
+
+        Request request = cut.signInUrl("https://gravitee.io");
+
+        assertNotNull(request);
+        assertEquals(HttpMethod.GET, request.getMethod());
+        assertEquals("https://www.facebook.com/v7.0/dialog/oauth?client_id=testClientId&redirect_uri=https://gravitee.io&response_type=code", request.getUri());
+        assertNull(request.getHeaders());
+    }
+
+    @Test
+    public void shouldGenerateSignInUrl_withScopes() {
+
+        when(configuration.getUserAuthorizationUri()).thenReturn("https://www.facebook.com/v7.0/dialog/oauth");
+        when(configuration.getClientId()).thenReturn("testClientId");
+        when(configuration.getResponseType()).thenReturn("code");
+        when(configuration.getScopes()).thenReturn(new HashSet<>(Arrays.asList("scope1", "scope2", "scope3")));
+
+        Request request = cut.signInUrl("https://gravitee.io");
+
+        assertNotNull(request);
+        assertEquals(HttpMethod.GET, request.getMethod());
+        assertEquals("https://www.facebook.com/v7.0/dialog/oauth?client_id=testClientId&redirect_uri=https://gravitee.io&response_type=code&scope=scope1%20scope2%20scope3", request.getUri());
+        assertNull(request.getHeaders());
+    }
+
+    @Test
+    public void shouldAuthenticate() {
+
+        Authentication authentication = mock(Authentication.class);
+        AuthenticationContext authenticationContext = mock(AuthenticationContext.class);
+        when(authentication.getContext()).thenReturn(authenticationContext);
+
+        io.gravitee.gateway.api.Request request = mock(io.gravitee.gateway.api.Request.class);
+        when(authenticationContext.request()).thenReturn(request);
+
+        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
+        parameters.set("code", "facebookAuthorizationCode");
+
+        when(request.parameters()).thenReturn(parameters);
+        when(configuration.getCodeParameter()).thenReturn("code");
+        when(configuration.getClientId()).thenReturn("testClientId");
+        when(configuration.getClientSecret()).thenReturn("testClientSecret");
+        when(configuration.getAccessTokenUri()).thenReturn("https://graph.facebook.com/v7.0/oauth/access_token");
+        when(configuration.getUserProfileUri()).thenReturn("https://graph.facebook.com/v7.0/me");
+
+        when(authentication.getContext().get("redirect_uri")).thenReturn("https://gravitee.io");
+        when(httpResponse.statusCode())
+                .thenReturn(HttpStatusCode.OK_200)
+                .thenReturn(HttpStatusCode.OK_200);
+        when(httpResponse.bodyAsJsonObject())
+                .thenReturn(new JsonObject().put("access_token", "facebookGeneratedAccessToken"))
+                .thenReturn(new JsonObject().put(FacebookUser.ID, "facebookID")
+                        .put(FacebookUser.EMAIL, "email@facebook.com")
+                        .put(FacebookUser.NAME, "facebookName")
+                        .put(FacebookUser.LOCATION, new JsonObject().put("location", new JsonObject()
+                                .put(LOCATION_STREET_FIELD, "facebookStreet")
+                                .put(LOCATION_CITY_FIELD, "facebookCity")
+                                .put(LOCATION_REGION_FIELD, "facebookRegion")
+                                .put(LOCATION_ZIP_FIELD, 123456)
+                                .put(LOCATION_COUNTRY_FIELD, "facebookCountry")))
+                        .put(FacebookUser.BIRTHDAY, "10/24")
+                        .put(FacebookUser.ABOUT, "facebook about me"));
+
+
+        TestObserver<User> obs = cut.loadUserByUsername(authentication).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertValue(user -> {
+            assertEquals("facebookID", user.getId());
+            assertEquals("facebookID", user.getId());
+            assertEquals("facebookID", user.getAdditionalInformation().get(StandardClaims.SUB));
+            assertEquals("facebookName", user.getAdditionalInformation().get(StandardClaims.NAME));
+            assertEquals("facebookID", user.getAdditionalInformation().get(StandardClaims.PREFERRED_USERNAME));
+            HashMap<String, Object> address = (HashMap<String, Object>) user.getAdditionalInformation().get(StandardClaims.ADDRESS);
+            assertNotNull(address);
+            assertEquals("facebookStreet", address.get("street_address"));
+            assertEquals("facebookCity", address.get("locality"));
+            assertEquals("facebookRegion", address.get("region"));
+            assertEquals(123456, address.get("postal_code"));
+            assertEquals("facebookCountry", address.get("country"));
+            assertEquals("0000-10-24", user.getAdditionalInformation().get(StandardClaims.BIRTHDATE));
+            assertNotNull(user.getAdditionalInformation().get(FacebookUser.ABOUT));
+            return true;
+        });
+
+        verify(client, times(1)).postAbs("https://graph.facebook.com/v7.0/oauth/access_token");
+        verify(client, times(1)).postAbs("https://graph.facebook.com/v7.0/me");
+    }
+
+    @Test
+    public void shouldAuthenticate_badCredentials() {
+
+        Authentication authentication = mock(Authentication.class);
+        AuthenticationContext authenticationContext = mock(AuthenticationContext.class);
+        when(authentication.getContext()).thenReturn(authenticationContext);
+
+        io.gravitee.gateway.api.Request request = mock(io.gravitee.gateway.api.Request.class);
+        when(authenticationContext.request()).thenReturn(request);
+
+        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
+        parameters.set("code", "facebookAuthorizationCode");
+
+        when(request.parameters()).thenReturn(parameters);
+        when(configuration.getCodeParameter()).thenReturn("code");
+        when(configuration.getClientId()).thenReturn("testClientId");
+        when(configuration.getClientSecret()).thenReturn("testClientSecret");
+        when(configuration.getAccessTokenUri()).thenReturn("https://graph.facebook.com/v7.0/oauth/access_token");
+
+        when(authentication.getContext().get("redirect_uri")).thenReturn("https://gravitee.io");
+        when(httpResponse.statusCode())
+                .thenReturn(HttpStatusCode.UNAUTHORIZED_401);
+        when(httpResponse.bodyAsString())
+                .thenReturn("not authorized");
+
+
+        TestObserver<User> obs = cut.loadUserByUsername(authentication).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(BadCredentialsException.class);
+
+        verify(client, times(1)).postAbs("https://graph.facebook.com/v7.0/oauth/access_token");
+    }
+
+    @Test
+    public void shouldAuthenticate_profileBadCredentials() {
+
+        Authentication authentication = mock(Authentication.class);
+        AuthenticationContext authenticationContext = mock(AuthenticationContext.class);
+        when(authentication.getContext()).thenReturn(authenticationContext);
+
+        io.gravitee.gateway.api.Request request = mock(io.gravitee.gateway.api.Request.class);
+        when(authenticationContext.request()).thenReturn(request);
+
+        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
+        parameters.set("code", "facebookAuthorizationCode");
+
+        when(request.parameters()).thenReturn(parameters);
+        when(configuration.getCodeParameter()).thenReturn("code");
+        when(configuration.getClientId()).thenReturn("testClientId");
+        when(configuration.getClientSecret()).thenReturn("testClientSecret");
+        when(configuration.getAccessTokenUri()).thenReturn("https://graph.facebook.com/v7.0/oauth/access_token");
+        when(configuration.getUserProfileUri()).thenReturn("https://graph.facebook.com/v7.0/me");
+
+        when(authentication.getContext().get("redirect_uri")).thenReturn("https://gravitee.io");
+        when(httpResponse.statusCode())
+                .thenReturn(HttpStatusCode.OK_200)
+                .thenReturn(HttpStatusCode.UNAUTHORIZED_401);
+        when(httpResponse.bodyAsJsonObject())
+                .thenReturn(new JsonObject().put("access_token", "badFacebookGeneratedAccessToken"));
+        when(httpResponse.bodyAsString())
+                .thenReturn("not authorized");
+
+        TestObserver<User> obs = cut.loadUserByUsername(authentication).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(BadCredentialsException.class);
+
+        verify(client, times(1)).postAbs("https://graph.facebook.com/v7.0/oauth/access_token");
+        verify(client, times(1)).postAbs("https://graph.facebook.com/v7.0/me");
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/test/resources/logback.xml b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/test/resources/logback.xml
new file mode 100644
index 0000000000..945f2e19e7
--- /dev/null
+++ b/gravitee-am-identityprovider/gravitee-am-identityprovider-facebook/src/test/resources/logback.xml
@@ -0,0 +1,14 @@
+<configuration>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] [%X{domain}] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="io.gravitee" level="INFO"/>
+
+    <root level="ERROR">
+        <appender-ref ref="STDOUT"/>
+    </root>
+</configuration>
\ No newline at end of file