Laravel Google Login with Socialite
Tags: laravel, webdev, php
Published: 2022-02-08 17:37:28

Social login help simplifies registration and login for the end users. Socialite is a social login package provided by Laravel. This package allows us to seamlessly integrate social logins like Facebook, Twitter, Google and many more. The list of adapters that **Socialite** provides can be found [here](https://socialiteproviders.com/).

A **Google Cloud Platform** (GCP) account is required for this example.

### Installation
First we need to scaffold our application with Laravel breeze. You can follow the steps provided [here](https://laravel.com/docs/8.x/starter-kits#laravel-breeze-installation).

Once the scaffolding is done, we will need to install the socialite package into our application.
```php
composer require laravel/socialite
```

### Setting Up Migrations
Let's create a table to store the user's socialite login.
```php
php artisan make:migration CreateSocialiteLoginsTable
```

Next, setup the columns for our `socialite_logins` table.
```php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateSocialiteLoginsTable extends Migration
{
    public function up(): void
    {
        Schema::create('socialite_logins', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->references('id')->on('users');
            $table->string('provider_id');
            $table->string('provider');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('socialite_logins');
    }
}
```
The `user_id` column will be used to map the socialite logins to the users. The type of provider will be saved in the `provider` column, i.e `google`.

Considering that the users are not required to register, make sure to change the `password` column in our `CreateUsersTable` migration to nullable, .

### Setting Up Models
Create a `SocialiteLogin` model.
```php
php artisan make:model SocialiteLogin
```

Since we are only implementing the Google authentication and a user can only have one Google account, the relationship between the `SocialiteLogin` model and `User` model is `One-to-One`. 

>If you feel like implementing another social login, you will need to change the relationship to `Many-to-One`.

Add the `user_id`, `provider` and `provider_id` field to the `$fillable` property in the `SocialiteLogin` model.
```php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class SocialiteLogin extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id',
        'provider',
        'provider_id'
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
```
Next, add the `socialiteLogin` relationship in the `User` model.
```php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasFactory;
    use HasApiTokens;

    /**
     * The attributes that are mass assignable.
     *
     * @var string[]
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    // change this to HasMany
    // if you are implementing
    // more than one social login
    public function socialiteLogin(): BelongsTo
    {
        return $this->belongsTo(SocialiteLogin::class);
    }
}
```

### Setting Up Google Cloud Platform
Login to a Google Cloud Platform account and select a project or create one. Click on the sidebar and navigate to the `API and services -> OAuth Consent Screen`.

#### Setting Up OAuth Consent Screen
We will be greeted by this screen.
![OAuth Consent](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/heu8y9xxvbyxsyed4yjj.png)

Since we are authenticating external users, the user type that we'll be selecting is external.

Once we have selected the type of user, we will be redirected to the OAuth Consent Screen page. We are only required to fill in the app information section and the developer contact information.
![App Information](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1n4jrr8qmpg72gu0483u.png)

The developer contact information will be your personal/work email.
![Developer Contact](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jp1fg5ggfczoyhzj4sg9.png)
 
After setting up the app information, we will be setting up the scope of permission that is needed for our application. Click the `add or remove scopes` and select `userinfo.email` and `userinfo.profile` scope.
![Scoped Permission](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ntutcu0so5ikxssxvtnw.png)
 
  
Click `save and continue` and setup the test user with an existing gmail account.

#### Setting Up Credentials
Proceed to the credentials tab located on the sidebar and create a new `OAuth Client ID`. The application type we are using is `web application`. 

>If you are developing with an SPA, you will need to setup the `Authorized JavaScript Origin` section. The `http://localhost:3000` url is sufficient for this field.

The `Authorized Redirect URIs` is where we are going to handle the authentication of the user based on their email and profile. Our url will be `http://127.0.0.1:8000/login/google/callback`.
![Credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1ke1q30swluydlwn836.png)
 

Once we have created the credentials, we will be given the `CLIENT_ID` and `CLIENT_SECRET` values.

The credentials will be placed in `services.php` config and  `.env` file.
```php
// .env
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI= // callback url

// services.php
'google' => [
   'client_id' => env('GOOGLE_CLIENT_ID'),
   'client_secret' => env('GOOGLE_CLIENT_SECRET'),
   'redirect' => env('GOOGLE_REDIRECT_URI'), // callback url
]
```

### Setting Up URL and Callback
Create a `SocialiteLoginController` controller. This controller will be consisting of two functions, `redirectToProvider` and `handleProviderCallback`

```php
php artisan make:controller SocialiteLoginController
```
#### Handling The Redirection and Callback
The `redirectToProvider` will only redirect the user to the google authentication page via the **Socialite Driver**. 

Moving on to the `handleProviderCallback`. First we get the `user` from the `Socialite` driver. This user returns a variety of informations since the Google provider supports OAuth2.0.

In our case, we only need the user's `email`, `name` and `id` of the provider. We will be using Laravel's `firstOrCreate` method to identify if the user exists. If it does not, we will create a new user.

We will be using the same method for our `SocialiteLogin` model. We are doing this to ensure that there is no duplication for the socialite login since a user can only have one google account.

We can now safely authenticate the `user` by using Laravel's `auth` helper and redirect them to the dashboard.

The controller will look something like this.
```php
namespace App\Http\Controllers;

use Laravel\Socialite\Facades\Socialite;

class SocialiteLoginController extends Controller
{
    public function redirectToProvider()
    {
        return Socialite::driver('google')->redirect();
    }

    public function handleProviderCallback()
    {
        $socialiteUser = Socialite::driver('google')->user();

        $user = User::firstOrCreate(
            [
                'email' => $socialiteUser->getEmail(),
            ],
            [
                'name' => $socialiteUser->getName(),
            ]
        );

        SocialiteLogin::firstOrCreate(
            [
                'user_id' => $user->id,
                'provider' => 'google',
            ],
            [
                'provider_id' => $socialiteUser->getId()
            ]
        );

        auth()->login($user);

        return redirect(route('dashboard'));
    }
}
```

#### Setting Up The Route
Add the controller to the `web.php` route file.
```php
// web.php
Route::get('/login/google', [SocialiteLoginController::class, 'redirectToProvider']);
Route::get('/login/google/callback', [SocialiteLoginController::class, 'handleProviderCallback']);
```

>The callback url needs to be the same with what we have setup in our Google Cloud Platform.

### Updating the UI
We begin by starting our development server.
```php
php artisan serve
```

The UI is pretty simple compared to what we have done so far.

Since we will be doing a redirection, using an `anchor` tag makes more sense than using a `button`. Paste the code below to the `login.blade.php`.

>Make sure to change the url accordingly

```html
// login.blade.php
<a
  href="/login/google"
  class="ml-3 inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300 disabled:opacity-25 transition ease-in-out duration-150"
>
   Login Google
</a>
```  

Click on the `Login Google` button to proceed to the Google OAuth Screen. After selecting a google account, we will be redirected to the dashboard after being authenticated. 
![Dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wax4qkamdp36qm5vwudu.png)
 
### Conclusion
Most of the providers that [Socialite](https://laravel.com/docs/8.x/socialite) supports have the same implementation thus helping us reduce the boilerplate needed to be written and maintained. 

Let me know if there are any questions or things that I can improve on.