Adding state parameter causes malformed URL
Closed this issue · 5 comments
I'm building a simple intgration for LinkedIn. Everything is working except that there it generates a slightly malformed URL. The authorize endpoint ends up looking like this:
https://www.linkedin.com/uas/oauth2/authorization&state=rfvjuhwswetbcdwzfeoghnxhovhjjz?client_id=7730si35gvu8rr&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fpage%2Flinkedin%2Fcallback
The ampersand after the word authorization
needs to be a question mark, and the question mark after the state parameter needs to be an ampersand.
I've tracked the source of the error down to this piece of the code: https://github.com/thoughtbot/yesod-auth-oauth2/blob/4afaba6645a0df69c4cf0760ff7ad56c69057bc2/Yesod/Auth/OAuth2.hs#L89-L90
My code that uses the library looks like this:
oauth2LinkedIn clientId clientSecret = authOAuth2 "linkedin"
OAuth2
{ oauthClientId = encodeUtf8 clientId
, oauthClientSecret = encodeUtf8 clientSecret
, oauthOAuthorizeEndpoint = "https://www.linkedin.com/uas/oauth2/authorization"
, oauthAccessTokenEndpoint = "https://www.linkedin.com/uas/oauth2/accessToken"
, oauthCallback = Nothing
}
$ fromProfileURL
"linkedin"
"https://api.linkedin.com/v1/people/~?format=json"
credsFromLinkedInUser
Thanks for reporting this; I think a number of plugins may have been broken in this way for quite some time (since the state
parameter was introduced). I've got what I hope is a fix in the referenced PR.
I tested the commit the referenced, and it does fix the issue.
@andrewthad How did you make this work? I have code like this:
data LinkedInUser = LinkedInUser
{ linkedinUserGivenName :: Text
, linkedinUserFamilyName :: Text
, linkedinUserEmail :: Text
}
instance FromJSON LinkedInUser where
parseJSON (Object v) = LinkedInUser
<$> v .: "firstName"
<*> v .: "lastName"
<*> v .: "emailAddress"
parseJSON _ = empty
oauth2LinkedIn :: YesodAuth m => Text -> Text -> AuthPlugin m
oauth2LinkedIn clientId clientSecret= authOAuth2 "linkedin" oauth makeCredentials
where
oauth = OAuth2
{ oauthClientId = encodeUtf8 clientId
, oauthClientSecret = encodeUtf8 clientSecret
, oauthOAuthorizeEndpoint = "https://www.linkedin.com/oauth/v2/authorization"
, oauthAccessTokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken"
, oauthCallback = Nothing
}
makeCredentials = fromProfileURL
"linkedin"
"https://api.linkedin.com/v1/people/~?format=json"
credsFromLinkedInUser
credsFromLinkedInUser :: LinkedInUser -> Creds m
credsFromLinkedInUser user = Creds
{ credsPlugin = "linkedin"
, credsIdent = linkedinUserEmail user
}
…But every time I try to authenticate, it fails on the callback with:
StatusCodeException (Status {statusCode = 401, statusMessage = "Unauthorized"}) [("Server","Apache-Coyote/1.1"),("x-li-request-id","BSFLTA89H2"),("Date","Tue, 06 Dec 2016 10:45:08 GMT"),("Vary","*"),("x-li-format","json"),("Content-Type","application/json;charset=UTF-8"),("Content-Encoding","gzip"),("X-Li-Fabric","prod-lor1"),("Transfer-Encoding","chunked"),("Connection","keep-alive"),("X-Li-Pop","PROD-IDB2"),("Set-Cookie","lidc=\"b=OB95:g=26:u=41:i=1481021109:t=1481100618:s=AQEUH5OqBzP-4npQrvrCuWNG2j364fgI\"; Expires=Wed, 07 Dec 2016 08:50:18 GMT; domain=.linkedin.com; Path=/"),("X-LI-UUID","0eb8EdqkjRSAoJJFDisAAA=="),("X-Response-Body-Start","{\n \"errorCode\": 0,\n \"message\": \"Unable to verify access token\",\n \"requestId\": \"BSFLTA89H2\",\n \"status\": 401,\n \"timestamp\": 1481021109219\n}"),("X-Request-URL","GET https://api.linkedin.com:443/v1/people/~?format=json")] (CJ {expose = [Cookie {cookie_name = "lidc", cookie_value = "\"b=OB95:g=26:u=41:i=1481021109:t=1481100618:s=AQEUH5OqBzP-4npQrvrCuWNG2j364fgI\"", cookie_expiry_time = 3016-04-08 00:00:00 UTC, cookie_domain = "linkedin.com", cookie_path = "/", cookie_creation_time = 2016-12-06 10:45:09.264807 UTC, cookie_last_access_time = 2016-12-06 10:45:09.264807 UTC, cookie_persistent = False, cookie_host_only = False, cookie_secure_only = False, cookie_http_only = False}]})
I found that apparently LinkedIn doesn't do OAuth2 properly: http://stackoverflow.com/questions/28094926/linkedin-oauth2-unable-to-verify-access-token
And here I found some code that may encourage LinkedIn to behave: https://github.com/ip1981/sproxy2/blob/master/src/Sproxy/Application/OAuth2/LinkedIn.hs
Here is my code from, what is at this point in time, a fairly old project that I am barely maintaining. I have no recollection of even writing it. Also, this feature was only really used once about a year ago, and LinkedIn's API may have changed since I wrote this, so it might not actually work any more. The main difference I can see is that we are using different endpoints:
data LinkedInUser = LinkedInUser
{ linkedInUserId :: Text
}
instance FromJSON LinkedInUser where
parseJSON (Object o) = LinkedInUser
<$> o .: "id"
parseJSON _ = mzero
-- Should probably improve the information that is retrieved
oauth2LinkedIn ::
( YesodAuth m
) => Text -- ^ Client ID
-> Text -- ^ Client Secret
-> AuthPlugin m
oauth2LinkedIn clientId clientSecret = authOAuth2 "linkedin"
OAuth2
{ oauthClientId = encodeUtf8 clientId
, oauthClientSecret = encodeUtf8 clientSecret
, oauthOAuthorizeEndpoint = "https://www.linkedin.com/uas/oauth2/authorization"
, oauthAccessTokenEndpoint = "https://www.linkedin.com/uas/oauth2/accessToken"
, oauthCallback = Nothing
}
$ fromProfileURL
"linkedin"
"https://api.linkedin.com/v1/people/~?format=json"
credsFromLinkedInUser
credsFromLinkedInUser :: LinkedInUser -> Creds m
credsFromLinkedInUser u = Creds
{ credsPlugin = "linkedin"
, credsIdent = linkedInUserId u
, credsExtra = []
}
@andrewthad Wow, thanks for getting back to me so quickly.
Yes, I spotted the endpoint difference, and I realised that it works with the ones you use and not with the ones in LinkedIn's documentation.
I'll see if I can pull out some more user information, and then I'll most likely submit a PR to this repo for a LinkedIn plugin.