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?
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
}
}