linkedinapi/linkedin
A Laravel package that wraps the LinkedIn API, simplifying OAuth authentication and common endpoints for user profiles, connections, and sharing updates. It helps PHP developers quickly add LinkedIn login and social posting features to their applications.
1. Install and inspect the package
composer require linkedinapi/linkedin
The package ships a single LinkedIn class (historically LinkedIn.php / linkedin.php) rather than a Laravel package. Check vendor/linkedinapi/linkedin/ immediately after install to confirm:
require_once,API_KEY, API_SECRET, OAUTH_CALLBACK, OAUTH_TOKEN, OAUTH_TOKEN_SECRET).2. Get credentials
Create a LinkedIn app in the LinkedIn Developer Portal. Note the app's client ID/secret and register your callback URL (https://your-app.test/auth/linkedin/callback). Be aware that most useful scopes (r_liteprofile, r_emailaddress, w_member_social) now require OAuth 2.0 and, for some, LinkedIn partner approval — this SDK targets the legacy OAuth 1.0a/v1 API.
3. Bind it in Laravel
Create a small service provider so you never new LinkedIn() in controllers:
// app/Providers/LinkedInServiceProvider.php
public function register(): void
{
$this->app->singleton(\LinkedIn::class, fn () => new \LinkedIn(
config('services.linkedin.key'),
config('services.linkedin.secret'),
config('services.linkedin.callback'),
));
}
Add credentials to config/services.php and .env:
'linkedin' => [
'key' => env('LINKEDIN_KEY'),
'secret' => env('LINKEDIN_SECRET'),
'callback' => env('LINKEDIN_CALLBACK'),
],
4. First use case — "Sign in with LinkedIn" and fetch the profile
Wire two routes: one that obtains a request token and redirects to LinkedIn, and one callback that exchanges the verifier for an access token and calls fetch('GET', '/v1/people/~:(id,first-name,last-name,email-address)'). Persist the returned token pair against your users row, then redirect into your app.
If your goal is simply social login in 2024+, stop here and evaluate laravel/socialite + socialiteproviders/linkedin instead; this package will not work against LinkedIn's current OAuth 2.0 endpoints without modification.
Thin wrapper service. Never let the SDK leak into controllers or models. Wrap it:
class LinkedInClient
{
public function __construct(private \LinkedIn $sdk) {}
public function profile(string $token, string $secret): array
{
$this->sdk->setTokenAccess($token, $secret);
return $this->sdk->fetch('GET', '/v1/people/~:(id,first-name,last-name,email-address)', ['format' => 'json']);
}
}
This gives you one place to swap in a Guzzle/Http-based OAuth 2.0 implementation later.
OAuth handshake as controller actions.
Route::get('/auth/linkedin', [LinkedInAuthController::class, 'redirect']);
Route::get('/auth/linkedin/callback', [LinkedInAuthController::class, 'callback']);
public function redirect(LinkedInClient $client)
{
$token = $client->requestToken(); // getRequestToken()
session(['linkedin_request_token' => $token]);
return redirect($client->authorizeUrl($token)); // redirect()
}
public function callback(Request $request, LinkedInClient $client)
{
$access = $client->exchange(
session()->pull('linkedin_request_token'),
$request->query('oauth_verifier') // retrieveTokenAccess()
);
auth()->user()->update([
'linkedin_token' => encrypt($access['token']),
'linkedin_secret' => encrypt($access['secret']),
]);
return redirect()->route('dashboard');
}
Keep the request token in the session (or a short-lived cache entry) so concurrent logins don't collide.
Token storage and retrieval. Store OAuth tokens encrypted (encrypt() cast or a EncryptedString cast). Add a small accessor on the User model that decrypts and hands the pair to your LinkedInClient. Never log tokens; redact oauth_token/oauth_token_secret from request logs.
Queued API calls. LinkedIn calls are slow and rate-limited. Push outbound work (sharing a post, syncing profile fields) onto a queue:
dispatch(new PublishLinkedInPost($user, $payload));
In the job, resolve LinkedInClient from the container, catch SDK exceptions, and use $this->release(60 * 5) on rate-limit responses rather than failing.
Testing. Because the SDK is a concrete class, bind a fake in tests:
$this->mock(LinkedInClient::class, function ($mock) {
$mock->shouldReceive('profile')->andReturn(['id' => 'abc', 'firstName' => 'Ada']);
});
Avoid hitting LinkedIn in CI; record fixtures of real fetch() responses and replay them.
Integration tips.
LinkedIn facade only if you really need LinkedIn::fetch() syntactically — otherwise inject the client.Cache::remember) since the legacy API is slow and rate-limited.The API is effectively dead. This SDK was built for LinkedIn's v1 REST API and OAuth 1.0a. LinkedIn sunset v1 around 2019 and requires OAuth 2.0 with OpenID Connect for profile data. Expect 401, 403, or 410 responses for /v1/people/~ and related endpoints. Treat this package as a legacy integration or a learning reference, not a greenfield choice.
Autoloading and naming. The package predates PSR-4. You may get a global LinkedIn class (\LinkedIn) that collides with nothing in Laravel but is not discoverable by Composer's classmap in all setups. If class_exists(\LinkedIn::class) fails, add a require_once in your service provider or a classmap entry in composer.json, then composer dump-autoload.
Config quirks. Many forks read constants (API_KEY, API_SECRET) defined at include time rather than constructor args. In Laravel this is painful because config:cache runs in a separate process. Prefer the constructor-based variant; if unavailable, define() the constants in a service provider before resolving the class — and never rely on .env being readable at runtime.
Error handling is weak. The SDK often returns false or an XML/JSON error body instead of throwing. Wrap every call and normalize:
$result = $client->fetch('GET', $endpoint);
if ($result === false || isset($result['errorCode'])) {
throw new LinkedInException($result['message'] ?? 'Unknown LinkedIn error');
}
Log the raw body (with tokens stripped) at debug level.
Redirect and callback URLs must match exactly. LinkedIn is strict about the registered callback, including trailing slashes and scheme. Use route('linkedin.callback') rather than a hardcoded string, and ensure APP_URL is correct behind proxies (TrustProxies).
Token longevity. OAuth 1.0a tokens are long-lived but revocable; OAuth 2.0 tokens expire in ~60 days. If you migrate, add a refresh job. Handle 401 by clearing stored tokens and prompting re-auth rather than retrying endlessly.
Rate limits and payload sizes. Legacy LinkedIn enforced per-app and per-user throttles. Batch profile fields into one :(...) projection instead of multiple calls, and back off exponentially on 429.
Extension points. Subclass LinkedIn and override the HTTP request method to route through Laravel's Http client — this gives you retries, timeouts, and logging without forking. For a forward path, replace the SDK entirely: Socialite for the handshake, then Http::withToken($token)->get('https://api.linkedin.com/v2/me') for data. That is the pattern most Laravel teams should actually ship.
How can I help you explore Laravel packages today?