Introduction
One of the main goals in the modern web is to make the user experience as simple as possible, for instance instead of filling all those boring forms to complete a basic registration, the user can sign up with one simple click.
That's why most websites include social media authentication. We will be using both Laravel 5.6 and Socialite, you can the check the documentation for more details on : Socialite Docs Now let's say you have a website with 2 types of users: student and teacher. Each one with it's own register form. Based on the submitted form we know the user type and store it in the database, for the oauth api that would mean that for each one there is a separate method hence callback url from the provider. That's what i will demonstrate here : how to use social media provider with multiple callback url for each user.
Application Setup
Let's start by creating our application using
Bashlaravel new SocialMediaAuth
If you started fresh like i did you will need to generate key for you application using
Bashphp artisan key:generate
Database Migration
We will stick with the default 'User' model Now we need to customize our User table before we migrate it
User Migrationpublic function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('firstName');
$table->string('lastName');
$table->string('type');
$table->string('email')->unique();
$table->string('password')->nullable();
$table->string('provider');
$table->string('provider_id');
$table->rememberToken();
$table->timestamps();
});
}
next go ahead to .env file and enter your database credentials
.envDB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=SocialMediaAuth
DB_USERNAME=root
DB_PASSWORD=123
Now that everything is setup we can go ahead and migrate to our database
Bashphp artisan migrate
Models Setup
After setting up the migration we must include the attributes in the mass assignments array in our User model
User.phpprotected $fillable = [
'firstName', 'lastName', 'type', 'email', 'password', 'provider', 'provider_id',
];
Including Socialite
Obvoiusly we need to require the package to work with it, if you're using laravel 5.6 all you need to do is run this command in your CLI
Bashcomposer require laravel/socialite
In terms of providers we'll use google and facebook, feel free to use any other provider they're all pretty much the same, just make sure they support multiple callback url's for example GitHub oauth doesn't support that, for the others you should be good to go. Now add the credentials for the OAuth services in our config/services.php file
config/services.php'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('FACEBOOK_CALLBACK_STUDENT'),
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_CALLBACK_STUDENT'),
],
By default we 'll set the student callback as the the one by default, later we'll change depending on the submitted form. We must now set the keys that we obtain after creating the app in the .env file, make sure the name is the same as the one in services.php file.
.envFACEBOOK_CLIENT_ID={{API Key}}
FACEBOOK_CLIENT_SECRET={{API secret}}
FACEBOOK_CALLBACK_STUDENT={{API CALLBACK URL FOR STUDENT}}
FACEBOOK_CALLBACK_TEACHER={{API CALLBACK URL FOR TEACHER}}
GOOGLE_CLIENT_ID={{API Key}}
GOOGLE_CLIENT_SECRET={{API secret}}
GOOGLE_CALLBACK_STUDENT={{API CALLBACK URL FOR STUDENT}}
GOOGLE_CALLBACK_TEACHER={{API CALLBACK URL FOR TEACHER}}
as you can see here and as I explained before for each provider there is 2 callback url one for student and the other one for teacher. The callback url should be something similar to : http://localhost:8000/{user_type}/{provider_type}/callback
Creating Social Media Api
In order to obtain an api key and secret you need to go and create a new application one for Facebook and another for Google+ API it's pretty straight forward, first create a project then select the type of application you want to integrate after that fill out the form then get the keys and past them in your .env file
Routing
Now we will cover our entire application routing system as you can see in the code snippet down below we will have : - home page where we can navigate our application - Sign Up form for each type of users - Redirection link to the provider based on each user type - Callback URL from the provider.
web.phpRoute::view('/', 'welcome');
Route::view('register-teacher', 'register_teacher');
Route::post('register-teacher', 'AuthController@registerTeacherStore');
Route::view('register-student', 'register_student');
Route::post('register-student', 'AuthController@registerStudentStore');
Route::get('oauth/{type}/{provider}', 'AuthController@RedirectToProvider');
Route::get('{type}/{provider}/callback', 'AuthController@ProviderCallback');
Route::get('users', 'AuthController@usersDisplay');
Authenticationn Medthod Controller
Let's make the AuthController that will contain our authentication methods
Bashphp artisan make:controller AuthController
Then we must inclue the User model and Socialite in order to use them in the controller methods
AuthController.phpuse App\User;
use Socialite;
First let's cover the normal form registration for both users
AuthController.phppublic function registerTeacherStore()
{
$this->validate(request(),[
'firstName' => 'required|string',
'lastName' => 'required|string',
'email' => 'required|email|unique:users,email',
'password' => 'required|string',
]);
User::create([
'firstName' => request('firstName'),
'lastName' => request('lastName'),
'type' => 'Teacher',
'email' => request('email'),
'password' => bcrypt(request('password')),
'provider' => 'Email'
]);
session()->flash('message','Registration Complete !');
return redirect('users');
}
public function registerStudentStore()
{
$this->validate(request(),[
'firstName' => 'required|string',
'lastName' => 'required|string',
'email' => 'required|email|unique:users,email',
'password' => 'required|string',
]);
User::create([
'firstName' => request('firstName'),
'lastName' => request('lastName'),
'type' => 'Student',
'email' => request('email'),
'password' => bcrypt(request('password')),
'provider' => 'Email'
]);
session()->flash('message','Registration Complete !');
return redirect('users');
}
Here we have the methods that'll be called after the form is submitted for each user type, first we include basic input validation, followed by storing the data in the DB after that we use session()->flash for some user feedback and then we redirect to the users page where we have all users listed.
AuthController.phppublic function changeCallbackUrl($type, $provider)
{
if($type == 'student')
{
switch ($provider)
{
case 'google':
$redirectUrl = env('GOOGLE_CALLBACK_STUDENT');
break;
case 'facebook':
$redirectUrl = env('FACEBOOK_CALLBACK_STUDENT');
break;
}
}
if($type == 'teacher')
{
switch ($provider)
{
case 'google':
$redirectUrl = env('GOOGLE_CALLBACK_TEACHER');
break;
case 'facebook':
$redirectUrl = env('FACEBOOK_CALLBACK_TEACHER');
break;
}
}
return $redirectUrl;
}
The changeCallbackUrl method takes 2 parameters the type (user or student) and the name of the provider in this case (google or facebook) then it returns the redirectUrl for each case. Note that we've already specified the callback url for each situation in the .env file.
AuthController.phppublic function RedirectToProvider($type, $provider)
{
$redirectUrl = $this->changeCallbackUrl($type, $provider);
return Socialite::with($provider)->redirectUrl($redirectUrl)->redirect();
}
AuthController.phppublic function ProviderCallback($type,$provider)
{
$redirectUrl = $this->changeCallbackUrl($type, $provider);
config(["services.$provider.redirect" => $redirectUrl]);
$allowedTypes = array('student','teacher');
if(in_array($type, $allowedTypes))
{
try
{
$user = Socialite::driver($provider)->stateless()->user();
$userDB = User::where('email', $user->email)->first();
if(!$userDB)
{
if($provider === 'google')
{
$lastName = $user->user['name']['familyName'];
$firstName = $user->user['name']['givenName'];
}
if($provider === 'facebook')
{
$lastName = $user->user['name']['lastName'];
$firstName = $user->user['name']['firstName'];
}
User::create([
'firstName' => $firstName,
'lastName' => $lastName,
'type' => $type,
'email' => $user->email,
'provider' => $provider,
'provider_id' => $user->id,
]);
session()->flash('message','Registration Complete !');
}
else
{
session()->flash('error','user already exists');
}
return redirect('users');
}
catch (\Exception $e)
{
return redirect('/');
}
}
else
{
return redirect('/');
}
}
Now as mentionned in the route web.php file the callback URL's structure can be similar to : http://localhost:8000/student/google/callback In order to a avoid mismatch error in our provider we need to change the redirect URL in the services.php file so that it would match the one used in the provider API. That's why we use again the changeCallbackUrl method so that it will assign the callback url based on the user type and provider. Then we created an array of allowed users and run a check if the user type returned from the provider exists in that array (just to avoid any sort of error or inserting a wrong type of users) once it's done we retrieve the user's information from the provider, after that we use $userDB to check if any user with similar email address exists in our database if it doesn't we go ahead and store it, if it does we redirect with an error message of course here you can try something else like log In the user directly.
AuthController.phppublic function usersDisplay()
{
$users = User::all();
return view('users',compact('users'));
}
The usersDisplay method lists the users stored in the database.
View Page Forms
register_student.blade.php<div>
<a class="socialMediaLink" href="{{url('oauth/student/facebook')}}"><i class="fab fa-facebook-f"></i> Facebook</a>
<a class="socialMediaLink" href="{{url('oauth/student/google')}}"><i class="fab fa-google-plus-g"></i> Google</a>
</div>
<form action="{{url('register-student')}}" method="post">
{{ csrf_field() }}
<div>
<label for="firstName">First Name</label>
<input type="text" name="firstName">
</div>
<div>
<label for="lastName">Last Name</label>
<input type="text" name="lastName">
</div>
<div>
<label for="email">Email</label>
<input type="email" name="email">
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password">
</div>
<div>
<input type="submit" value="Register">
</div>
</form>
register_teacher.blade.php<div>
<a class="socialMediaLink" href="{{url('oauth/teacher/facebook')}}"><i class="fab fa-facebook-f"></i> Facebook</a>
<a class="socialMediaLink" href="{{url('oauth/teacher/google')}}"><i class="fab fa-google-plus-g"></i> Google</a>
</div>
<form action="{{url('register-teacher')}}" method="post">
{{ csrf_field() }}
<div>
<label for="firstName">First Name</label>
<input type="text" name="firstName">
</div>
<div>
<label for="lastName">Last Name</label>
<input type="text" name="lastName">
</div>
<div>
<label for="email">Email</label>
<input type="email" name="email">
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password">
</div>
<div>
<input type="submit" value="Register">
</div>
</form>
Here we have both registration forms. In the top div we have the link that will redirect to the social media provider for each user there is 2 links one for google and another for facebook as explained previously
users.blade.php<div>
<table>
<thead>
<th>First Name </th>
<th>Last Name </th>
<th>Type </th>
<th>Email </th>
<th>Provider </th>
<th>Creation Date </th>
</thead>
<tbody>
@foreach ($users as $user)
<tr>
<td> {{$user->firstName}} </td>
<td> {{$user->lastName}} </td>
<td> class="capitelize">{{$user->type}} </td>
<td> {{$user->email}} </td>
<td> class="capitelize">{{$user->provider}} </td>
<td> {{$user->created_at->format('d/m/Y')}} </td>
</tr>
@endforeach
</tbody>
</table>
</div>
The table were we display our users, we also include the provider type to know which provider is used by each user.
Screenshots
Source Code
That's all folks ! if you want to access the source code feel free to clone the project from Github. And of course if you need help with the code don't hesitate just drop a comment and i will reply asap.