spring-bootfacebook-graph-apispring-social

Tried accessing nonexisting field (context) on node type (User)


I am accessing connection.fetchUserProfile() from Connection<?> connection but it gives org.springframework.social.UncategorizedApiException: (#100) Tried accessing nonexisting field (context) on node type (User). This particular error never happen before until now.

maven :

<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-facebook</artifactId>
     <version>3.0.0.M3</version>
</dependency>

Any idea why is this happening?


Solution

  • I had the same issue and looked for a solution everywhere. Without any luck, I ended up writing a custom service that calls the Facebook Graph API and populates the UserProfile object.

    Add a new FBService.java class to your project:

    package net.attacomsian.services;
    
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.http.*;
    import org.springframework.social.connect.UserProfile;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.Collections;
    
    @Service
    public class FBService {
    
        private final RestTemplate restTemplate;
    
        public FBService(RestTemplateBuilder restTemplateBuilder) {
            this.restTemplate = restTemplateBuilder.build();
        }
    
        public UserProfile getProfile(String id, String accessToken) {
            try {
                //params
                String params = "fields=id,name,email,first_name,last_name&access_token=" + accessToken;
    
                //build url
                String url = "https://graph.facebook.com/v3.2/" + id + "?" + params;
    
                //create headers
                HttpHeaders headers = new HttpHeaders();
                headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
    
                // create request
                HttpEntity request = new HttpEntity(headers);
    
                //use rest template
                ResponseEntity<String> response = this.restTemplate.exchange(url, HttpMethod.GET, request, String.class);
    
                //check for status code
                if (response.getStatusCode().is2xxSuccessful()) {
                    JsonNode root =  new ObjectMapper().readTree(response.getBody());
    
                    // return a user profile object
                    return new UserProfile(root.path("id").asText(), root.path("name").asText(), root.path("first_name").asText(),
                            root.path("last_name").asText(), root.path("email").asText(), null);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
    
            return null;
        }
    }
    
    

    v3.2 is the Graph API version. Replace it with your own. Now inject this service to your controller and instead of calling:

    UserProfile profile = connection.fetchUserProfile();
    

    Call the new service getProfile() method like below:

    Profile profile = fbService.getProfile("me", connection.createData().getAccessToken());
    

    It works like a magic for me.

    Update: Here is the complete web controller code that shows how you can use the custom service for Facebook while keep using the default one for Google's user profile:

    @Controller
    public class AuthController {
    
        private FBService fbService;
        private final ProviderSignInUtils providerSignInUtils;
    
        public AuthController(FBService fbService,
                              ConnectionFactoryLocator connectionFactoryLocator,
                              UsersConnectionRepository connectionRepository) {
            this.fbService = fbService;
            this.providerSignInUtils = new ProviderSignInUtils(connectionFactoryLocator, connectionRepository);
        }
    
        @GetMapping("/social-signup")
        public String socialSignup(WebRequest request, HttpSession session) {
    
            Connection<?> connection = providerSignInUtils.getConnectionFromSession(request);
            if (connection == null) {
                return "redirect:/login";
            }
    
            //fetch user information
            UserProfile profile = null;
            if (connection.getKey().getProviderId().equalsIgnoreCase("google")) {
                profile = connection.fetchUserProfile();
            } else if (connection.getKey().getProviderId().equalsIgnoreCase("facebook")) {
                profile = fbService.getProfile("me", connection.createData().getAccessToken());
            }
    
            // TODO: continue doing everything else
        }
    }