go to part...
Database
A single user may have multiple social media accounts (a Facebook account, a Twitter account, an Instagram account, and so on). Let's create a separate database table to store information about all of the user's accounts. Run php artisan make:model SocialAccount -m
to create a model with a related migration file. Edit the migration file as follows:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSocialAccountsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('social_accounts', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->string('provider_id');
$table->string('provider');
$table->string('token');
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('CASCADE');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('social_accounts');
}
}
user_id
references a user the account belongs to, provider_id
is the id of the account as provided by a social network site, provider
is the name or identifier of a social network, and token
is the authentication token we receive after successful user authentication. We don't really need the token as we're not going to perform further requests using this token after the authentication, but we'll keep it just in case. Migrate the changes by running php artisan migrate
.
Finally, open the app/SocialAccount.php
model and add the user
relationship. Also remove the mass assignment check by assigning an empty array to the $guarded
property.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class SocialAccount extends Model
{
protected $guarded = [];
public function user()
{
return $this->belongsTo('App\User');
}
}
API Key and Facebook Settings
Now we need to register our app with Facebook and get all the required keys. Go to Facebook for Developers and add a new app. Then go to app's basic settings page and add your website's domain. Then click + Add Platform
, choose Website
and enter the URL of your website. I'm just using the artisan's inbuilt server, so my domain is localhost
and the URL is http://localhost:8000
. Save changes.
Open config/services.php
and add this at the end of the config file:
'facebook' => [
'client_id' => '',
'client_secret' => '',
'redirect' => '/social-auth/facebook/callback',
],
Copy and paste client_id
and client_secret
from the Settings
page (they're called App ID
and App Secret
at Facebook respectively).
Now go back to the browser and navigate to Product -> Facebook Login -> Settings
. Add "http://localhost:8000/social-auth/facebook/callback" to the Valid OAuth redirect URIs
field. Change localhost:8000
to your actual domain. Save changes.
Routes
Social networks' APIs use OAuth. We need to add 2 routes to handle that process. Open routes/web.php
and add these routes:
Route::get('/social-auth/{provider}', 'Auth\SocialController@redirectToProvider')->name('auth.social');
Route::get('/social-auth/{provider}/callback', 'Auth\SocialController@handleProviderCallback')->name('auth.social.callback');
We're using wildcarded routes to support multiple social networks.
Views
Add a link to the Facebook authentication somewhere into your views. We'll be using the same route for registering and logging in users (so I'd use a Blade partial). Our link will look something like this (I'm using the Font Awesome icons):
<a href="{{ route('auth.social', 'facebook') }}" title="Facebook">
<i class="fa fa-2x fa-facebook-square"></i>
</a>
Controller
Time to write the core of the authentication system. Let's create our controller by running php artisan make:controller 'Auth\SocialController'
. First we'll create the method that will redirect users to the Facebook's authentication page.
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Laravel\Socialite\Facades\Socialite;
class SocialController extends Controller
{
public function redirectToProvider($provider)
{
return Socialite::driver($provider)->redirect();
}
}
Open your website and check the Facebook button - the redirect should work now. Click the button on the Facebook's prompt and you will be redirected back to our site to the /social-auth/facebook/callback
path. We still don't have a controller method for this route, so you'll get an error. Let's create it,
public function handleProviderCallback($provider)
{
$user = Socialite::driver($provider)->user();
}
Here we can get the basic information about the user from Facebook. See how easy it is with Socialite? You can add dd($user);
and see what information is being returned yourself. Next we need to create a new user or find an existing one in our database based on the data we received. Then we'll log this user in and redirect them to an appropriate page.
public function handleProviderCallback($provider)
{
$socialiteUser = Socialite::driver($provider)->user();
$user = $this->findOrCreateUser($provider, $socialiteUser);
auth()->login($user, true);
return redirect('/');
}
Change the redirect path to whatever you need. Don't pass the second parameter true
to the login()
method if you don't want your app to "remember" the user.
Now we need to create the findOrCreateUser()
method. Inside we'll perform a few steps. First, we'll try to find an existing user in the social_accounts
table based on the Facebook profile id we received. In case of failure we'll try to find a user by the email in the users
table. If that also fails - we'll create (or "register") a new user.
public function findOrCreateUser($provider, $socialiteUser)
{
if ($user = $this->findUserBySocialId($provider, $socialiteUser->getId())) {
return $user;
}
if ($user = $this->findUserByEmail($provider, $socialiteUser->getEmail())) {
$this->addSocialAccount($provider, $user, $socialiteUser);
return $user;
}
$user = User::create([
'name' => $socialiteUser->getName(),
'email' => $socialiteUser->getEmail(),
'password' => bcrypt(str_random(25)),
]);
$this->addSocialAccount($provider, $user, $socialiteUser);
return $user;
}
The logic of the method should be clear. If a user was registered with the same social network - they will be just logged in. If a user was registered with a different social network, but with the same email - a new social account of the user will be added to the database. If neither user's social accounts nor email were found in the database - a new user will be created.
Here are the remaining methods. Hopefully, no explanation is required here.
public function findUserBySocialId($provider, $id)
{
$socialAccount = SocialAccount::where('provider', $provider)
->where('provider_id', $id)
->first();
return $socialAccount ? $socialAccount->user : false;
}
public function findUserByEmail($provider, $email)
{
return User::where('email', $email)->first();
}
public function addSocialAccount($provider, $user, $socialiteUser)
{
SocialAccount::create([
'user_id' => $user->id,
'provider' => $provider,
'provider_id' => $socialiteUser->getId(),
'token' => $socialiteUser->token,
]);
}
Important Tweak
Note that some users don't have emails associated with their social network profiles because they registered using their phone number alone. Most social networks, including Facebook, don't give access to users' phone numbers via API. In this case it is impossible to link two social network accounts together and instead two different users will be created on your website.
We need to modify our code to comply with this. $socialiteUser->getEmail()
will return null if a user has no email. That's fine when we're creating a new user, except you need to replace ->unique()
with ->nullable()
at the email
field of the users
table in the migrations.
On the other hand, the findUserByEmail()
method will break. It will just return us the first user without an email it finds instead of null
. So let's check if the provided $email
is null in the first place and perform the search only if it's not. Modify the method like this:
public function findUserByEmail($provider, $email)
{
return !$email ? null : User::where('email', $email)->first();
}
Adding Other Social Networks
Adding other social networks is easy. Just add the required keys to the config/services.php
file and a link to your views. Refer to the Laravel documentation if you have questions left. Socialite supports a limited number of providers, but luckily there are community maintained providers at the Socialite Providers website.
All the materials at voerro are absolutely free and are worked on in the author's spare time. If you found any of the tutorials helpful, please consider supporting the project. Thank you!