Adding Socialite for Social logins to Laravel Spark

Starting point

PHP 7.4
Laravel v7.12
Spark v10.0
Running on Valet

Laravel Spark is a SaaS scaffold providing user and team management, subscriptions, Stripe integration. The Spark package comes with Controllers that handle user login, however, you may want to install Socialite to provide for Social platform login and registration.

The approach below is the method I used to integrate Socialite while attempting not to interfere too much with Spark files to avoid issues updating Spark down the track.

1. Install Socialite

composer require laravel/socialite

Edit your config/app.php file

Add the line below to your list of ‘providers’

Laravel\Socialite\SocialiteServiceProvider::class,

Also add the line below to your list of ‘aliases’

‘Socialite’ => Laravel\Socialite\Facades\Socialite::class,

2. Add config for your integrations

In this example we’re going to setup login via Facebook and Github.

You will need to create a new “app” in the selected Social platforms that you intend to integrate with. We’ll look at the steps for Github and Facebook below, but for now we will add config variables to store each platforms OAuth details.

Edit config/services.php

‘facebook’ => [
‘client_id’ => env(‘FACEBOOK_CLIENT_ID’),
‘client_secret’ => env(‘FACEBOOK_CLIENT_SECRET’),
‘redirect’ => env(‘FACEBOOK_CALLBACK’),
],
‘github’ => [
‘client_id’ => env(‘GITHUB_CLIENT_ID’),
‘client_secret’ => env(‘GITHUB_CLIENT_SECRET’),
‘redirect’ => env(‘GITHUB_URL’),
],

Then in your .env file add

#SocialiteGITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK=https://your-domain.com/auth/github/callback
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
FACEBOOK_CALLBACK=https://your-domain.com/auth/facebook/callback

3. Add your login and callback routes

In your routes/web.php file, add the following:

Route::get(‘auth/{provider}/{purpose}’, ‘Auth\LoginController@redirectToProvider’);
Route::get(‘auth/{provider}/callback’,’Auth\LoginController@handleProviderCallback’);

4. Add the Controller that the routes above are referencing

Create a file app/Http/Controllers/Auth/LoginController.php

namespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;
use Socialite;
class LoginController extends Controller
{
/**
* Redirect the user to the given authentication page.
*
* @param Request $request
* @param $provider string The social provider e.g. facebook
* @param $purpose string Whether the user is logging in or registering
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function redirectToProvider(Request $request, string $provider, string $purpose)
{
$request->session()->put(‘social:purpose’, $purpose);
return Socialite::driver($provider)->redirect();
}
}

In my application I wanted to treat the Social platform authentication differently depending on whether it is being used for logging in or registering.

So the route contains a “purpose” parameter, which we store in the session. This way after we receive the callback from the Social platform, we can check this value and process the details accordingly.

Here is the callback handler.

/**
* Process the social platform callback
*
* @param $request Request
* @param $provider string The social provider
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Validation\ValidationException
*/
public function handleProviderCallback(Request $request, $provider)
{
$origin = $request->session()->get(‘social:purpose’);
$social_user = Socialite::driver($provider)->user();
$user = $this->users->find($social_user->getEmail()); if ($origin == “login” && !$user) {
// user was logging in but doesn’t exist in the system
throw ValidationException::withMessages([‘No matching user found. Please register your account.’]);
} else if ($origin == “register” && !$user) {
// Note this code is (almost) a copy of the Spark vendor code
// for registering a new user
// @todo update whenever Spark user registration code is updated
// Create user register request
$registerRequest = new RegisterRequest([
‘name’ => $social_user->getName(),
‘email’ => $social_user->getEmail(),
‘password’ => Hash::make(Str::random(12)),
]);
// Register the user
[$user, $paymentId] = Spark::interact(
Register::class, [$registerRequest]
);
// Mark email as verified, since the social platform has handled this already
$user->email_verified_at = Carbon::now();
$user->save();
Auth::login($user); event(new UserRegistered($user)); return redirect(‘/home’);
}
}

There’s a fair bit to unpack here, so we’ll take it one step at a time.

Firstly, we check the session variable “social:purpose” to see whether this was a login attempt or registration.

$purpose = $request->session()->get(‘social:purpose’);

Then we try to load this user’s details from our UserRepository.

If the purpose was to login and no user is found in the repository then this user doesn’t have an account yet, so we show an error.

If the user was attempting to register ($purpose == “register”), then we want to create a new User with the details from the Social account. Spark already contains code for registering a new User so we want to utilise as much of this as possible, rather than duplicate the relevant code.

There is more than one way to do this, a few options are discussed in this thread https://laracasts.com/discuss/channels/spark/how-to-register-a-user?page=1

The strategy I used was to (almost) duplicate the code from thespark/src/Http/Controllers/Auth/RegisterController.php register() function. The Register class has a handle() method that accepts a RegisterRequest interface argument. We can’t type-hint a RegisterRequest parameter in our callback method, as this would invoke the validation logic in Spark’s RegisterRequest implementation, so instead we’ll declare our own RegisterRequest class.

Create a class app/Http/Requests/Auth/RegisterRequest.php

namespace App\Http\Requests\Auth;use Laravel\Spark\Http\Requests\Auth\RegisterRequest as SparkRegisterRequest;
use Laravel\Spark\Contracts\Http\Requests\Auth\RegisterRequest as Contract;
/**
* This is for programmatically registering users; used after registering via a social platform.
*
* Class RegisterRequest
* @package App\Http\Requests\Auth
*/
class RegisterRequest extends SparkRegisterRequest implements Contract
{
//
}

Note this class implements the Spark RegisterRequest interface so we can pass it to Spark’s Register::handle() method, but it also extends Spark’s RegisterRequest class so that when we pass it to Register::handle(), the methods that get called are implemented for us.

If you wanted to provide custom validation for your Social platform registration process, then you could override any methods you need to in your RegisterRequest class.

So, now we can instantiate our RegisterRequest, set the name and email values from our social platform and call the Register function with:

$registerRequest = new RegisterRequest([
‘name’ => $social_user->getName(),
‘email’ => $social_user->getEmail(),
‘password’ => Hash::make(Str::random(12)),
]);
[$user, $paymentId] = Spark::interact(
Register::class, [$registerRequest]
);

Since we don’t need to verify the email address, coming from a social platform, we can just set the ‘email_verified_at’ field.

$user->email_verified_at = Carbon::now();
$user->save();

We’re almost done. Now we login the user and redirect them to their home page.

Side Note

If you’re using an IDE (e.g. PHPStorm) and use Socialite; is showing as “Class Not Found”, you can install an IDE helper package to help your IDE identify Laravel’s aliased classes.

composer require — dev barryvdh/laravel-ide-helper

php artisan ide-helper:generate

5. Set up apps in Social Platforms to get OAuth credentials

Github setup

  • Log in to Github
  • Click the profile icon in the top right corner and then Settings
  • Click “Developer Settings” on the bottom of the left side menu
  • Click “OAuth Apps” then the “New OAuth App” button
  • Enter the fields below. Note the “Authorization Callback URL” field should match the one above in your .env file e.g. https://your-domain.com/auth/github/callback
  • Click Register Application

After this, the Client ID and Client Secret will be displayed. Copy these values to your .env file.

Facebook setup

  • Log in at https://developers.facebook.com/apps/
  • Click “Add a New App”
  • Fill in the App Name and Contact Email, and click Create App ID
  • Click on “Settings” on the left menu, then on “Basic”
  • Copy the App ID and App Secret to your .env file
  • Fill in the App Domains field with your website domain name
  • Fill any other fields, as required.
  • Add an address under the heading “Data Protection Officer Contact Information”
  • At the bottom of the form, click “Add Platform”, then select “Website”
  • Enter your websites home page URL in the Site URL field
  • Click “Save Changes”
  • Click on the + next to “Products” at the bottom of the left side menu
  • Click on “Facebook Login”
  • Click on “Settings” under “Facebook Login” in the left side menu
  • Add the Facebook Callback URL to the “Valid OAuth Redirect URIs” field, as set in your .env file
  • Click “Save Changes”

6. Add Social Login links to a suitable Blade template

You can add your Social Login links to any Blade template you like; obviously it will need to be a publicly visible View.

For my application I slightly modified the Spark Vendor View at resources/views/vendor/spark/auth/login.blade.php by adding the following above the Login Button

<! — Login via Socials ->
@include(“auth.social-login”)

Then created a new blade template at resources/views/auth/social-login.blade.php with the following

<div class=”form-group row”>
<div class=”col-md-6 offset-md-4">
<div class=”social-logins”>
<a href=”{{ url(‘/auth/github/login’) }}” class=”btn btn-github”>Github</a>
<a href=”{{ url(‘auth/facebook/login’) }}” class=”btn btn-facebook”>Facebook</a>
</div>
</div>
</div>

To add the social registration options, I edited the Spark Vendor View at resources/views/vendor/spark/auth/register-common-form.blade.php by adding the following, above the Terms and Conditions section

@include(“auth.social-register”)

The created a new blade template at resources/views/auth/social-register.blade.php with the following

<div class=”form-group row”>
<div class=”col-md-6 offset-md-4">
<div class=”social-logins”>
<a href=”{{ url(‘/auth/github/register’) }}” class=”btn btn-github”>Github</a>
<a href=”{{ url(‘/auth/facebook/register’) }}” class=”btn btn-facebook”>Facebook</a>
</div>
</div>
</div>

That’s all for now!
Your users should be able login or register via their Facebook or Github accounts.

CTO @technocrat_au. Web developing for over 17 years using PHP, Drupal, Laravel, React, VueJS, .NET, AWS… Author of https://buff.ly/2yZYw3Z