I have a project that has a Python/Django backend. It sits behind Keycloak for authentication. Everything works, users can login using Keycloak.
We have a use case where a client needs to access some endpoints from the backend. We were hoping to provide the client with some sort of API Key so they can authenticate with Keycloak and reach the backend endpoints programmatically. After some research we thought of using an Offline Token. We are able to get the token and are trying to add the access token as the Bearer Token. The issue is we are unable to get past the login screen.
We are using Keycloak 17.0.0.
Here is the workflow in Python:
# Get the token
data = {
"client_id": <id>,
"client_secret": <secret>,
"username": "admin",
"password": "foo",
"grant_type": "password",
"scope": "openid offline_access"
}
response = requests.post('http://keycloak:8080/realms/<realm>/protocol/openid-connect/token', headers={'Content-Type': 'application/x-www-form-urlencoded'}, data=data)
# What is returned in the response
{
"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkWVQ3eWJOdWM3ODN5OU53d3VabmdTaHNSMV83T0xtQ2E5NW5HZHh5bXZvIn0.eyJleHAiOjE2NjI3NDU5NjgsImlhdCI6MTY2Mjc0NTY2OCwianRpIjoiZjcyZDBhYWUtOGFlMy00NjA1LTgzMWYtYzdiN2YzN2E2MzY1IiwiaXNzIjoiaHR0cDovL2tleWNsb2FrOjgwODAvcmVhbG1zL2FyY2FuZS1maXJlIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjJmNzM5ZGQ1LTQxZmUtNDVmNy1hYzBjLTM2NTU4N2MyNTE3MyIsInR5cCI6IkJlYXJlciIsImF6cCI6Im5naW54Iiwic2Vzc2lvbl9zdGF0ZSI6IjVjMGIzNGM2LTc2YWItNDJlNy04ZjBjLTljODEzNzA4ZjcwZCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1hcmNhbmUtZmlyZSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIG9mZmxpbmVfYWNjZXNzIGVtYWlsIiwic2lkIjoiNWMwYjM0YzYtNzZhYi00MmU3LThmMGMtOWM4MTM3MDhmNzBkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiQ2hyaXMiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJDaHJpcyJ9.J3oPEDWw_c-L6jMySSBF1uNV9mjl7d8AFwky8kN71qhTEJLDJFWKznIQ0sAMK8pGenbYOQFGDnAiL5E5BGh5g97jkv-PK1xSsvjKjIEAJuH15FGIeHB8RSth7ZYPcca-2kzRsqfs9ueeKbe1IAMlMcFdKgX3qJa3MPsLLYWVBI5QbxTf068sWxAoWNWCQzYfyTnsZKnnbGEHzMyHyTjKaFfCHj8Y2lTw1RXeEVdts2ck8OVg5B66NHxu4KHQqnS2t3EhhX-vsovctrZ-yyX_KXkv9uaZ8OUjbsPcAFr0Ta8vcK5ay-FyXfmcApwp6JptNBcL4M54OHcYAV3wmJS5Tg",
"expires_in":300,
"refresh_expires_in":0,
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlMWI1ZjRmNC0xY2RiLTQwZmUtYWI0OS0yODFmNWExNzE1Y2YifQ.eyJpYXQiOjE2NjI3NDU2NjgsImp0aSI6IjRkMDUwMWQyLTEwYzktNDcwYS1iNmExLWNmZTE3MGUwMzc5YyIsImlzcyI6Imh0dHA6Ly9rZXljbG9hazo4MDgwL3JlYWxtcy9hcmNhbmUtZmlyZSIsImF1ZCI6Imh0dHA6Ly9rZXljbG9hazo4MDgwL3JlYWxtcy9hcmNhbmUtZmlyZSIsInN1YiI6IjJmNzM5ZGQ1LTQxZmUtNDVmNy1hYzBjLTM2NTU4N2MyNTE3MyIsInR5cCI6Ik9mZmxpbmUiLCJhenAiOiJuZ2lueCIsInNlc3Npb25fc3RhdGUiOiI1YzBiMzRjNi03NmFiLTQyZTctOGYwYy05YzgxMzcwOGY3MGQiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIG9mZmxpbmVfYWNjZXNzIGVtYWlsIiwic2lkIjoiNWMwYjM0YzYtNzZhYi00MmU3LThmMGMtOWM4MTM3MDhmNzBkIn0.DOu_zKq5WFI4PcTn5Qpe-VFwOj-aLtvc3q9SACs51Ew",
"token_type":"Bearer",
"id_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkWVQ3eWJOdWM3ODN5OU53d3VabmdTaHNSMV83T0xtQ2E5NW5HZHh5bXZvIn0.eyJleHAiOjE2NjI3NDU5NjgsImlhdCI6MTY2Mjc0NTY2OCwiYXV0aF90aW1lIjowLCJqdGkiOiI0MDI5ZmI1ZC05ZDQwLTQ5NTQtYmNiMC02MmYzOTU2YjdmOTIiLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODA4MC9yZWFsbXMvYXJjYW5lLWZpcmUiLCJhdWQiOiJuZ2lueCIsInN1YiI6IjJmNzM5ZGQ1LTQxZmUtNDVmNy1hYzBjLTM2NTU4N2MyNTE3MyIsInR5cCI6IklEIiwiYXpwIjoibmdpbngiLCJzZXNzaW9uX3N0YXRlIjoiNWMwYjM0YzYtNzZhYi00MmU3LThmMGMtOWM4MTM3MDhmNzBkIiwiYXRfaGFzaCI6InNuVW11a3I1VUZWbTRyYVlxM1FxVlEiLCJhY3IiOiIxIiwic2lkIjoiNWMwYjM0YzYtNzZhYi00MmU3LThmMGMtOWM4MTM3MDhmNzBkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiQ2hyaXMiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJDaHJpcyJ9.AUGLQUiA8gyz_yEz5mShGdgVS9vQx_LbmJ6Xh9sEqTtIo9rDgflhKBlGK_b8V8KHir1NExtcuLAlSdMAwJWZ02IzMPNazkYEqxM_PMJh3nLXV6Q7Ph9a4BoPsN2xs6c9BxhlrXKgN1NChm38cKZHnBzw0ZlXcGTJfJdQvGjO8GGiXlZuzo9JioCByn-ZQvWtfepEHKZREx6rVcahSBM5PNG1i8GATRbAIxWpl88CRwv6r9OHXcvdjEEZo8Jl4yePumchyEo9NGMnf2Vk0Alp-cZv90AIO91uUAwo6a3P-iT-rJvk-tfJVed7XzxDqOaUA4ZMD9kCmZCyERolglV2QQ",
"not-before-policy":0,
"session_state":"5c0b34c6-76ab-42e7-8f0c-9c813708f70d",
"scope":"openid profile offline_access email"
}
# Make request to the backend endpoint
response = requests.get('http://localhost/api/foo', headers={'Authorization': 'Bearer ' + response.json().get('access_token')})
What gets returned in the response is just the Keycloak login page:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" class="login-pf">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Sign in to ***</title>
...
I'm not sure why we are not able to get past the login page. I thought if we provide the access token in the header we could get past the login page and access the backend endpoints.
Here are some of our relevant files:
dev.conf Nginx configuration
server {
listen 80;
server_name localhost;
charset utf-8;
client_max_body_size 10000m;
resolver 127.0.0.11 valid=30s ipv6=off;
resolver_timeout 10s;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# login with keycloak
access_by_lua_file /etc/nginx/lua/auth.lua;
...
auth.lua Lua file used in nginx configuration
local opts = {
redirect_uri_path = "/redirect_uri",
accept_none_alg = true,
discovery = "http://keycloak:8080/realms/<realm>/.well-known/openid-configuration",
client_id = <id>,
client_secret = <secret>,
redirect_uri_scheme = "http",
logout_path = "/logout",
redirect_after_logout_uri = "http://keycloak:8080/realms/<realm>/protocol/openid-connect/logout",
post_logout_redirect_uri = "http://localhost",
redirect_after_logout_with_id_token_hint = false,
revoke_tokens_on_logout = true,
session_contents = {id_token=true,access_token=true,user=true}
}
-- call introspect for OAuth 2.0 Bearer Access Token validation
local res, err = require("resty.openidc").authenticate(opts)
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- decode and send the user roles for this session
local jwt = require "resty.jwt"
local jwt_obj = jwt:load_jwt(res.access_token)
local cjson = require "cjson"
ngx.log(ngx.DEBUG, "res.access_token.sub=", cjson.encode(jwt_obj))
ngx.req.set_header("ACCESS-TOKEN", cjson.encode(jwt_obj))
Our Keycloak settings
We're unable to get past the login screen and access our backend endpoints. Is there something in our settings? I was wondering if its the logic in our Lua file. Also is there a better way for the client to access our endpoints?
Any help would be appreciated.
I was able to solve the issue. I had to change the code in the Lua file.
Here is what I changed it to:
local opts = {
redirect_uri_path = "/redirect_uri",
accept_none_alg = true,
discovery = "http://keycloak:8080/realms/<realm>/.well-known/openid-configuration",
client_id = <id>,
client_secret = <secret>,
redirect_uri_scheme = "http",
logout_path = "/logout",
redirect_after_logout_uri = "http://keycloak:8080/realms/<realm>/protocol/openid-connect/logout",
post_logout_redirect_uri = "http://localhost",
redirect_after_logout_with_id_token_hint = false,
revoke_tokens_on_logout = true,
session_contents = {id_token=true,access_token=true,user=true}
}
local oidc = require("resty.openidc")
-- call bearer_jwt_verify for OAuth 2.0 JWT validation
local res, err, access_token = oidc.bearer_jwt_verify(opts)
if err or not res then
-- call authenticate for OpenID Connect user authentication
res, err = oidc.authenticate(opts)
access_token = res.access_token
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
end
-- decode and send the user roles for this session
local jwt = require "resty.jwt"
local jwt_obj = jwt:load_jwt(access_token)
local cjson = require "cjson"
ngx.log(ngx.DEBUG, "res.access_token.sub=", cjson.encode(jwt_obj))
ngx.req.set_header("ACCESS-TOKEN", cjson.encode(jwt_obj))
I check for the bearer token using bearer_jwt_verify. If there is none set I then revert to authenticating as before with authenticate.
Related
I have VueJS frontend talking to a Rails backend. I can successfully create 1 a session and receive the CSRF token to store, but it's never sent 2 in subsequent Axios requests when deployed on Heroku. It works fine on localhost, and both the frontend + backend are hosted on herokuapp.com so SameSite shouldn't be an issue.
Axios is set to include the XSRF token with requests:
const API_URL = process.env.VUE_APP_API_URL || "http://localhost:8080";
axios.defaults.baseURL = API_URL;
axios.defaults.headers.post["Content-Type"] = "application/json";
axios.defaults.xsrfCookieName = "CSRF-TOKEN";
axios.defaults.xsrfHeaderName = "X-CSRF-Token";
axios.defaults.withCredentials = true;
How can I ensure Axios passes this cookie to my Rails Application? Images below: cookie returned on session creation and then not included in the following request.
[1
This ended up being an issue with .herokuapp.com being on the public suffix list. Adding a custom domain solved the issue.
https://devcenter.heroku.com/articles/cookies-and-herokuapp-com
I have an oAuth Provider which uses Authentication URL. In some cases, my Authentication URL returns "non-200" with meaning messages e.g. incorrect username, incorrect password, user locked etc.
I wonder how to pass the meaningful message from Authentication URL to the client who is requesting the token via API (oauth2/token)??
I have tried many ways, the API Connect always results 401 - invalid grant only.
Here is my oAuth Provider Code
x-ibm-configuration:
testable: true
enforced: true
phase: realized
oauth2:
client-type: confidential
scopes:
weather: Weather Information
openid: Enable OIDC
grants:
- password
identity-extraction:
type: basic
authentication:
x-ibm-authentication-url:
url: 'https://8hxovobj7g.execute-api.eu-west-2.amazonaws.com/Prod/auth'
authorization:
type: authenticated
access-token:
ttl: 1500
refresh-token:
count: 2048
ttl: 2682000
gateway: datapower-gateway
assembly:
execute:
...
...
when authen fail due to incorrect username, the authentication URL return
401 Unauthorized
{
"error": "incorrect username"
}
However, when I use postman to post a request to API Connect Gatwatway to the oAuth Provider (path: /oauth2/token)
if correct username password (authen URL user registry return 200-OK, I will get
{
"token_type": "bearer",
"access_token": "AAIkNTU4M2RlMzktODY1NS00ZDQ1LTgyMjctODEyMDM4MDUzMTE2m7lBYXfx73OVPONAHoLT5VNdtSVD40Hu-M3nAQPu6wdviOxcIfbsOXBwt-Iy8EAgLzuATlZB7RBME_U5Ymd5fDkRTwy05G9zGmV7mIkawaELtiOj4xdzQr7Vn-indlv-y1NFEjvRv2VrK0d3TOqZnTEj5heDdY7Q0X9BFeydV4MtS-gCpnj-9l6TU3XqyeiK5hGnBZkZRAWOIskLm4KCyf8n_mnsi42vN9GLxlxoO9EmuHAwXOxr_aocKaaVlLKK5vDMHBRws2Vguqk3eVuoh9EnkRZvjbTurmW57bCgX3nMTd6MwcEYFkAGh-cOcEDyydZR6BI_pLuwaUM9RN8Vnb7EATQjzW2d_eHKQyjShcyM0TqxzhYq3q90fLfJLo08WxDgTFaKpGHA6qoZmUpYRLeyyImhOPtyd9p1l9z87g52duHbL1cyVGErHktTVpeXsmIRtn-QTTvI4jWmjxPZnSYj_rEeR9S8QAxYpHSEPmJQQmsjISf2SIRLABwuhG9dKyrrzs3UTotVyIotxmJjc9lfEsEtDTz9Ej--yQFw97ESHCVEvOkifeyIJ9F5MyPFh7fMEoGGwyDmWEfZSYRpkLg4_ib3dbjkGAuthiwjdA0",
"metadata":
...
}
if incorrect username/ password (authen URL user registry return 401-Unauthorized with response-body (error message), I will get
401-Unauthorized
{
"error": "invalid_grant"
}
The Authentication URL is custom made. I have tried to make different returns in authentication URL (different http response code/ body/ headers) which all cannot affect the end-user's oauth's oauth/token result which always return the msg "invalid grant". what I really want to pass the authentication URL result message to the end-users to let the users know what is wrong such as incorrect username
Thanks for your kindly help
Unfortunately, it seems that in APIC v5 it is not possible to adjust OAuth error based on the response from Authentication URL call.
You can check DataPower XSLT code which handles this case (local:/isp/aaa-ldap-lib.xsl) to see more details. For any non-200 response code returned by an authentication URL, the same hardcoded error is produced. Only a response code is checked and all response information is discarded after that check - including a response code.
In the case of non-200 response code returned by authentication URL following XSLT code is executed:
<xsl:call-template name="error">
<xsl:with-param name="code" select="'401'"/>
<xsl:with-param name="reason" select="'Unauthorized'"/>
<xsl:with-param name="challenge" select="'Basic'" />
</xsl:call-template>
That means it is not possible to distinguish between errors caused by different authentication URL responses in OAuth API assembly catch. You can catch UnauthorizedError in the created assembly in OAuth API but you would have no additional information based on which you could create custom error response.
However, if you are using on-premise APIC v5 and you have access to the DataPower Gateway you could:
change mentioned xsl file to propagate status code/reason from authentication URL (possibly using gateway extensions)
info necessary, add a custom assembly to your OAuth API where you would catch UnauthorizedError and add GatewayScript code to adjust behavior
GatewayScript code to adjust behavior in case of UnauthorizedError caught would be something like:
let p = session.name('policy');
let e = p.getVariable('fw/exception');
let statusCode = e.httpCode;
let statusReason = e.httpReasonPhrase;
...
I've searched around a lot and seen various answers. Some seem like they may be outdated, and others use gems (omnicontacts) that I can't seem to get working with devise.
In my situation, I already get an oauth token from Google successfully. Now I'm trying to send a get request to the url given by Google to get all my contacts. I'm able to do this in Google's oauth2 playground, but not from my rails app.
Here is the relevant code:
require 'net/http'
require 'json'
class User < ActiveRecord::Base
def get_google_contacts(auth_token)
uri = URI.parse("https://www.google.com/m8/feeds/contacts/default/full?oauth_token=#{auth_token}&max-results=50000&alt=json")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # You should use VERIFY_PEER in production
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
contacts = JSON.parse(response.body)
puts contacts
end
end
Notebooks Controller: this is where I want a user to be able to access their contacts from Google.
def show
#contacts = current_user.get_google_contacts(current_user.oauth_token)
end
Here is the error log I get in my local:
JSON::ParserError in NotebooksController#show
746: unexpected token at '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Error
401
(Client Error)!!1</title>
<p><b>401.</b>
<ins>That's an error.</ins></p>
<p>There was an error in your request.
<ins>That's all we know.</ins></p></body></html>'
I think I may be using incompatible techniques to parse the xml I get from Google into json, but that is basically what I want to do. The only Rails related documentation I found in Google API documentation was dated 2009, but it mentioned the gdata gem. Should I be using that?
Thank you so much for any help on this.
EDIT
This is the response I get from Google Oauth2 Playground on a 200 ok (https://developers.google.com/oauthplayground/):
Content-type: application/atom+xml; charset=UTF-8
-content-encoding: gzip
<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom'
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
xmlns:gContact='http://schemas.google.com/contact/2008'
xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:gd='http://schemas.google.com/g/2005'>
<id>kaustubh.bhardwaj86#gmail.com</id><updated>2014-01-13T18:34:22.842Z</updated><category
scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#contact'/>
<title type='text'>Kaustubh Bhardwaj's Contacts</title>
EDIT #2
Ok, I've isolated my problem. I'm getting Net::HTTPUnauthorized on my get request. Here is the updated code for the get request I'm making to Google Contacts API:
uri = URI.parse("https://www.google.com/m8/feeds/contacts/default/full?max-results=50000")
# headers = { "access_token" => "#{auth_token}" }
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new(uri.request_uri)
request.initialize_http_header({ "access_token" => "#{auth_token}" })
response = http.request(request)
puts response.code
puts response
That is the same url I am using in Google Oauth2 playground.
I suspect you're setting the token incorrectly. It is more normal to set it in an http header
Authorization: Bearer 0b79bab50daca910b000d4f1a2b675d604257e42
If you set it in the URL, I believe it's access_token=, rather than your oauth_token= but since I never use it this way, I might be wrong.
I have a method that works just fine for calls to non-ssl apis, but it gives me the following error response whenever I request https-only apis:
757: unexpected token at '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
Reason: You're speaking plain HTTP to an SSL-enabled server port.<br />
Instead use the HTTPS scheme to access this URL, please.<br />
</p>
</body></html>
The method is fairly straightforward:
def my_service_api_call(path = nil, query = nil)
my_service_api_key = ENV["MY_SERVICE_KEY"]
api_path = "#{path}/?api_key=#{my_service_api_key}&#{query}"
url = URI.parse(api_path)
req = Net::HTTP::Get.new(api_path)
res = Net::HTTP.start(url.host, url.port) { |http| http.request(req) }
JSON.parse(res.body)["results"]
end
This works great over http but fails over https-only. Is there an equivalent way to do HTTPS requests?
There's a more elegant way to reuse your instance of Net::HTTP and enable requests over HTTPS:
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # Sets the HTTPS verify mode
#data = http.get(uri.request_uri)
Notice the use of use_ssl. From the documentation:
Turn on/off SSL. This flag must be set before starting session. If you change use_ssl value after session started, a Net::HTTP object raises IOError.
Note also that the usage of VERIFY_NONE is controversial, since it doesn't force the validity of of certificates to be checked. For many applications and users, this will not bear any negative ramifications. In cases where certificate validity should be checked, this post suggests the following:
Securely transfer the correct certificate and update the default certificate store or set the ca file instead.
You need to set use_ssl to true:
like this:
http = Net::HTTP.new(uri.hostname, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.ssl_version = :SSLv3
DotNetOpenAuth is an amazing package. However, I keep hitting some hurdle or other. Till now most of them got solved, now I have hit a problem, which is taking up a lot of my time.
The Problem:
I want to share a post on linkedin using DotNetOpenAuth. I was able to share a few posts a while back, but all of a sudden things are now broken, and I am not able to post shares anymore. I do not recollect making much changes in my code. Can someone have a look at my diagnosis, code and see if I am messing something up?
The Diagnosis:
I ran fiddler while making requests via my code and via LinkedIn's Rest Console. Here's the Fiddler output for both scenarios:
Fiddler Output for request that was made via Rest Console provided by linkedin
POST /v1/people/~/shares HTTP/1.1
Authorization: OAuth oauth_consumer_key="{consumer_key}",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1362258084",oauth_nonce="-141812272",oauth_version="1.0",oauth_token="{access_token}",oauth_signature="HBMUfvHYJAxz%2BszxStVJ%2BhfQPEQ%3D"
Host: api.linkedin.com
Content-Length: 645
X-Target-URI: http://api.linkedin.com
Content-Type: application/xml
Connection: Keep-Alive
<?xml version="1.0" encoding="UTF-8"?>
<share>
<comment>test comment</comment>
<content>
<title>test title</title>
<submitted-url>{my_test_url}</submitted-url>
<submitted-image-url>{my_image_url}</submitted-image-url>
</content>
<visibility>
<code>anyone</code>
</visibility>
</share>
Fiddler output for request that was made via my code(using DNOA):
POST http://api.linkedin.com/v1/people/~/shares HTTP/1.1
Authorization: OAuth oauth_token="{access_token}",oauth_consumer_key="{my_consumer_key}",oauth_nonce="ECYFLc0l",oauth_signature_method="HMAC-SHA1",oauth_signature="FobR745YkMlmBpOqoTDr8CwGZIQ%3D",oauth_version="1.0",oauth_timestamp="1362263560"
Content-Type: application/xml
Host: api.linkedin.com
Content-Length: 510
Connection: Keep-Alive
<?xml version="1.0" encoding="UTF-8"?>
<share>
<comment>test comment</comment>
<content>
<title>test title</title>
<description>test description</description>
<submitted-url>{my_test_url}</submitted-url>
<submitted-image-url>{my_image_url}</submitted-image-url>
</content>
<visibility>
<code>anyone</code>
</visibility>
</share>
More details:
I know that I need to pass scope while performing oauth authentication, so already have this in place. I also get proper permission prompt that reads "NETWORK UPDATES Retrieve and post updates to LinkedIn as you" when user is authorizing my application
Do note that I do get an access token, and I am able to perform some read operations, like for getting user details using this token, however, I am not able to use the token to share posts on LinkedIn. Currently the implementation is using LinkedIn oAuth 1.0a.
Here's my code block as well:
public bool PostToLinkedIn(WebConsumer consumer, string sShareMessageXml, string accessToken = null)
{
var payload = Encoding.ASCII.GetBytes(sShareMessageXml);
AuthorizedTokenResponse token = null;
bool bIsAccessTokenSupplied = !string.IsNullOrEmpty(accessToken);
if (!bIsAccessTokenSupplied)
{
token = consumer.ProcessUserAuthorization();
}
if (token != null || bIsAccessTokenSupplied)
{
this.AccessToken = bIsAccessTokenSupplied ? accessToken : token.AccessToken;
var resourceEndpoint = new MessageReceivingEndpoint("http://api.linkedin.com/v1/people/~/shares", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
HttpWebRequest request = consumer.PrepareAuthorizedRequest(resourceEndpoint, AccessToken);
request.ServicePoint.Expect100Continue = false;
request.Method = "POST";
request.ContentLength = payload.Length;
request.ContentType = "application/xml";
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(payload, 0, payload.Length);
}
var response = consumer.Channel.WebRequestHandler.GetResponse(request);
var responseData = response.GetResponseReader().ReadToEnd();
var xmlData = XDocument.Parse(responseData);
if (xmlData.Elements(XName.Get("update")).Any())
{
return true;
}
}
return false;
}
Please let me know if you need more details. Any help would be great.
I have posted a brief version of the same problem on DNOA's google group as well here. Hopefully someone has faced this issue and has a solution.