Closed chernovpavl closed 3 years ago
Just checking to see if there are any plans to update this or if we should find another workaround?
I just added custom driver for linkedin at this moment
@CHERiPP cool, did you use any documentation on creating a custom driver? I've only seen this thread and the instructions are lacking for me.
No, I didn't find any documentation. I wrote Linkedinv2 driver and extended AllyManager in hooks.js file. I also had to rewrite AllyUser and use it, because linkedin api has a slightly different set of fields
const { hooks } = require('@adonisjs/ignitor')
const AllyManager = require('../node_modules/@adonisjs/ally/src/AllyManager')
const LinkedInV2 = require('../app/Classes/Ally/Drivers/LinkedinV2')
hooks.before.providersBooted(() => {
AllyManager.extend('linkedinv2', LinkedInV2)
})
Hey all, 👋
May you provide a PR with the needed change @CHERiPP?
I don't have dev application in linkedin that would have access to any information other than first name, last name and email of user. Therefore, my version of the driver is limited in functionality and does not support, for example, downloading a picture profile. I don't think that this option is acceptable for PR.
I don't have dev application in linkedin that would have access to any information other than first name, last name and email of user. Therefore, my version of the driver is limited in functionality and does not support, for example, downloading a picture profile. I don't think that this option is acceptable for PR.
Your version of the driver would suit my needs. Can you please send it to me at zorandavidovic@live.com. Thanks in advance CHERiPP!
This is my driver implementation:
'use strict'
const got = require('got')
const loc = '../../../../node_modules/@adonisjs/ally/'
const CE = require(loc + 'src/Exceptions')
const OAuth2Scheme = require(loc + 'src/Schemes/OAuth2')
const utils = require(loc + 'lib/utils')
const AllyUser = require('../AllyUser')
const _ = require('lodash')
/**
* LinkedIn driver to authenticating users via OAuth2Scheme.
*
* @class LinkedIn
* @constructor
*/
class LinkedInV2 extends OAuth2Scheme {
constructor (Config) {
const config = Config.get('services.ally.linkedinv2')
utils.validateDriverConfig('linkedinv2', config)
utils.debug('linkedinv2', config)
super(config.clientId, config.clientSecret, config.headers)
this._redirectUri = config.redirectUri
this._redirectUriOptions = _.merge({ response_type: 'code' }, config.options)
this.scope = _.size(config.scope) ? config.scope : ['r_liteprofile', 'r_emailaddress']
this.fields = _.size(config.fields) ? config.fields : ['id', 'firstName', 'lastName']
}
/**
* Injections to be made by the IoC container.
*
* @attribute inject
*
* @return {Array}
*/
static get inject () {
return ['Adonis/Src/Config']
}
/**
* Returns a boolean telling if driver supports
* state
*
* @method supportStates
*
* @return {Boolean}
*/
get supportStates () {
return true
}
/**
* Scope seperator for seperating multiple
* scopes.
*
* @attribute scopeSeperator
*
* @return {String}
*/
get scopeSeperator () {
return ' '
}
/**
* Base url to be used for constructing
* linkedin oauth urls.
*
* @attribute baseUrl
*
* @return {String}
*/
get baseUrl () {
return 'https://www.linkedin.com/oauth/v2'
}
/**
* Relative url to be used for redirecting
* user.
*
* @attribute authorizeUrl
*
* @return {String}
*/
get authorizeUrl () {
return 'authorization'
}
/**
* Relative url to be used for exchanging
* access token.
*
* @attribute accessTokenUrl
*
* @return {String}
*/
get accessTokenUrl () {
return 'accessToken'
}
/**
* Returns the user profile as an object using the
* access token.
*
* @attribute _getUserProfile
*
* @param {String} accessToken
*
* @return {Object}
*
* @private
*/
async _getUserProfile (accessToken) {
const profileUrl = `https://api.linkedin.com/v2/me?fields=${this.fields.join(',')}`
const response = await got(profileUrl, {
headers: {
'x-li-format': 'json',
Authorization: `Bearer ${accessToken}`
},
json: true
})
return response.body
}
/**
* Returns the user email as an object using the
* access token.
*
* @attribute _getUserEmail
*
* @param {String} accessToken
*
* @return {Object}
*
* @private
*/
async _getUserEmail (accessToken) {
const emailUrl =
'https://api.linkedin.com/v2/clientAwareMemberHandles?q=members&projection=(elements*(primary,type,handle~))'
const response = await got(emailUrl, {
headers: {
'x-li-format': 'json',
Authorization: `Bearer ${accessToken}`
},
json: true
})
return response.body
}
/**
* Normalize the user profile response and build an Ally user.
*
* @param {object} userProfile
* @param {object} accessTokenResponse
*
* @return {object}
*
* @private
*/
_buildAllyUser (userProfile, email, accessTokenResponse) {
const user = new AllyUser()
const expires = _.get(accessTokenResponse, 'result.expires_in')
const emailAddress = email.elements.find(e => e.type === 'EMAIL')
user
.setOriginal(userProfile)
.setFields(
userProfile.id,
userProfile.firstName.localized[Object.keys(userProfile.firstName.localized)[0]],
userProfile.lastName.localized[Object.keys(userProfile.lastName.localized)[0]],
emailAddress && emailAddress['handle~'] && emailAddress['handle~'].emailAddress,
null,
null
)
.setToken(
accessTokenResponse.accessToken,
accessTokenResponse.refreshToken,
null,
expires ? Number(expires) : null
)
return user
}
/**
* Returns the redirect url for a given provider.
*
* @method getRedirectUrl
*
* @param {String} [state]
*
* @return {String}
*/
async getRedirectUrl (state) {
const options = state
? Object.assign(this._redirectUriOptions, { state })
: this._redirectUriOptions
return this.getUrl(this._redirectUri, this.scope, options)
}
/**
* Parses the redirect errors returned by linkedin
* and returns the error message.
*
* @method parseRedirectError
*
* @param {Object} queryParams
*
* @return {String}
*/
parseRedirectError (queryParams) {
return queryParams.error_description || 'Oauth failed during redirect'
}
/**
* Returns the user profile with it's access token, refresh token
* and token expiry.
*
* @method getUser
* @async
*
* @param {Object} queryParams
* @param {String} [originalState]
*
* @return {Object}
*/
async getUser (queryParams, originalState) {
const code = queryParams.code
const state = queryParams.state
/**
* Throw an exception when query string does not have
* code.
*/
if (!code) {
const errorMessage = this.parseRedirectError(queryParams)
throw CE.OAuthException.tokenExchangeException(errorMessage, null, errorMessage)
}
/**
* Valid state with original state
*/
if (state && originalState !== state) {
throw CE.OAuthException.invalidState()
}
const accessTokenResponse = await this.getAccessToken(code, this._redirectUri, {
grant_type: 'authorization_code'
})
const userProfile = await this._getUserProfile(accessTokenResponse.accessToken)
const emailAddress = await this._getUserEmail(accessTokenResponse.accessToken)
return this._buildAllyUser(userProfile, emailAddress, accessTokenResponse)
}
/**
* Get user by access token
*
* @method getUserByToken
*
* @param {String} accessToken
*
* @return {void}
*/
async getUserByToken (accessToken) {
const userProfile = await this._getUserProfile(accessToken)
return this._buildAllyUser(userProfile, null, { accessToken, refreshToken: null })
}
}
module.exports = LinkedInV2
Thank you, Pavel
🙂
From: Pavel Chernov notifications@github.com Sent: Tuesday, June 4, 2019 2:37 PM To: adonisjs/adonis-ally Cc: Zoran Davidović; Comment Subject: Re: [adonisjs/adonis-ally] LinkedIn login not working (api v2) (#69)
This is my driver implementation:
'use strict'
const got = require('got') const loc = '../../../../nodemodules/@adonisjs/ally/' const CE = require(loc + 'src/Exceptions') const OAuth2Scheme = require(loc + 'src/Schemes/OAuth2') const utils = require(loc + 'lib/utils') const AllyUser = require('../AllyUser') const = require('lodash')
/**
@constructor */ class LinkedInV2 extends OAuth2Scheme { constructor (Config) { const config = Config.get('services.ally.linkedinv2')
utils.validateDriverConfig('linkedinv2', config) utils.debug('linkedinv2', config)
super(config.clientId, config.clientSecret, config.headers)
this._redirectUri = config.redirectUri this.redirectUriOptions = .merge({ response_type: 'code' }, config.options)
this.scope = _.size(config.scope) ? config.scope : ['r_liteprofile', 'remailaddress'] this.fields = .size(config.fields) ? config.fields : ['id', 'firstName', 'lastName'] }
/**
/**
/**
/**
/**
/**
/**
https://api.linkedin.com/v2/me?fields=${this.fields.join(',')}
const response = await got(profileUrl, {
headers: {
'x-li-format': 'json',
Authorization: Bearer ${accessToken}
},
json: true
})
return response.body }
/**
const response = await got(emailUrl, {
headers: {
'x-li-format': 'json',
Authorization: Bearer ${accessToken}
},
json: true
})
return response.body }
/**
user .setOriginal(userProfile) .setFields( userProfile.id, userProfile.firstName.localized[Object.keys(userProfile.firstName.localized)[0]], userProfile.lastName.localized[Object.keys(userProfile.lastName.localized)[0]], emailAddress && emailAddress['handle~'] && emailAddress['handle~'].emailAddress, null, null ) .setToken( accessTokenResponse.accessToken, accessTokenResponse.refreshToken, null, expires ? Number(expires) : null )
return user }
/**
return this.getUrl(this._redirectUri, this.scope, options) }
/**
/**
/**
/**
const accessTokenResponse = await this.getAccessToken(code, this._redirectUri, { grant_type: 'authorization_code' })
const userProfile = await this._getUserProfile(accessTokenResponse.accessToken) const emailAddress = await this._getUserEmail(accessTokenResponse.accessToken) return this._buildAllyUser(userProfile, emailAddress, accessTokenResponse) }
/**
return this._buildAllyUser(userProfile, null, { accessToken, refreshToken: null }) } }
module.exports = LinkedInV2
— You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/adonisjs/adonis-ally/issues/69?email_source=notifications&email_token=AHGEB5CUBI5DWWYHAXGG6JDPYZOW7A5CNFSM4HLIBJTKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODW4NOXI#issuecomment-498653021, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AHGEB5ASME46M7LA4U42VG3PYZOW7ANCNFSM4HLIBJTA.
can't use linked driver how to fix?
Can you provide any details or errors?
how do you implement the linkedInV2 ??
any update on this..?
@CHERiPP thanks for the solution.
For any who also need profile picture:
// in constructor add profilePicture field
this.fields = _.size(config.fields) ?
config.fields :
['id', 'firstName', 'lastName', 'profilePicture(displayImage~:playableStreams)']
// in _getUserProfile method change request from:
const profileUrl = `https://api.linkedin.com/v2/me?fields=${this.fields.join(',')}`
// to:
const profileUrl = `https://api.linkedin.com/v2/me?projection=(${this.fields.join(',')})`
// in _buildAllyUser method, get image url from userProfile response
const avatarUrl = _.get(userProfile, 'profilePicture[displayImage~].elements[0].identifiers[0].identifier')
// yeah it is buried very deep: https://docs.microsoft.com/en-us/linkedin/shared/references/v2/profile/profile-picture
// then pass it to the setFields method, my version:
user
.setOriginal(userProfile)
.setFields(
userProfile.id,
userProfile.firstName.localized[Object.keys(userProfile.firstName.localized)[0]],
userProfile.lastName.localized[Object.keys(userProfile.lastName.localized)[0]],
emailAddress && emailAddress['handle~'] && emailAddress['handle~'].emailAddress,
avatarUrl,
)
.setToken(
accessTokenResponse.accessToken,
accessTokenResponse.refreshToken,
null,
expires ? Number(expires) : null
)
Also, few changes are required in AllyUser class:
// in constructor, update _userFields object:
this._userFields = {
id: '',
name: '',
email: '',
avatar: '',
}
// Update setFields method:
setFields(id, firstName, lastName, email, avatar) {
this._userFields = { id, name: `${firstName} ${lastName}`, email, avatar }
return this
}
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
LinkedIn login not working for api v2
The LinkedIn driver uses the v1 version of the API. And new applications do not work with this version of api.
All new applications created on the LinkedIn Developer Platform as of January 14, 2019 can use LinkedIn's v2 APIs. Starting May 1, 2019, LinkedIn will deprecate use of its v1 APIs. If your developer application currently depends on LinkedIn v1 APIs, see the frequently asked questions below before migrating to LinkedIn v2 APIs.
Source: https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/migration-faq?context=linkedin/consumer/context