OAuth with Facebook

Now that Brent has TopCluck up and running he can finally challenge Farmer
Scott to an all out egg counting brawl. Brent knows that if they are both
tracking egg collections on TopCluck Farmer Scott will be proven wrong and
everyone will see how awesome his hens really are.

But what fun is winning if no one else gets to see? So Brent hatches another
idea: having users share their chicken-laying progress on Facebook.

Fortunately, Facebook uses OAuth 2.0 for their API, so we're already dangerous.
And like a lot of sites, they even have a PHP library to help us work with
it. Installing it via Composer is easy. In fact, I already added it to our
composer.json, so the library is downloaded and ready to go:

{
"require": {
...
"facebook/php-sdk": "~3.2.3"
}
}

The library has a simple example, but it's easier to see it integrated in
a real application.

We're going to integrate with Facebook using the same authorization code
grant type we just used with COOP. So it shouldn't be any surprise that we
need the same 2 pages as before: 1 that redirects to Facebook and 1 that
handles things after Facebook redirects back to us.

In fact, if you open up FacebookOAuthController.php, you'll see that
I've started us with a setup that looks exactly like we had with COOP.

When we click this, we hit the code in the first function. Just like before,
our job is to redirect to the authorize URL on Facebook. If we dig a little
bit on Google, we can see this is /dialog/oauth. We could start building
this by hand, but the Facebook SDK can help us out.

If we look at their simple usage example of the PHP SDK, we can see how to
create the Facebook object. Copy this into the code for our page:

Now, to get the authorize URL, we can use the getLoginUrl() function on
the SDK. Remember that this URL always has 3 important things on it: the
client ID, the redirect URI back to our site and the list of scopes we need.
The object already has our client ID, so lets pass the redirect URI and scopes
here. For Facebook, these are called redirect_uri and scope:

To know which scopes you need, you have to check with the API you're using.
If we google about Facebook API scopes, we find a page that explains all
of them. We'll ultimately want to be able to get basic user information and
post to a user's timeline. These are email and publish_actions.

Finally, let's redirect the user to this URL. The flow should feel completely
familiar by now:

When we try it out, we do go to Facebook's /dialog/oauth with the client_id,
redirect_uri and scope parameters. But we get an error:

Given URL is not allowed by the Application configuration. One or more
of the given URLs is not allowed by the App's settings. It must match
the Website URL or Canvas URL, or the domain must be a subdomain of one
of the App's domains.

It's complaining about the redirect URL we're sending. For added security,
OAuth servers allow, and sometimes require you to configure your redirect
URL in your application. Go back to our application and click Settings and
then "Add Platform". Choose "Website" and then fill in the URL of your site.

Tip

Facebook likes to change their interface, so this may look different
someday soon! But one way or another, you're looking for a way to register
your redirect URL.

And just like that, when we try it again, it works. Facebook made us do that
so that no other sites can try to use our app id and have Facebook redirect
back to some other domain. COOP's application settings also have this ability,
but it wasn't required, so we skipped it. But, it's always better to fill
this in.

At the authorize URL, Facebook describes the scopes that we're asking for,
including the ability to post. One nice thing about Facebook is that we can
choose to grant this scope, but make any posts show only to us. That's a
great way to test things.

OAuth tells us that our next step is to make an API request to the token
endpoint to exchange our authorization code for an access token. That's absolutely
right, and it can be done with the help of the SDK:

When we try the process again, we get a valid-looking user id. So, what just
happened?

The getUser() method does a whole lot more than it looks like. It actually
looks for the code query parameter and makes the API request to get the
access token automatically! This is awesome, but it's also magic! If you
can keep in mind how OAuth works and what's happening behind the scenes at
each step, you'll be in great shape when something goes wrong.

Just like with COOP, we need to handle failure. If we're missing the authorization
code or something else goes wrong behind the scenes, the getUser() method
will return 0. Let's use that to render the error template:

When something does go wrong, Facebook will redirect back to us with information
about what went wrong on the standard error and error_description
query parameters. Because they're following this OAuth standard, we can easily
find error details and even decide what to do next. For example, if the error
is set to access_denied, then it means the user denied our authorization
request. In our app, I'm just passing all of the query parameters into a template
that will display them.

To try this, we first need to go to Facebook and remove the app from our
account. Unlike COOP, most OAuth servers remember if you authorized an app
and don't ask you again.

On TopCluck, click "Connect with Facebook" again but "Cancel" the authorization
request. After the redirect, we see the error, error_description and
error_reason query parameters. But instead of seeing the error template,
our valid userId is printed out as if it were successful. What just happened?

Our OAuth flow did fail. But even still, the Facebook object looks and
finds a valid access token that it stored in the session from the last, successful
authorization. That's nice, but it's unexpected. Just remember that
getUser() tries many things: like exchanging the authorization code for
an access token or simply finding an access token that it already stored
in the session.

To see the error page, clear out your session cookie to reset everything.
Log back in, then connect with Facebook but deny the request again. Oh Cluck!
Error page! Without any session data to fall back on, the Facebook object
doesn't have an access token and so can't make an API request to get the user
id.

And of course, let's redirect back to the homepage after finishing. Try
the whole cycle out - this time approving our application's authorization
request. We now know that a lot is happening behind the scenes.

First, the Facebook object exchanges the authorization code for an access
token and saves it in the session. This all happens when we call getUser().
Next, we save the Facebook user ID into the database and redirect to the
homepage. Clicking the "User Info" box shows us the Facebook ID.

So why aren't we storing the access token or expiration? Actually, this is
up to you. The Facebook object is automatically storing the access token
in the session. So, everything is easy right now.

But on the user's next session, the access token will be gone and we'll need
to re-ask the user to authorize. If you want to avoid this, you could store
the Facebook access token in the database. In a second, I'll show you how
you'd use that access token. Of course, these tokens don't last forever, so
eventually you'll need to re-authorize them or use a Using Refresh Tokens,
the topic of an upcoming chapter!

Leave a comment!

2018-01-17Diego Aguiar

Exactly, and you can even create a test application inside your production application. BTW, pollos.

Cheers!

2018-01-17Ariel Rodríguez

Nevermind, I should have been using localhost and not the IP.

2018-01-17Ariel Rodríguez

Hi, I followed the tutorial so far and it's great but I need some advice here.

When I try to add my domain (http://192.168.10.10:9001/) to facebook, it won't let me save it and this is the error I get: