Your Guide to Laravel Excellence

LinkedIn OAuth Authentication in Laravel Without Socialite

LinkedIn OAuth Authentication in Laravel Without Socialite

In this guide, we’ll learn the process of integrating LinkedIn authentication into a Laravel 10 without using any third-party package like Laravel Socialite. We'll rely solely on LinkedIn API’s endpoints to create authentication from scratch. By the end of this tutorial, you’ll have a Laravel app where users can log in using their LinkedIn credentials. This setup is ideal for scenarios where you need more control over the authentication process or want to avoid extra dependencies.

Setting Up a Laravel Project

To get started, create a fresh Laravel project. Open your terminal and run the following command:

composer create-project laravel/laravel linkedin-integration

After creating the project, we’ll need to install Laravel UI for authentication scaffolding. Laravel UI provides simple authentication scaffolding to get started quickly.

composer require laravel/ui
 php artisan ui bootstrap –auth
 npm install

Finally, don’t forget to configure your database connection in the .env file. For this tutorial, name your database social-integration.

DB_DATABASE=socialIntegration

Integrate LinkedIn Login

To integrate LinkedIn login, you'll need a LinkedIn Developer Account. Follow these steps:

Visit LinkedIn Developer Portal

Click on "Create App".

Fill out the required fields and set up your app.

Once created, navigate to "Auth" settings, and you'll find your Client ID and Client Secret.

Set your redirect URI to match the one in your Laravel app, for example: http://127.0.0.1:8000/auth/linkedin/callback.

Now, update your .env file with the LinkedIn API credentials:

LINKEDIN_CLIENT_ID=your_client_id LINKEDIN_CLIENT_SECRET=your_client_secret LINKEDIN_REDIRECT_URI=http://127.0.0.1:8000/auth/linkedin/callback

DB_DATABASELINKEDIN_CLIENT_ID=77zxxxxxxxxxi7k
LINKEDIN_CLIENT_SECRET=NzqxxxxxxxxdQGJ
LINKEDIN_REDIRECT_URI="http://127.0.0.1:8000/auth/linkedin/callback"

Updating the Migration for LinkedIn Data

Next, we need to update the users table to store the linkedin_id and picture_url fields. This allows us to track which users logged in through LinkedIn and store their profile pictures Update your users migration like this:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('linkedin_id')->nullable();
    $table->string('picture_url')->nullable();
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->rememberToken();
    $table->timestamps();
});

Updating the User Model

protected $fillable = [ 'name',  'email', 'linkedin_id', 'picture_url'];

After updating the migration, run the following command to apply the changes to your database:

php artisan migrate

Creating the LinkedIn Controller

We now need to create a controller to handle the LinkedIn authentication logic. Run this Artisan command to create a new controller:

php artisan make:controller LinkedinController
namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;

class LinkedinController extends Controller

{

    public function redirectToLinkedin()
    {
        $clientId = config('services.linkedin.client_id');
        $redirectUri = config('services.linkedin.redirect_uri');
        $scope = 'openid%20profile%20email';
        $response_type = 'code';

        // $state = bin2hex(random_bytes(16));
        // session(['linkedin_state' => $state]); //optional
        // if you are creating state then you also need to pass in url


        return redirect("https://www.linkedin.com/oauth/v2/authorization?response_type=$response_type&client_id=$clientId&redirect_uri=$redirectUri&scope=$scope");
    }

    public function handlelinkedinCallback(Request $request)

    {

        // $state = $request->query('state');
        //  Verify the state parameter to prevent CSRF attacks
        // if (!$state || $state !== $request->session()->pull('linkedin_state')) {
        //     return response()->json(['error' => 'Invalid state'], 400);
        // }


        $code = $request->query('code');
        if (empty($code)) {
            return response()->json(['error' => 'Authorization code not provided'], 400);
        }

        $clientId = config('services.linkedin.client_id');
        $clientSecret = config('services.linkedin.client_secret');
        $redirectUri = config('services.linkedin.redirect_uri');
        
        $response = Http::asForm()->post('https://www.linkedin.com/oauth/v2/accessToken', [
            'code'          => $code,
            'client_id'     => $clientId,
            'client_secret' => $clientSecret,
            'redirect_uri'  => $redirectUri,
            'grant_type'    => 'authorization_code',
        ]);

        $accessToken = $response->json('access_token');
        $userData = $this->getUserData($accessToken);
        $user = $this->findOrCreateUser($userData);
        $userRecord = User::where('email', $user['email'])->where('linkedin_id', $user['linkedin_id'])->first();
        if ($userRecord) {
            Auth::login($userRecord);
            return redirect('/'); //Redirect the user where you want
        } else {
            return redirect()->route('login');
        }
    }

    private function getUserData($accessToken)

    {
        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . $accessToken,
        ])->get('https://api.linkedin.com/v2/userinfo');
        return $response->json();
    }

    private function findOrCreateUser($userData)

    {

        $user = User::where('email', $userData['email'])->first();
        
        if (!$user) {

            // Create a new user if not exists in database
            $user = User::create([
                'name' => $userData['name'],
                'email' => $userData['email'],
                'linkedin_id' => $userData['sub'],
                'picture_url'   => $userData['picture'],
            ]);
        }
        return $user;
    }
}

LinkedIn Login Functionality

Below are the key functionalities involved in LinkedIn Login integration:

redirectToLinkedin: Redirects the user to LinkedIn's OAuth authorization page.

handleLinkedinCallback: Handles the callback after LinkedIn’s authorization. It exchanges the authorization code for an access token and uses this token to fetch user data from LinkedIn.

getUserData: Fetches user data from LinkedIn using the access token.

findOrCreateUser: Finds an existing user or creates a new one based on the LinkedIn profile information.

Now goto your routes/web.php

Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('auth/linkedin', [LinkedinController::class, 'redirectToLinkedin']);
Route::get('auth/linkedin/callback', [LinkedinController::class, 'handlelinkedinCallback']);

Lets step up our views.Now go to resources/views/auth/login.blade.php

<div class="row mb-0">
    <div class="col-md-8 offset-md-4">
        <a href="{{ url('auth/linkedin') }}" class="btn btn-primary">Login with Linkedin</a>
        <button type="submit" class="btn btn-primary">
            {{ __('Login') }}
        </button>
        @if (Route::has('password.request'))
            <a class="btn btn-link" href="{{ route('password.request') }}">
                {{ __('Forgot Your Password?') }}
            </a>
        @endif
    </div>
</div>

Running the Application

You’re almost done! Open two terminals and run the following commands:

//In the first terminal, start the Laravel development server:
php artisan serve

//In the second terminal, compile your frontend assets:

npm run dev

Once the server is running, navigate to http://127.0.0.1:8000 and try logging in with LinkedIn. alt! alt! alt!

Recommeded Posts

Introduction to Multiple Authentication Guards in Laravel

Introduction to Multiple Authentication Guards in Laravel

Introduction to Multiple Authentication Guards in Laravel

1 month ago Read article →
Laravel 11: How to Download Files from External URLs and Servers

Laravel 11: How to Download Files from External URLs and Servers

Learn how to download files from external URLs and servers in Laravel 11. This easy guide walks you through the steps to set up file downloads in your Laravel application, making it simple to fetch files from anywhere.

1 month ago Read article →
AWS Elastic Transcoder: Convert & Stream Videos in Laravel 11

AWS Elastic Transcoder: Convert & Stream Videos in Laravel 11

AWS Elastic Transcoder: Convert & Stream Videos in Laravel 11

1 month ago Read article →
Laravel 11.30 Introduces HasUniqueStringIds for Simple Unique IDs

Laravel 11.30 Introduces HasUniqueStringIds for Simple Unique IDs

Laravel 11.30 Introduces HasUniqueStringIds for Simple Unique IDs

1 month ago Read article →