Closed noeurphireak closed 5 years ago
+1 All social logins with LinkedIn are now broken
I've been working on a solution and believe I'm nearly there but at the moment I'm bumping into the following error when performing a request to the https://api.linkedin.com/v2/me
endpoint.
Client error:
GET https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))
resulted in a403 Forbidden
response: {"serviceErrorCode":100,"message":"Not enough permissions to access: GET /me","status":403}
It seems I have no permissions to perform an API request to the endpoint even though I correctly authenticated and have added the correct scopes. Some suggest you need permission from LinkedIn itself in order to perform the API request but that seems a tad old and it would be surprising if that was still the case: https://stackoverflow.com/questions/46960458/any-queries-to-the-api-linkedin-com-v2-return-not-enough-permissions-to-access
Also see my answer on a StackOverflow issue here: https://stackoverflow.com/questions/50363237/not-enough-permissions-to-access-me-get/50943840#comment94493533_50943840
Here's what I have so far:
<?php
namespace Laravel\Socialite\Two;
use Illuminate\Support\Arr;
class LinkedInProvider extends AbstractProvider implements ProviderInterface
{
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['r_basicprofile', 'r_emailaddress'];
/**
* The separating character for the requested scopes.
*
* @var string
*/
protected $scopeSeparator = ' ';
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state);
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://www.linkedin.com/oauth/v2/accessToken';
}
/**
* Get the POST fields for the token request.
*
* @param string $code
* @return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* {@inheritdoc}
*/
protected function getUserByToken($token)
{
$basicProfile = $this->getBasicProfile($token);
$emailAddress = $this->getEmailAddress($token);
return array_merge($basicProfile, $emailAddress);
}
/**
* Get the basic profile fields for the user.
*
* @param string $token
* @return array
*/
protected function getBasicProfile($token)
{
$url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))';
$response = $this->getHttpClient()->get($url, [
'headers' => [
'Authorization' => 'Bearer '.$token,
'X-RestLi-Protocol-Version' => '2.0.0',
],
]);
return json_decode($response->getBody(), true);
}
/**
* Get the email address for the user.
*
* @param string $token
* @return array
*/
protected function getEmailAddress($token)
{
$url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))';
$response = $this->getHttpClient()->get($url, [
'headers' => [
'Authorization' => 'Bearer '.$token,
'X-RestLi-Protocol-Version' => '2.0.0',
],
]);
return json_decode($response->getBody(), true)['handle~'];
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
$name = Arr::get($user, 'firstName.localized.en_US').' '.Arr::get($user, 'lastName.localized.en_US');
return (new User)->setRaw($user)->map([
'id' => $user['id'],
'nickname' => null,
'name' => $name,
'email' => Arr::get($user, 'emailAddress'),
// 'avatar' => Arr::get($user, 'pictureUrl'),
// 'avatar_original' => Arr::get($user, 'pictureUrls.values.0'),
]);
}
}
I was trying to do a request to see what the output of the profilePicture
field would be because at the moment I have no clue how the Asset Playable Streams work: https://docs.microsoft.com/en-us/linkedin/shared/references/v2/digital-media-asset#asset-playable-streams-table
This all looks super complex and far fetched if you ask me. Any help is greatly appreciated.
Our application does not have a problem right now (not sure if it did earlier). The linked update says v1 to be retired in March, so it sounds like they may have just messed up by breaking this earlier?
@driesvints Try using r_liteprofile
instead of r_basicprofile
. Their v2 API mentions nothing about r_basicprofile
when it comes to Sign In with LinkedIn:
When requesting an authorization code in Step 2 of the OAuth 2.0 Guide, make sure to request the
r_liteprofile
and/orr_emailaddress
scopes!
https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin
@tomsisk thanks. That wasn't really obvious at the beginning because with the r_basicprofile
you're suppose to get much more fields. I've updated it to that scope and it now works 👍
I'll send in a PR in a bit.
Here it is: https://github.com/laravel/socialite/pull/310
Merged and should be fixed with the next major release.
Okay thank @driesvints and everyone for your time answer my question. I will try that
Is it possible to update this in the v3 branch? My site can't be updated past Laravel 5.4/
@chrisGeonet no since v3.0 uses the old implementation and it'll break for people who are still on it. You'll have to upgrade to 4.0.
Sorry, I don't understand your reasoning. Are you saying that if the V3 code is updated, it will break existing V3 implementations? It will break anyway when LinkedIn discontinue the V1 API on March 1st.
The Google authentication was updated on V3 when the GooglePlus API was discontinued.
Upgrading isn't always an option for older sites.
@chrisGeonet if we replace the current code on v3 with the new LinkedIn integration, everyone who uses the current old LinkedIn integration will see their apps break. If you want to use the new LinkedIn integration you'll need to update to v4.
You can create a custom Provider for Socialite and use @driesvints code. Works fine. Thanks mate!
@Krato What does that provider look like and how did you implement it?
Did you call it in EventServiceProvider.php?
This is what I have:
<?php
namespace App\Providers;
use Illuminate\Support\Arr;
use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;
class LinkedInServiceProvider extends AbstractProvider implements ProviderInterface
{
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['r_basicprofile', 'r_emailaddress'];
/**
* The separating character for the requested scopes.
*
* @var string
*/
protected $scopeSeparator = ' ';
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state);
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://www.linkedin.com/oauth/v2/accessToken';
}
/**
* Get the POST fields for the token request.
*
* @param string $code
* @return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* {@inheritdoc}
*/
protected function getUserByToken($token)
{
$basicProfile = $this->getBasicProfile($token);
$emailAddress = $this->getEmailAddress($token);
return array_merge($basicProfile, $emailAddress);
}
/**
* Get the basic profile fields for the user.
*
* @param string $token
* @return array
*/
protected function getBasicProfile($token)
{
$url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))';
$response = $this->getHttpClient()->get($url, [
'headers' => [
'Authorization' => 'Bearer '.$token,
'X-RestLi-Protocol-Version' => '2.0.0',
],
]);
return json_decode($response->getBody(), true);
}
/**
* Get the email address for the user.
*
* @param string $token
* @return array
*/
protected function getEmailAddress($token)
{
$url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))';
$response = $this->getHttpClient()->get($url, [
'headers' => [
'Authorization' => 'Bearer '.$token,
'X-RestLi-Protocol-Version' => '2.0.0',
],
]);
return json_decode($response->getBody(), true)['handle~'];
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
$name = Arr::get($user, 'firstName.localized.en_US').' '.Arr::get($user, 'lastName.localized.en_US');
return (new User)->setRaw($user)->map([
'id' => $user['id'],
'nickname' => null,
'name' => $name,
'email' => Arr::get($user, 'emailAddress'),
// 'avatar' => Arr::get($user, 'pictureUrl'),
// 'avatar_original' => Arr::get($user, 'pictureUrls.values.0'),
]);
}
}
Here is my EventServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
'App\Events\SomeEvent' => [
'App\Listeners\EventListener',
],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
'SocialiteProviders\YouTube\YouTubeExtendSocialite@handle',
'SocialiteProviders\Twitch\TwitchExtendSocialite@handle',
'SocialiteProviders\Instagram\InstagramExtendSocialite@handle',
'App\Providers\LinkedInServiceProvider',
'SocialiteProviders\ThirtySevenSignals\ThirtySevenSignalsExtendSocialite@handle',
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//
}
}
And I get the following error:
Unresolvable dependency resolving [Parameter #1 [ <required> $clientId ]] in class Laravel\Socialite\Two\AbstractProvider
@jeremykenedy I use a custom social provider and a custom controller. Check this guide: https://www.laragle.com/2017/03/step-by-step-laravel-social-login.html
And use this class using your own namespace:
<?php
namespace App\Social;
use Illuminate\Support\Arr;
use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;
use Laravel\Socialite\Two\User;
class LinkedinV2ServiceProvider extends AbstractProvider implements ProviderInterface
{
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['r_liteprofile', 'r_emailaddress'];
/**
* The separating character for the requested scopes.
*
* @var string
*/
protected $scopeSeparator = ' ';
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state);
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://www.linkedin.com/oauth/v2/accessToken';
}
/**
* Get the POST fields for the token request.
*
* @param string $code
* @return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* {@inheritdoc}
*/
protected function getUserByToken($token)
{
$basicProfile = $this->getBasicProfile($token);
$emailAddress = $this->getEmailAddress($token);
return array_merge($basicProfile, $emailAddress);
}
/**
* Get the basic profile fields for the user.
*
* @param string $token
* @return array
*/
protected function getBasicProfile($token)
{
$url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))';
$response = $this->getHttpClient()->get($url, [
'headers' => [
'Authorization' => 'Bearer '.$token,
'X-RestLi-Protocol-Version' => '2.0.0',
],
]);
return json_decode($response->getBody(), true);
}
/**
* Get the email address for the user.
*
* @param string $token
* @return array
*/
protected function getEmailAddress($token)
{
$url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))';
$response = $this->getHttpClient()->get($url, [
'headers' => [
'Authorization' => 'Bearer '.$token,
'X-RestLi-Protocol-Version' => '2.0.0',
],
]);
return json_decode($response->getBody(), true)['elements'][0]['handle~'];
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
$name = Arr::get($user, 'firstName.localized.en_US').' '.Arr::get($user, 'lastName.localized.en_US');
$images = Arr::get($user, 'profilePicture.displayImage~.elements');
$avatar = Arr::first(Arr::where($images, function ($image) {
return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 100;
}));
$originalAvatar = Arr::first(Arr::where($images, function ($image) {
return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 800;
}));
return (new User())->setRaw($user)->map([
'id' => $user['id'],
'nickname' => null,
'name' => $name,
'email' => Arr::get($user, 'emailAddress'),
'avatar' => Arr::get($avatar, 'identifiers.0.identifier'),
'avatar_original' => Arr::get($originalAvatar, 'identifiers.0.identifier'),
]);
}
}
And extend socialite providers in your AppServiceProvider or in your own provider to override LinkedIn provider
$socialite = $this->app->make('Laravel\Socialite\Contracts\Factory');
$socialite->extend(
'linkedinv2',
function ($app) use ($socialite) {
$config = $app['config']['services.linkedin'];
return $socialite->buildProvider(LinkedinV2ServiceProvider::class, $config);
}
);
Regards!
@Krato I follow the same but getting above error. Can you please help me for the same. I'm using laravel 5.1
Hi I had the same problem on Socialite 3.3
I solved it, I changed the file LinkedInProvider.php with the source code below
/ vendor/laravel/socialite/src/two/LinkedInProvider /
`<?php
namespace Laravel\Socialite\Two;
use Illuminate\Support\Arr;
class LinkedInProvider extends AbstractProvider implements ProviderInterface { /**
/**
/**
/**
/**
/**
{@inheritdoc} */ protected function getUserByToken($token) { $basicProfile = $this->getBasicProfile($token); $emailAddress = $this->getEmailAddress($token);
return array_merge($basicProfile, $emailAddress); }
/**
@return array */ protected function getBasicProfile($token) { $url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))';
$response = $this->getHttpClient()->get($url, [ 'headers' => [ 'Authorization' => 'Bearer '.$token, 'X-RestLi-Protocol-Version' => '2.0.0', ], ]);
return (array) json_decode($response->getBody(), true); }
/**
@return array / protected function getEmailAddress($token) { $url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements(handle~))';
$response = $this->getHttpClient()->get($url, [ 'headers' => [ 'Authorization' => 'Bearer '.$token, 'X-RestLi-Protocol-Version' => '2.0.0', ], ]);
return (array) Arr::get((array) json_decode($response->getBody(), true), 'elements.0.handle~'); }
/**
{@inheritdoc} */ protected function mapUserToObject(array $user) { $preferredLocale = Arr::get($user, 'firstName.preferredLocale.language').'_'.Arr::get($user, 'firstName.preferredLocale.country'); $firstName = Arr::get($user, 'firstName.localized.'.$preferredLocale); $lastName = Arr::get($user, 'lastName.localized.'.$preferredLocale);
$images = (array) Arr::get($user, 'profilePicture.displayImage~.elements', []); $avatar = Arr::first(Arr::where($images, function ($image) { return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 100; })); $originalAvatar = Arr::first(Arr::where($images, function ($image) { return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 800; }));
return (new User)->setRaw($user)->map([ 'id' => $user['id'], 'nickname' => null, 'name' => $firstName.' '.$lastName, 'first_name' => $firstName, 'last_name' => $lastName, 'email' => Arr::get($user, 'emailAddress'), 'avatar' => Arr::get($avatar, 'identifiers.0.identifier'), 'avatar_original' => Arr::get($originalAvatar, 'identifiers.0.identifier'), ]); }
}`
Hi I had the same problem on Socialite 3.3
I solved it, I changed the file LinkedInProvider.php with the source code below
/ vendor/laravel/socialite/src/two/LinkedInProvider /
`<?php
namespace Laravel\Socialite\Two;
use Illuminate\Support\Arr;
class LinkedInProvider extends AbstractProvider implements ProviderInterface { /* The scopes being requested. @var array */ protected $scopes = ['r_liteprofile', 'r_emailaddress'];
/**
- The separating character for the requested scopes.
- @var string */ protected $scopeSeparator = ' ';
/**
- {@inheritdoc} */ protected function getAuthUrl($state) { return $this->buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state); }
/**
- {@inheritdoc} */ protected function getTokenUrl() { return 'https://www.linkedin.com/oauth/v2/accessToken'; }
/**
- Get the POST fields for the token request.
- @param string $code
- @return array */ protected function getTokenFields($code) { return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; }
/**
- {@inheritdoc} */ protected function getUserByToken($token) { $basicProfile = $this->getBasicProfile($token); $emailAddress = $this->getEmailAddress($token); return array_merge($basicProfile, $emailAddress); }
/**
- Get the basic profile fields for the user.
- @param string $token
- @return array */ protected function getBasicProfile($token) { $url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))'; $response = $this->getHttpClient()->get($url, [ 'headers' => [ 'Authorization' => 'Bearer '.$token, 'X-RestLi-Protocol-Version' => '2.0.0', ], ]); return (array) json_decode($response->getBody(), true); }
/**
- Get the email address for the user.
- @param string $token
- @return array / protected function getEmailAddress($token) { $url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements(handle~))'; $response = $this->getHttpClient()->get($url, [ 'headers' => [ 'Authorization' => 'Bearer '.$token, 'X-RestLi-Protocol-Version' => '2.0.0', ], ]); return (array) Arr::get((array) json_decode($response->getBody(), true), 'elements.0.handle~'); }
/**
- {@inheritdoc} */ protected function mapUserToObject(array $user) { $preferredLocale = Arr::get($user, 'firstName.preferredLocale.language').'_'.Arr::get($user, 'firstName.preferredLocale.country'); $firstName = Arr::get($user, 'firstName.localized.'.$preferredLocale); $lastName = Arr::get($user, 'lastName.localized.'.$preferredLocale); $images = (array) Arr::get($user, 'profilePicture.displayImage~.elements', []); $avatar = Arr::first(Arr::where($images, function ($image) { return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 100; })); $originalAvatar = Arr::first(Arr::where($images, function ($image) { return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 800; })); return (new User)->setRaw($user)->map([ 'id' => $user['id'], 'nickname' => null, 'name' => $firstName.' '.$lastName, 'first_name' => $firstName, 'last_name' => $lastName, 'email' => Arr::get($user, 'emailAddress'), 'avatar' => Arr::get($avatar, 'identifiers.0.identifier'), 'avatar_original' => Arr::get($originalAvatar, 'identifiers.0.identifier'), ]); }
}`
This works for me.Thanks for the answer
I ran into this problem also. i didn't figure out guys already updated the provider. there are two linkedin providers now I used 'linkedin-openid' instead of 'linkedin' Socialite::driver('linkedin-openid') also make sure to add an entry for linkedin-openid in the service file config
I got this error when I try to login with my LinkedIn Account
laravel/socialite": "^3.1"
and here is script
$userSocial = Socialite::driver('linkedin')->user();
here is the developer update from linkedin (https://engineering.linkedin.com/blog/2018/12/developer-program-updates)
Client error:
GET https://api.linkedin.com/v1/people/~:(id,first-name,last-name,formatted-name,email-address,headline,location,industry,public-profile-url,picture-url,picture-urls::(original))
resulted in a410 Gone
response: { "errorCode": 0, "message": "This resource is no longer available under v1 APIs", "requestId": "3WIBWBXOPW", "s (truncated...)How to fix this issue ?
Thank you