Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with oAuth 2.0 LinkedIn Upgrade API Web Service #309

Closed
noeurphireak opened this issue Dec 17, 2018 · 18 comments
Closed

Issue with oAuth 2.0 LinkedIn Upgrade API Web Service #309

noeurphireak opened this issue Dec 17, 2018 · 18 comments
Labels

Comments

@noeurphireak
Copy link

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 a 410 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

@noeurphireak noeurphireak changed the title Issue with oAuth 2.0 LinkedIn Upgrade API service Issue with oAuth 2.0 LinkedIn Upgrade API Web Service Dec 17, 2018
@driesvints driesvints added the bug label Dec 17, 2018
@riasvdv
Copy link

riasvdv commented Dec 17, 2018

+1 All social logins with LinkedIn are now broken

@driesvints
Copy link
Member

driesvints commented Dec 17, 2018

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 a 403 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.

@tomsisk
Copy link

tomsisk commented Dec 17, 2018

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/or r_emailaddress scopes!

https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin

@driesvints
Copy link
Member

@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.

@driesvints
Copy link
Member

Here it is: #310

@driesvints
Copy link
Member

Merged and should be fixed with the next major release.

@noeurphireak
Copy link
Author

Okay thank @driesvints and everyone for your time answer my question. I will try that

@chrisGeonet
Copy link

Is it possible to update this in the v3 branch? My site can't be updated past Laravel 5.4/

@driesvints
Copy link
Member

@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.

@chrisGeonet
Copy link

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.

@driesvints
Copy link
Member

@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.

@Krato
Copy link

Krato commented Jul 2, 2019

You can create a custom Provider for Socialite and use @driesvints code. Works fine. Thanks mate!

@jeremykenedy
Copy link

jeremykenedy commented Aug 1, 2019

@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

@Krato
Copy link

Krato commented Aug 1, 2019

@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!

@webexpert4rv
Copy link

image @Krato I follow the same but getting above error. Can you please help me for the same. I'm using laravel 5.1

@RDZU
Copy link

RDZU commented Sep 13, 2019

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 = ' ';

/**

/**

/**

  • 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);
    }

/**

/**

/**

  • {@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'),
    ]);
    }

}`

drakeford97 added a commit to drakeford97/Socialite that referenced this issue May 25, 2022
LinkedIn has suddenly stopped supported the previous endpoint we used to retrieve user profile information so we need to use the new API for authenticating users.

Fixes laravel/socialite#309
@bhumikaZen
Copy link

bhumikaZen commented Apr 4, 2023

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 = ' ';

/**

/**

/**

  • 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);
    }

/**

/**

/**

  • {@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

@mbh00
Copy link

mbh00 commented Mar 17, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests