This class exposes the public method getAuthToken which takes a user's Facebook OAuth token as an input parameter and queries Facebook for information about it - such as the application it is valid for, its expiry date, the user it belongs to etc.

The flow for this method is as follows:

  • Uses the token passed into the method to construct a URL to be read from
  • Reads the JSON response from Facebook into a String
  • Deserializes this String into a new FacebookAuthToken object
  • Returns this object

Once this object is returned it is possible to use the various public getter methods I have provided to query its attributes.

The main thing I don't like about this solution is that it uses two top-level classes - the AuthTokenDeserializer class as well as the primary FacebookAuthToken class. I tried to nest AuthTokenDeserializer within FacebookAuthToken but the compiler complained about trying to create a new AuthTokenDeserializer from within getAuthToken, which is a static context. Fair enough, I suppose.

Additionally I am not over the moon that getAuthToken throws an IOException - because now every invocation of this method within my application has to catch it. Perhaps it is better to catch this exception within the method and simply return null?

Apart from these issues, are there any other inefficiencies or elements of poor design in my code? I am new to Java so try to challenge everything I do (so that I get better).

class FacebookAuthToken {

    private static final String APP_ID = "99999999999999";

    // TODO: Refresh APP_TOKEN before Go-Live
    private static final String APP_TOKEN = "999999999999";

    private static final String URL_PLACEHOLDER =
            "https://graph.facebook.com/debug_token?input_token=userToken&access_token=accessToken";

    private static final String ACCESS_TOKEN = APP_ID + "|" + APP_TOKEN;

    private String app_id;
    private String application;
    private int expires_at;
    private boolean is_valid;
    private int issued_at;
    private String[] scopes;
    private String user_id;

    public static FacebookAuthToken getAuthToken(String userToken) throws IOException {

        URL url = new URL(buildAuthTokenURL(userToken));
        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
        StringBuilder builder = new StringBuilder();
        String line;

        while ((line = reader.readLine()) != null) {
            builder.append(line).append("\n");
        }
        reader.close();
        String myJson = builder.toString();

        Gson gson = new GsonBuilder().registerTypeAdapter(FacebookAuthToken.class,
                new AuthTokenDeserializer()).create();

        FacebookAuthToken authToken = gson.fromJson(myJson, FacebookAuthToken.class);

        return authToken;
    }

    public String getAppId() {
        return this.app_id;
    }

    public String getApp() {
        return this.application;
    }

    public int getExpiry() {
        return this.expires_at;
    }

    public boolean isValid() {
        return this.is_valid;
    }

    public int getCreated() {
        return this.issued_at;
    }

    public String[] getPermissions() {
        return this.scopes;
    }

    public String getUserID() {
        return this.user_id;
    }

    private static String buildAuthTokenURL(String userToken) {
        String result = URL_PLACEHOLDER.replaceAll("userToken", userToken);
        return result.replaceAll("accessToken", ACCESS_TOKEN);
    }
}
/* Example response to be deserialized by AuthTokenDeserializer:
{
   "data": {
      "app_id": "999999999999",
      "application": "MyApp",
      "expires_at": 1455462874,
      "is_valid": true,
      "issued_at": 1450278874,
      "scopes": [
         "public_profile"
      ],
      "user_id": "999999999999999"
   }
}
 */

class AuthTokenDeserializer implements JsonDeserializer<FacebookAuthToken> {

    @Override
    public FacebookAuthToken deserialize(JsonElement jsonElement, Type type,
                                         JsonDeserializationContext context) {

        JsonElement data = jsonElement.getAsJsonObject().get("data");
        return new Gson().fromJson(data, FacebookAuthToken.class);
    }
}
up vote 6 down vote accepted

These values would probably be better off in a configuration file or something - not embedded and compiled with the executable:

private static final String APP_ID = "99999999999999";

// TODO: Refresh APP_TOKEN before Go-Live
private static final String APP_TOKEN = "999999999999";

private static final String URL_PLACEHOLDER =
        "https://graph.facebook.com/debug_token?input_token=userToken&access_token=accessToken";

This means the ACCESS_TOKEN should probably be set in a dedicated method:

private static final String ACCESS_TOKEN = APP_ID + "|" + APP_TOKEN;

I'd probably extract these app-config fields into their own app-config class, and have the FacebookAuthToken class take a dependency on it.


Additionally I am not over the moon that getAuthToken throws an IOException - because now every invocation of this method within my application has to catch it. Perhaps it is better to catch this exception within the method and simply return null?

Absolutely! You'll want to perhaps log the exception details, but the client code only needs to care about whether or not they can get an authentication token.

  • Thanks Mat('s Mug). Is retuning null the standard way to signal to the client code that an error has occurred? – sav0h Dec 18 '15 at 11:34

Single responsibility principle

FacebookAuthToken does one too many things:

  • download a URL
  • create a token

It would be good to move the download logic to a separate method, a private helper.

Working with resources

Opening a reader and closing it manually is old fashioned and error prone. Use the modern try-with-resources syntax.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.