Pages

Showing posts with label Facebook. Show all posts
Showing posts with label Facebook. Show all posts

Thursday, January 25, 2018

Login into a web application with Facebook via redirect

Like Google, the Facebook documentation on login includes two options: using Facebook Javascript SDK and without it. I used the second option, manually building a login flow, to create a Javascript-free login flow for the back-end.

In my simple web application the flow is as follows:

  • When the login button is click, the user is redirected to the facebook page. The login button is inside an anchor tag:

    <a href="https://www.facebook.com/v2.12/dialog/oauth?client_id=407938939637716&redirect_uri=http://localhost:8080/test/facebook&scope=email&response_type=code&state=http://localhost:8080/test/|1462070876
    ">

    The url contains the following parameters:

    • client_id from the app's dashboard.
    • redirect_uri will receive the response from the Facebook login. The uri must be whitelisted in the App Dashboard.
    • state maintains state between the request and callback. It will be appended unchanged to the redirect_uri.
    • response_type:
      • code - the response data is included as URL parameters and contains code parameter. The data is accessible to the back-end.
      • token - the response data is included as a URL fragment and contains an access token. The response hash can be accesses only on the client by javascript.
    • scope - a list of Permissions to request from the person using your app. Note, even if you request the email permission it is not guaranteed you will get an email address. For example, if someone signed up for Facebook with a phone number instead of an email address, the email field may be empty.
  • After a login attempt, the browser is redirected to the redirect_uri with appended response paremeters code and state:

    http://localhost:8080/test/facebook?code=AQAlQPnW9Vbft-F8DW_ybmgjnk5fJ9ok7YiOglNDvougkpDUYyOC4D5gdhWQ4o54cYcVE9bihhvKjO6HRIQCXNIhLW6jUlaCvehwScSXbE9U3zDKBsbJo-uvMgPc9xJbzKAunmIdr3dDjJ72-SKqipvEYTkHtKksbVDEfbUR4DRL6ei8SQyf7A-8ULAGhZhJAgLsqfKYkqal2GzgbxtK3npvBS1OiWZFZvlGirHPbnOpM80EO5E7WDBqG7GsSR9c6lM6Xudehpo7U9OacW5h2XDwIuVTCFg3pNgtiptkotEimhBgigdgWTLFJJgYzFhYRrfj3O-ksbcAknd_nqUvYFfgewJo-5ejJ4HL1fGdzlrF-A&state=http%3A%2F%2Flocalhost%3A8080%2Ftest%2F%7C1462070876

    The state values in the original request and the response are the same. The code has to be included in a GET request to another endpoint. Additionally, client_id, redirect_uri used in the initial request, and client_secret from the App Dashboard are required.

    https://graph.facebook.com/v2.12/oauth/access_token?code=AQAlQPnW9Vbft-F8DW_ybmgjnk5fJ9ok7YiOglNDvougkpDUYyOC4D5gdhWQ4o54cYcVE9bihhvKjO6HRIQCXNIhLW6jUlaCvehwScSXbE9U3zDKBsbJo-uvMgPc9xJbzKAunmIdr3dDjJ72-SKqipvEYTkHtKksbVDEfbUR4DRL6ei8SQyf7A-8ULAGhZhJAgLsqfKYkqal2GzgbxtK3npvBS1OiWZFZvlGirHPbnOpM80EO5E7WDBqG7GsSR9c6lM6Xudehpo7U9OacW5h2XDwIuVTCFg3pNgtiptkotEimhBgigdgWTLFJJgYzFhYRrfj3O-ksbcAknd_nqUvYFfgewJo-5ejJ4HL1fGdzlrF-A&client_id=407938939637716&client_secret=7fd6fa037e097fcf002fb350b815f5b3&redirect_uri=http://localhost:8080/test/facebook

    The response is a Json containing an access_token.

    {"access_token":"EAAFzBKZBWT9QBABhRNBOKwQZBMza2siRzUZBeQHi0Jp2qrXijTEnbVijy4ZAjnTzBzigZA11Vh4RhZBZB2DDu1n8fEJBdBh5mxFETqJJFLLHQnkG0XyffthKg2tU62csiCvsCHxiVOMuZAWy2lS74sKLTbL8sC8EgLEZD","token_type":"bearer","expires_in":5181583}

    That the difference from Google sign in. In Google additionally id_token with the user's detail is received. In Facebook an additional request is required.

  • Using the received access token, the sever-side code makes yet another GET request retrieves a user email from the Graph API. The /me node is a special endpoint that translates to the user_id of the person whose access token is currently being used to make the API calls. Access tokens are portable. Graph API calls can be made from clients or from your server on behalf of clients. The calls to the Graph API are better secured by adding appsecret_proof parameter.

    https://graph.facebook.com/v2.12/me?access_token=EAAFzBKZBWT9QBABhRNBOKwQZBMza2siRzUZBeQHi0Jp2qrXijTEnbVijy4ZAjnTzBzigZA11Vh4RhZBZB2DDu1n8fEJBdBh5mxFETqJJFLLHQnkG0XyffthKg2tU62csiCvsCHxiVOMuZAWy2lS74sKLTbL8sC8EgLEZD&debug=all&fields=email&format=json&method=get&pretty=0&appsecret_proof=34b9499185c81a1f37a68cb3b012ae2dcb6882d8066dc58c88641247c28bbce9
    

    The response is a json:

    {"email":"dummy000@mail.ru","id":"10215579219697013","__debug__":{}}

    The retrieved email is used to log the user into the web application.

In App Dashboard I used such security settings:

The option below allows only the API calls that either include appsecret_proof or are made from the same device the token was issued.

Wednesday, January 24, 2018

HmacSHA256 - sha256 hash using a key (for appsecret_proof in Facebook)

Unlike Google, in Facebook access tokens are portable - they can be used without client or app id. To kind of protect or rather label them, all Graph API calls from a server (only server) should secured by adding a parameter appsecret_proof set to the sha256 hash of the access token generated using the app secret as the key.

Here is an example of how to do it on a Java server:

public class Sha256Digest {

    Mac mac;
     
    Sha256Digest() throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
        this(APP_SECRET);
    }

    Sha256Digest(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
        SecretKeySpec sk = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8.toString()), "HmacSHA256");
        mac = Mac.getInstance("HmacSHA256");
        mac.init(sk);
    }

    String hash(String msg) throws UnsupportedEncodingException {
        return HexConverter.bytesToHex(mac.doFinal(msg.getBytes(StandardCharsets.UTF_8.toString())));
    }

    public static void main(String[] args) throws Exception {
        System.out.println(new Sha256Digest().hash("Test"));
    }
}

For converting an array of bytes into a string of hexadecimal values I use an additional class:

public class HexConverter {

    private final static char[] HEXARRAY = "0123456789abcdef".toCharArray();

    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEXARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEXARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }
}

The resulting string can be included in Facebook calls like:

https://graph.facebook.com/v2.11/me?access_token=EAAFzBKZBWT9QBAFXEDBfdKu8Q7cDXZAWXSaZAIKuDZB04A5mAlCTMpXKgBJNd42MXZAo5Gk8ZAv8u6mjCXGLfLTjT6ORikMLWOCFTxbaxHqcOfpJU7iGIyk5xKozSv0HG4ctm0wpE2xHriZCeITEQZAKWbHoveuj2xbGSBvdPhE8uX5HXtEdgUkc82XNZAuQSLi8ZD&debug=all&fields=email&format=json&method=get&pretty=0&appsecret_proof=734e6e019eb20821682797320845f1df2e813f01cc779cbcd94bb55a9a37457f

To prevent API calls lacking the proof, Require App Secret switch should be activated in the application settings. Only allow calls from a server and require app secret or app secret proof for all API calls.

Using JSON-P to parse heterogeneous JSON in HTTP responses

Suppose you need to query Facebook Graph API. The responses to your HTTP requests have JSON format. A very convinient Java API for JSON Processing helps to parse and query the heterogeneous JSON responses.

For example, I try to get an email of the user whose access token was obtained after the user's login into my application. For this, I access url like:

https://graph.facebook.com/v2.11/me?access_token=EAAFzBKZBWT9QBAHzWcGGSy5GepjlS9S1YEPvN1p2jwaGxc0QZCaVoAZCmsZB8YaE1AkegbmObdBDY64DDD1t1kxezOgpEFKbbLKlyQyPcEiyUZCwSI3iJOhe9ioahZA9Ye6hvOybhzGeOODFdihEnPbuw5sso5CzPEZAQL1RkdM3cfKajOdKsPmMWOvNhrDtE0ZD&debug=all&fields=email&format=json&method=get&pretty=0

The reponse is a JSON with some hexadecimal digits encoding @ character:

{"email":"marian.caikovski\u0040mail.ru","id":"10215579219697013","__debug__":{}}

To easily execute an HTTP request, parse the response and get the decoded email property I use:

String readUserEmailFromGraphAPI(String token) throws IOException {
    try (JsonReader jsonReader = Json.createReader(
            new InputStreamReader(
                    new URL("https://graph.facebook.com/v2.11/me?access_token=" + token + "&debug=all&fields=email&format=json&method=get&pretty=0")
                            .openStream()))) {
        JsonObject obj = jsonReader.readObject();
        return obj.getString("email");
    }
}