Google Authentication into a Laravel Application using Socialite

In this post we’re going to add authentication via Google to a Laravel app.

I’m assuming that you already have a Laravel 8 project installed and running.

Google API Keys

First, we will need to enable Credentials for OAuth 2.0 on the Google Developer Console.

Go to the Google Developer Console http://console.developers.google.com/

From there, Go to API & Services -> Credentials -> Create Credentials -> OAuth Client ID

A form will be prompted to create the credentials, fill the form with this info.

  • Application Type: Web Application
  • Name: The name of your application
  • Authorized JavaScripts Origin: Nothing
  • Authorized redirect Uris: The url for go back to your app > after the google login

The most important part here is the   Authorized redirect Uris, Here you can put all the urls that you will be using to redirect the user after the success login in google to your app, is a list of valid urls that can be used in the app.

To be more clear, let’s assume that the production domain of your app is myapp.com, our callback route will be myapp.com/auth/callback, so you need to add  myapp.com/auth/callback.

However, if you have another environment, dev for example, the domain can be dev.myapp.com, you can add dev.myapp.com/auth/callback here too. Even local.myapp.com for your local environment.

We will set this callback in the socialite configuration later.

After that you’ll get the Client ID and Secret from google that we will be using to configure socialite.

Notes

You might need to create a project in Google Cloud Platform first, follow the step by step form.

You might need to set the Oauth consent screen first, this is data for the google oauth form, fill the data and continue.

Install Socialite

Next, install the socialite package into your laravel project using composer, run

composer require laravel/socialite

Done, the library is added.

Add Socialite Service

Next step is to add the socialite service to Laravel.

Go to config/app.php and look into the array for the providers entry, you need to add a new item inside of the providers array for Socialite.

Laravel\Socialite\SocialiteServiceProvider::class,

Also, in the same file add the alias in the alias array.

'Socialite' => Laravel\Socialite\Facades\Socialite::class,

Configure Socialite

Now, go to app/services.php to add the configs for Google for Socialite. We need to add this inside of the returned array.

'google' => [
    'client_id' => env('GOOGLE_CLIENT_ID'),
    'client_secret' => env('GOOGLE_CLIENT_SECRET'),
    'redirect' => env('GOOGLE_REDIRECT'),
],

The code is clear enough, we are going to set the client id, the secret and the callback urls, it must be one of the urls added when we created the Google Keys.

As you may know, the env function will read the real values from the .env file, so let’s edit .env file and add the real values at the end of it.

GOOGLE_CLIENT_ID=92504BLABLABLA-YOUR-KEY
GOOGLE_CLIENT_SECRET=GOCSP-BLABLABLA-YOUR-SECRET
GOOGLE_REDIRECT=https://myapp.com/auth/callback

Remember that every environment will have its own .env file, so change the redirect url to the callback url that matches the corresponding env.

NOTE: The callback url is resolved in the browser, so there is no need to have a public url accessible from everywhere. If you browser can solve it, it will work. I make this note because you can just set your local environment url and it will work, there is no need to do something extra.

Alter user table to add google_id

We will need to add a google_id field into the users table in order to track the Google <-> Local Users interaction.

For that, we can just simply add the field in the users migration that comes by default, or, a better way will be to do a migration to alter the table and add the field, i’ll choose the second one.

Create the migration

php artisan make:migration add_google_id_column

Go to the migration file (database/migrations/DATE_add_google_id_column.php) to add the alter

public function up() {
    if (Schema::hasTable('users') && Schema::hasColumn('users', 'google_id')) {
        Schema::table('users', function (Blueprint $table) {
            $table->string('google_id')->nullable();
        });
    }
    
}

public function down() {
    if (Schema::hasTable('users')) {
        if (Schema::hasColumn('users', 'google_id')) {
            Schema::table('users', function (Blueprint $table) {
                $table->dropColumn('google_id');
            });
        }
    }
}

Run the migration

php artisan migrate

Verify that the column was added to the table in the database.

Create the Auth Controller

Awesome, all most done. The next step is to add a controller to handle these routes.

  • Login Screen, shows the login screen with the login button (/login)
  • Redirect from our app to google auth after the login button is clicked. (/auth/login)
  • Callback from Google after login (/auth/callback)
  • Logout (/logout)

To create the controller we will use artisan

php artisan make:controller AuthController

Let’s go to app\Http\Controllers\AuthController.php

The controller must look like this

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Facades\Auth;
use App\Models\User;

class AuthController extends Controller
{

    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    public function login()
    {
        //Code here
    }

    public function redirectToGoogle()
    {
        //Code here
    }

    public function handleGoogleCallback()
    {
        //Code here
    }

    public function logout(Request $request)
    {
        //Code here
    }
}

That’s the basic idea. Let’s see each method in detail.

public function login()
{
    return view('login');
}

This is going to return a blade view with a link to the /auth/login path (that will be calling to redirectToGoogle).

The blade view resources/views/login.blade.php must contain this.

<a href="/auth/login">Login</a>

Now, when the user clicks on that link will call redirectToGoogle, that method is like this

public function redirectToGoogle()
{
    return Socialite::driver('google')->redirect();
}

Basically Sociality will build a valid oauth url using the config data for google and will redirect the user to the google login screen. The user is going to leave our app.

In google the user will login or select his account if is already logged in into that browser. After the login or selection, if is approved, google will make another redirection to the redirect url that we have set in the .env file.

That redirect url must be managed by the handleGoogleCallback method, and the code must be something like this.

public function handleGoogleCallback()
{
    $user = Socialite::driver('google')->user();
    $localUser = User::where('email', $user->email)->first();

    if (!$localUser) {
        $localUser = User::create([
            'name' => $user->name,
            'email' => $user->email,
            'google_id' => $user->id,
            'password' => Hash::make(Str::random(20)),
        ]);
    }

    Auth::login($localUser);
    return redirect('/');
}

As you can see, we load the google user information from Socialite. An exception is thrown if the user can’t be loaded.

With the google user info, we lookup for a local user.

If it’s not found, we will create a user with the google info and will use this new user as if it was found before for the next step.

If it’s found, we will login the user using the laravel Auth facade and redirect the user to the home.

That’s it, the user is logged in.

Finally, to logout the user we will use the laravel Auth facade again

public function logout(Request $request)
{
    Auth::logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();
    return redirect('/');
}

Add Auth Routes

These are the routes that we need to add into routes/web.php file to enable the AuthController Methods.

use App\Http\Controllers\AuthController;

....

Route::get('login', [AuthController::class, 'login'])->name('login');

Route::get('logout', [AuthController::class, 'logout'])->name('logout');

Route::get('auth/login', [AuthController::class, 'redirectToGoogle']);

Route::get('auth/callback', [AuthController::class, 'handleGoogleCallback']);

Note that login and logout are named routes, Laravel will use the login route to make his internal redirects when a user is not logged in on a route that requires auth.

To require auth into a route, you have to add the auth middleware to the route. For example

Route::get('/', [MyController::class, 'index'])       
    ->middleware('auth');

Or using group routes

Route::middleware(['auth'])->group(function () {
    Route::get('/', [MyController::class, 'index']);
    Route::resource('some_resource', ResourceController::class);
});

Testing

In order to test this, you need to validate the following requirements

  • If the user goes to any auth required route and is not logged in, it must be redirected to /login
  • If the user goes to /login, the sign in button must be shown.
  • If the user clicks into the sign in button, must be redirect to google login.
  • After a successful login in google, the user must be redirected to our app and it must be logged in.
  • If is a new user, a new entry in the users table must exists.
  • If the user goes to /logout must be logged out.

Troubles

If you are getting socialite exceptions, probably there is a problem with the oauth flow, maybe some old cookies are in the browser and they are bringing problems.

On any change that you do in your code, start the testing on a Incognito Browsing or clean all cookies and data of your app.

Conclusions

Socialite is a good library to integrate third party auth services, as you can see is very easy to setup and configure.

I hope this post help you using it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.