Search code examples
spring-bootspring-securityspring-security-oauth2spring-oauth2

Facebook Spring OAuth2User does not contain email


I'm trying to implement the signup phase with Facebook of my Spring webapp using Spring OAuth2. I'm following this guide https://www.callicoder.com/spring-boot-security-oauth2-social-login-part-2/ but something in my code does not work as expected. The object of type OAuth2User has just the id and the name of the facebook account inside the attributes.

In particular inside the @Service class CustomOAuth2UserService inside the method processOAuth2User

private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) {
        OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(oAuth2UserRequest.getClientRegistration().getRegistrationId(), oAuth2User.getAttributes());
        if(StringUtils.isEmpty(oAuth2UserInfo.getEmail())) {
            throw new OAuth2AuthenticationProcessingException("Email not found from OAuth2 provider");
        }
        ...
} 

oAuth2User.getAttributes(), as I said, has only two attributes: id and name but not the email.

My CustomOAuth2UserService class is this:

@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    @Autowired
    private UserService userService;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(oAuth2UserRequest);

        try {
            return processOAuth2User(oAuth2UserRequest, oAuth2User);
        } catch (AuthenticationException ex) {
            throw ex;
        } catch (Exception ex) {
            // Throwing an instance of AuthenticationException will trigger the OAuth2AuthenticationFailureHandler
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex.getCause());
        }
    }

    private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) {
        OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(oAuth2UserRequest.getClientRegistration().getRegistrationId(), oAuth2User.getAttributes());
        if(StringUtils.isEmpty(oAuth2UserInfo.getId())) {
            throw new RuntimeException("Id not found from OAuth2 provider");
        }

        FacebookUser user = null;
        try {
            user = (FacebookUser) userService.getByFacebookId(oAuth2UserInfo.getId());
        } catch (UserNotFoundException e) {
            user = registerNewUser(oAuth2UserRequest, oAuth2UserInfo);
        }

        return new CustomUserDetails(user);
    }

    private FacebookUser registerNewUser(OAuth2UserRequest oAuth2UserRequest, OAuth2UserInfo oAuth2UserInfo) {
        FacebookUser user = new FacebookUser(oAuth2UserInfo.getId());
        user.setProvider(AuthProvider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId()));
        user.setIdentity(new Identity(oAuth2UserInfo.getName()));
        user.setEmail(oAuth2UserInfo.getEmail());
        return (FacebookUser) userService.addFacebookUser(user);
    }

}

which is inserted in the SecurityConfiguration class:

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@EnableJpaRepositories(basePackageClasses = UserRepository.class)
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomOAuth2UserService customOAuth2UserService;

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .formLogin()
                    .loginPage("/login")
                    .failureUrl("/login?error=true")
                    .and()
                .oauth2Login()
                    .loginPage("/login")
                .userInfoEndpoint()
                    .userService(customOAuth2UserService);
       }
 }

Solution

  • I found the solution of this problem. I needed to specify the fields I was interested inside the properties file. So I needed to add inside the application.properties file the following properties:

    spring.security.oauth2.client.registration.facebook.scope=email,public_profile
    spring.security.oauth2.client.provider.facebook.authorizationUri = https://www.facebook.com/v3.0/dialog/oauth
    spring.security.oauth2.client.provider.facebook.tokenUri = https://graph.facebook.com/v3.0/oauth/access_token
    spring.security.oauth2.client.provider.facebook.userInfoUri = https://graph.facebook.com/v3.0/me?fields=id,first_name,middle_name,last_name,name,email,verified,is_verified,picture
    

    So my application.properties file, for what concerns the OAuth2 module is this:

    spring.security.oauth2.client.registration.facebook.client-id=****
    spring.security.oauth2.client.registration.facebook.client-secret=****
    spring.security.oauth2.client.registration.facebook.scope=email,public_profile
    spring.security.oauth2.client.provider.facebook.authorizationUri = https://www.facebook.com/v3.0/dialog/oauth
    spring.security.oauth2.client.provider.facebook.tokenUri = https://graph.facebook.com/v3.0/oauth/access_token
    spring.security.oauth2.client.provider.facebook.userInfoUri = https://graph.facebook.com/v3.0/me?fields=id,first_name,middle_name,last_name,name,email,verified,is_verified,picture
    

    With this online tool https://developers.facebook.com/tools/explorer/ you can automatically generate the url with all the field you need. Once you have it, you just have to paste it next to this property spring.security.oauth2.client.provider.facebook.userInfoUri and that's it.