Integrating LinkedIn with Laravel & Filament: A Seamless OAuth Journey

Imagine giving your users a one-click way to enrich their profiles or share content without ever leaving your application. Manual data entry is tedious, but third-party integrations, particularly via OAuth, can seem daunting.

In our landing project, we aimed to simplify user profile management and content sharing by enabling direct LinkedIn account integration. The goal was a smooth, secure, and intuitive process for users to connect and disconnect their LinkedIn profiles.

The Integration Challenge

Integrating a service like LinkedIn involves several moving parts: redirecting users for authentication, handling callback responses, securely storing credentials, and providing a clear user interface to manage the connection. This isn't just about showing a button; it's about a robust, secure flow.

Building the Bridge: Key Components

Our approach focused on segmenting the logic into distinct, manageable components:

1. The OAuth Orchestrator: LinkedinConnectionController

This controller is the brain of the operation, handling the initial redirect to LinkedIn and processing the callback after a user authorizes our application. It orchestrates the OAuth dance, ensuring tokens are exchanged securely and user data is retrieved.

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite; // Or a custom LinkedIn client

class LinkedinConnectionController extends Controller
{
    public function redirectToLinkedIn()
    {
        return Socialite::driver('linkedin-openid')
                        ->scopes(['r_liteprofile', 'r_emailaddress', 'w_member_social'])
                        ->redirect();
    }

    public function handleLinkedInCallback()
    {
        try {
            $user = Socialite::driver('linkedin-openid')->user();
            // Store user's LinkedIn ID and token
            auth()->user()->update([
                'linkedin_id' => $user->getId(),
                'linkedin_token' => $user->token,
                // ... other profile data
            ]);

            return redirect('/dashboard')->with('status', 'LinkedIn connected successfully!');
        } catch (\Exception $e) {
            return redirect('/dashboard')->with('error', 'Failed to connect LinkedIn: ' . $e->getMessage());
        }
    }

    public function disconnectLinkedIn()
    {
        auth()->user()->update([
            'linkedin_id' => null,
            'linkedin_token' => null,
        ]);

        return redirect('/dashboard')->with('status', 'LinkedIn disconnected.');
    }
}

2. Database Foundation: User Table Updates

To persist the connection, we introduced new columns to our users table. This is crucial for linking a local user account to their LinkedIn profile and storing the necessary access tokens.

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('linkedin_id')->nullable()->after('email');
            $table->string('linkedin_token', 1024)->nullable()->after('linkedin_id'); // Using a longer token field
            $table->timestamp('linkedin_connected_at')->nullable()->after('linkedin_token');
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn(['linkedin_id', 'linkedin_token', 'linkedin_connected_at']);
        });
    }
};

3. User Experience: ConnectLinkedinWidget for Filament

Filament, our administrative panel framework, provided an elegant way to expose this functionality to administrators or directly to users within their dashboard. The ConnectLinkedinWidget displays the current connection status and offers "Connect" or "Disconnect" actions. This brings the backend logic to life in an intuitive UI.

<?php
namespace App\Filament\Widgets;

use Filament\Widgets\Widget;
use Illuminate\Contracts\View\View;

class ConnectLinkedinWidget extends Widget
{
    protected static string $view = 'filament.widgets.connect-linkedin-widget';

    public function isConnected(): bool
    {
        return (bool) auth()->user()->linkedin_id;
    }

    public function render(): View
    {
        return view(static::$view, [
            'isConnected' => $this->isConnected(),
        ]);
    }
}

And in the corresponding Blade view resources/views/filament/widgets/connect-linkedin-widget.blade.php:

<div>
    @if ($isConnected)
        <p>LinkedIn connected!</p>
        <a href="{{ route('linkedin.disconnect') }}" class="button">Disconnect</a>
    @else
        <p>Connect your LinkedIn account.</p>
        <a href="{{ route('linkedin.connect') }}" class="button">Connect with LinkedIn</a>
    @endif
</div>

4. Routing and Testing

Robust routing ensures all OAuth redirects, callbacks, and disconnection requests are correctly handled. Extensive testing, leveraging tools like PHPUnit, guarantees the flow works as expected, covering success scenarios, error handling, and security aspects.

// In web.php
use App\Http\Controllers\LinkedinConnectionController;

Route::get('/linkedin/connect', [LinkedinConnectionController::class, 'redirectToLinkedIn'])->name('linkedin.connect');
Route::get('/linkedin/callback', [LinkedinConnectionController::class, 'handleLinkedInCallback'])->name('linkedin.callback');
Route::get('/linkedin/disconnect', [LinkedinConnectionController::class, 'disconnectLinkedIn'])->name('linkedin.disconnect');

Outcome and Takeaways

By modularizing the integration, from controller logic to database schema and UI components, we created a feature that is both powerful and easy to maintain. Users now enjoy a seamless experience, and the codebase remains clean and testable.

This structured approach to third-party integration, leveraging Laravel's capabilities and Filament's extensibility, minimizes complexity and maximizes developer efficiency. When tackling similar integrations, remember to break down the flow, consider all user journeys, and test rigorously.

Integrating LinkedIn with Laravel & Filament: A Seamless OAuth Journey
Gerardo Ruiz

Gerardo Ruiz

Author

Share: