3

I am developing an app whose frontend is written using React.js and the backend REST API is written using the Spring framework. I wanted to add social logins to my website, so after days of googling and research, I understood that OAuth2 is the solution. I came to know that the frontend should handle getting the authorization token from the Resource Server(Facebook here) and my backend(java) should validate that token and connect with Facebook to get an access token. Then that access token should be stored in my database along with the user details(e.g email).

Here is my requirement, once the user clicks on the "Continue with Facebook" button, my app should create there account in my own database using details - email and Name(the signup feature). And later whenever they click on this button again, they will be logged in not sign up. The way other websites handle it.

As of now, I have the button working in my app, which brings me the authorization token from Facebook.

Can someone please guide me the path I should follow here.

Also, any special attention to some error handling I should follow.

The Coder
  • 3,447
  • 7
  • 46
  • 81
  • Spring has the project for Facebook auth with Spring Security integration [Reference](https://docs.spring.io/spring-social-facebook/docs/current/reference/htmlsingle/) – WildDev Dec 20 '18 at 16:51
  • @WildDev Am I right with my findings? As I am fetching the authorization token from frontend, is it the right way? Or should I do everything at backend? Doing everything at backend create problem in redirects, as it's a REST api. – The Coder Dec 21 '18 at 06:05
  • It's rather about backend configuration. «Continue with Facebook» is native form submitter button by default. The submission target is Spring Security endpoint and it answers with external redirect to social network bypassing front-end logic. The authentication is done then user is redirected back on predefined return url. Starting from this point, `Authentication` object contains the user data such as social ID etc. – WildDev Dec 21 '18 at 18:44
  • 1
    @WildDev Consider this scenario - user clicks on the «Continue with Facebook» button, the button talks to the backend API endpoint `/auth/facebook`. The Spring security connects with the Facebook and then **1.)** if the user is not logged in, the facebook redirects to login page **2.)** for the first time, user need to give access to my app, this also requires redirection to fb urls. My confusion is, how this redirection is handled for a REST API. As, on browsers(frontend) it's simple. – The Coder Dec 21 '18 at 19:13
  • I'm not sure if `/auth/facebook` endpoint supports async requests which are required by `REST` architecture. The one most often seen in the documentation uses synchronized requests with following browser redirection. There's no requirement to handle it manually on front-end side. – WildDev Dec 21 '18 at 20:11
  • @TheCoder have u find any solution ??.. I have same confusion – ASHISH SHARMA May 12 '19 at 21:36

1 Answers1

8

Here's the general approach using Spring Boot as a REST API backed by Spring Data JPA and Spring Security that works for iOS and ember.js together. There's probably libraries and what not that you can use but I'm just going to outline the fundamental flow.

  1. Your user object needs a one to one mapping to a facebook account. Best practice would involve encrypting the authToken before storing in the DB
@Entity
class FacebookAccount {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    Long id

    String facebookUserId
    String authToken

    @OneToOne
    @JoinColumn(name="user_id")
    User user
}
@Entity
class User{

...
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    FacebookAccount facebookAccount
}
  1. Use the facebook Javascript SDK to get a User Access Token and the User's Facebook User ID. You'll get a response back from facebook in your react app that looks like this in the successful case:
{
    status: 'connected',
    authResponse: {
        accessToken: '...',
        expiresIn:'...',
        reauthorize_required_in:'...'
        signedRequest:'...',
        userID:'...'
    }
}
  1. Hit some login endpoint with the info received in step 2 like /login/facebook. I cannot predict how your app is structured. In my app, this code is handled by my Authentication Filter that implements GenericFilterBean. I pass a header X-Auth-Facebook with the token.

  2. Verify the token. I'm doing this in a class that implements AuthenticationProvider within the Authentication authenticate(Authentication authentication) throws AuthenticationException method. This class will need your App's Access Token accessToken and the user's Token userAccessToken:

URIBuilder builder = URIBuilder.fromUri(String.format("%s/debug_token", "https://graph.facebook.com"))
builder.queryParam("access_token", accessToken)
builder.queryParam("input_token", userAccessToken)
URI uri = builder.build()
RestTemplate restTemplate = new RestTemplate()

JsonNode resp = null
try {
    resp = restTemplate.getForObject(uri, JsonNode.class)
} catch (HttpClientErrorException e) {
    throw new AuthenticationServiceException("Error requesting facebook debug_token", e)
}

Boolean isValid = resp.path("data").findValue("is_valid").asBoolean()
if (!isValid)
    throw new BadCredentialsException("Token not valid")

String fbookUserId = resp.path("data").findValue("user_id").textValue()
if (!fbookUserId)
    throw new AuthenticationServiceException("Unable to read user_id from facebook debug_token response")

// spring data repository that finds the FacebookAccount by facebook user id
FacebookAccount fbookAcct = facebookAccountRepository.findByFacebookUserId(fbookUserId)
if(!fbookAcct){
    // create your user here
    // save the facebook account as well
} else{
  // update the existing users token
  fbookAcct.authToken = userAccessToken
  facebookAccountRepository.save(fbookAcct)
}
// finish the necessary steps in creating a valid Authentication

I, personally, then create a token that my client's use when accessing my API (rather than have them continue to pass the facebook token with all requests).

I also need more user provided information to create the user (a chosen username, agreeing to terms and conditions, etc). So my actual implementation throws an EntityNotFoundException instead of creating the user, which my clients then use to pop up a registration form that provides only the fields I cannot get from facebook. On submit of this from the client, I hit my /signup/facebook endpoint with the facebook token and what's needed to create my user. I fetch the profile from facebook and create the user (automatically logging them in the process).

Edit: If you want to use Spring 0Auth, you could follow the example for creating a Spring 2 Oauth Rest Template

@Bean
public OAuth2ProtectedResourceDetails facebook() {
    AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
    details.setId("facebook");
    details.setClientId("233668646673605");
    details.setClientSecret("33b17e044ee6a4fa383f46ec6e28ea1d");
    details.setAccessTokenUri("https://graph.facebook.com/oauth/access_token");
    details.setUserAuthorizationUri("https://www.facebook.com/dialog/oauth");
    details.setTokenName("oauth_token");
    details.setAuthenticationScheme(AuthenticationScheme.query);
    details.setClientAuthenticationScheme(AuthenticationScheme.form);
    return details;
}

@Bean
public OAuth2RestTemplate facebookRestTemplate(OAuth2ClientContext clientContext) {
    OAuth2RestTemplate template = new OAuth2RestTemplate(facebook(), clientContext);
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,
            MediaType.valueOf("text/javascript")));
    template.setMessageConverters(Arrays.<HttpMessageConverter<?>> asList(converter));
    return template;
}

and then in use:

public String photos(Model model) throws Exception {
        ObjectNode result = facebookRestTemplate
                .getForObject("https://graph.facebook.com/me/friends", ObjectNode.class);
        ArrayNode data = (ArrayNode) result.get("data");
        ArrayList<String> friends = new ArrayList<String>();
        for (JsonNode dataNode : data) {
            friends.add(dataNode.get("name").asText());
        }
        model.addAttribute("friends", friends);
        return "facebook";
    }

I took the above request for friends from the project. it shouldn't be hard to tailor the above code I showed with debug_token to use the Spring OAuth rest template. Hope this helps :)

mistahenry
  • 8,554
  • 3
  • 27
  • 38
  • is there any way to validate facebook authorization token by spring oauth2 plugin?? – ASHISH SHARMA May 13 '19 at 10:06
  • I've never used spring oauth2 but when looking at the [docs](https://projects.spring.io/spring-security-oauth/docs/oauth2.html), there's a whole section on being an `OAuth 2.0 Client`. See [here](https://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/client/OAuth2RestTemplate.html) or their [example](https://github.com/spring-projects/spring-security-oauth/blob/8eedb354ae0015e0f33f0c3d958d9ac84d010721/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/FacebookController.java) – mistahenry May 13 '19 at 11:49
  • @ASHISHSHARMA does this not adequately answer your question? – mistahenry May 16 '19 at 15:41