I'm trying to consume a tone analyzer service from a nodejs app. I get unauthorized access problem, but these credentials work fine when I use them in a curl.
Running locally, in my app.js file I've included the data of the tone analyzer as follows:
var ToneAnalyzerV3 = require('watson-developer-cloud/tone-analyzer/v3');
var toneAnalyzer = new ToneAnalyzerV3({
version: '2017-09-21',
iam_apikey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
});
Then I've added this, so my app listens for post requestes in the /tone url:
app.post('/tone', function(req, res, next) {
var params = {'tone_input': req.body}
toneAnalyzer.tone(params, function(err, data) {
if (err) {
return next(err);
}
return res.json(data);
});
});
But when I call it I get "Unauthorized: Access is denied due to invalid credentials".
The thing is that these credentials work fine in curl:
curl -X POST -u "apikey:XXXXXXXXXXXXXXXXXXXXXXXXXXXXX" --header "Content-Type: application/json" --data-binary #tone.json "https://gateway-lon.watsonplatform.net/tone-analyzer/api/v3/tone?version=2017-09-21&sentences=false"
{"document_tone":{"tones":[{"score":0.6165,"tone_id":"sadness","tone_name":"Sadness"},{"score":0.829888,"tone_id":"analytical","tone_name":"Analytical"}]}}
The reason you are getting unauthorised errors when running locally is that your service is hosted in https://gateway-lon.watsonplatform.net. If you don't specify an endpoint / url in your ToneAnalyzerV3 constructor then the API / SDK defaults to Dallas. So although your credentials may be correct for London, they are not correct for Dallas.
When you deployed your app to the cloud (which I guess was to the London location), you probably bound the service into your application. This sets environment variables allowing the SDK to determine the endpoint.
You constructor should look like:
var toneAnalyzer = new ToneAnalyzerV3({
version: '2017-09-21',
iam_apikey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
url: 'https://gateway-lon.watsonplatform.net/tone-analyzer/api',
});
I don't see problem with code (also never used watson stuff), but you may check the following point :
How the request you really send is formated : Because I see that you send param that is not present in your curl request.
Is your function using POST aswell (you don't provide much detail on what the call to toneAnalyzer.tone does exactly) ? Maybe it's a conflict of headers or Content-Type.
Do you use a proxy (enterprise settings or stuff like that) ? If you do, you may check that node is correctly using it.
You should also provide a bit more details on what exactly your tone object do, and try to find where the call to the IBM API is done.
Related
I am developing and testing a Node.js back-end service that uses an endpoint to receive requests and data. I tested this service in Visual Code running locally (http://localhost:8098) using Postman. The POST to the URL works fine locally.
But when I deploy the service to Azure or to an Ubuntu server VM (as a Docker image in a Kubernetes pod) and test it with Postman, I receive the aforementioned 431 error.
I have the minimum header fields that Postman adds to the request, so it is definitely not a header fields too large issue. The problem must lie elsewhere, but I have no idea where to look.
I have developed and deployed multiple back-end services to the same web app prior to this one with no issues. I used one of those services as a template to build this one.
I'm not sure what to add as information, but I will add any information requested. I am at wits end with this. Any help would be greatly appreciated.
Code:
let router = require('express').Router()
// Test
router.post('/test/', auth.optional, async function (req, res) {
console.log('Inbound request:')
console.log(req.headers)
// let context = req.context
// req.body.tenant_id = req.auth.tenantId
const result = await commsysEventService.test(req.body)
res.json(result)
})
module.exports = router
The code is as simple as can be. The code never even makes it to the console.log lines.
From Postman:
Postman screenshot
I have a React app in Electron, and I'm trying to access the spotify API using the spotify-web-api-node library. However, I'm not sure exactly how the oauth flow is meant to work inside of an Electron app... Firstly, for the redirect URL, I used this question and added a registerFileProtocol call to my file. Then I added a specific ipcMain.on handler for receiving the spotify login call from a page, which I've confirmed works with console logs. However, when I get to actually calling the authorizeURL, nothing happens?
This is part of my main.js:
app.whenReady().then(() => {
...
protocol.registerFileProtocol(
"oauthdesktop",
(request, callback) => {
console.log("oauthdesktop stuff: ", request, callback);
//parse authorization code from request
},
(error) => {
if (error) console.error("Failed to register protocol");
}
);
});
ipcMain.on("spotify-login", (e, arg) => {
const credentials = {
clientId: arg.spotifyClientId,
clientSecret: arg.spotifySecret,
redirectUri: "oauthdesktop://test",
};
const spotifyApi = new SpotifyWebApi(credentials);
console.log("spapi: ", spotifyApi);
const authorizeURL = spotifyApi.createAuthorizeURL(
["user-read-recently-played", "playlist-modify-private"],
"waffles"
);
console.log("spurl: ", authorizeURL);
axios.get(authorizeURL);
}
I'd expect the typical spotify login page popup to show up, but that doesn't happen. I'd also expect (possibly) the registerFileProtocol callback to log something, but it doesn't. What am I meant to be doing here? The authorization guide specifically mentions doing a GET request on the auth url, which is what I'm doing here...
In a desktop app it is recommended to open the system browser, and the Spotify login page will render there, as part of creating a promise. The opener library can be used to invoke the browser.
When the user has finished logging in, the technique is to receive the response via a Private URI Scheme / File Protocol, then to resolve the promise, get an authorization code, then swap it for tokens. It is tricky though.
RESOURCES OF MINE
I have some blog posts on this, which you may be able to borrow some ideas from, and a couple of code samples you can run on your PC:
Initial Desktop Sample
Final Desktop Sample
The second of these is a React app and uses a Private URI scheme, so is fairly similar to yours. I use the AppAuth-JS library and not Spotify though.
I'm currently developing my app and I'm at the stage where I can start testing messages from Twilio. I configured my server on digital ocean with a public facing IP address and my Nodejs app is listening to calls from Twilio.
I also configured my phone number's message "request url" to "http://username:password#198.xxx.xxx.xxx/messages" with "HTTP POST".
When I debug the headers, I don't see the "authorization" headers. I'm I missing something here?
Any help is much appreciated!
Below is the code.
var headerValues = bag.req.headers.authorization.split(' ');
console.log(bag.req.headers);
var scheme = headerValues[0];
if (scheme === 'Basic') {
var credentials = headerValues[1];
var decoded = new Buffer(credentials, 'base64').toString().split(':');
bag.req.creds = {
userName: decoded[0],
password: decoded[1],
authType: 'basic'
}
}
I use the same setup as you do in several call centers I have built.
If you are using a proxy setup which requires username:password# before the IP address then your issue is likely with that proxy if you can access the code by going directly to the actual server ip address as I note below. However, you did not mention using a proxy just using a digital ocean droplet so I am responding assuming you do not have a proxy setup.
So if you do have a proxy setup make sure you can access the IP address of the server directly first.
Also if those are just extra variables you need to pass over you may be better off appending them after the IP address
for instance xxx.xxx.xxx.xxx/username/password
Then get them with req.params
for instance (and yes this will work with post data since its merely part of the URL and not an actual get command post)
router.post('/sms/:username/:password'), function(req, res, next){
username = req.params.username;
}
First you would not want to direct your request URL at "http://username:password#198.xxx.xxx.xxx/messages" with "HTTP POST".
If you do not have a domain directed at your IP address yet you want your request URL to be
https://198.xxx.xxx.xxx/inbound/sms
{Replacing /inbound/sms with whatever route you are using}
Then at the top of your route (I am using express so my setup may look different than your)
I have the node.js twilio library
, twilio = require('twilio')
, capability = new twilio.Capability(sid, auth)
, client = require('twilio')(sid, auth)
Then here is an example of my /sms route
router.post('/sms', function(req, res, next){
var sid = req.body.SmsSid;
var from = req.body.From;
var to = req.body.To;
var date = Date();
var body = req.body.Body;
if(req.body.NumMedia > 0){
code to handle MMS
}
Code to handle SMS data
res.send("Completed");
});
I ran into this this week and discovered that behavior surrounding Basic Auth in the URL is very cloudy. For one thing, it appears to be deprecated from the URI spec as it pertains to HTTP:
...
3.2.1. User Information
...
Use of the format "user:password" in the userinfo field is deprecated.
...7.5. Sensitive Information
URI producers should not provide a URI that contains a username or password that is intended to be secret. URIs are frequently displayed by browsers, stored in clear text bookmarks, and logged by user agent history and intermediary applications (proxies). A password appearing within the userinfo component is deprecated and should be considered an error (or simply ignored) except in those rare cases where the 'password' parameter is intended to be public.
...
Because of this, both Firefox and Chrome appear to just strip it out and ignore it. Curl, however, seems to convert it to a valid Authorization header.
Whatever the case, I believe this functionality is actually the responsibility of the HTTP user agent, and it appears that Twilio's user agent is not doing its job. Thus, there is no way to make basic auth work.
However, it appears Twilio's preferred method of auth is to simply sign the request using your account's secret auth key, which you can then verify when handling the request. See here.
On researching the raw NodeJS Request and IncomingMessage classes, there appears to be no way to get at the full, raw URL to compensate for Twilio's non-conformity. I believe this is because the actual data of an HTTP request doesn't contain the full URL.
My understanding is that it's actually the HTTP user agent that's responsible for extracting and formatting the auth info from the URL. That is, a conformant HTTP user agent should parse the URL itself, using the hostname and port portion to find the right door on the right machine, the protocol portion to establish the connection with the listener, the verb combined with the URL's path portion to indicate what functionality to activate, and presumably it is then responsible for converting the auth section of the URL to an official HTTP Authorization header.
Absent that work by the user agent, there is no way to get the auth data into your system.
(This is my current understanding, although it may not be totally accurate. Others, feel free to comment or correct.)
I need to retrieve my saved reading list from my Pocket account
and it seems that I need to acquire access token through their oAuth to make a request.
I've got consumer key for the access token and as per Pocket API documentation, the request will resemble something like this.
POST /v3/oauth/request HTTP/1.1
Host: getpocket.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Accept: application/x-www-form-urlencoded
consumer_key=1234-abcd1234abcd1234abcd1234&
redirect_uri=pocketapp1234:authorizationFinished
My question is... isn't oAuth for 3rd party apps to enable authentication via Google, Facebook account? I don't see how this idea is relevant for my website that will only require access to my own data from Pocket to share on my site.
I understand I will need to authenticate somehow so I can access my data but is oAuth the process I will need to go through to get what I need?
It seems that they support only 3 legged OAuth flow. You can use Grant in your NodeJS app, or just get access token from here.
Grant
save the following example to a file
set your key here: key:'...'
install the needed dependencies
run the file with node.js
navigate to http://localhost:3000/connect/getpocket
follow the instructions on screen
At the end you'll see your access_token.
var express = require('express')
, session = require('express-session')
var options = {
server: {protocol:'http', host:'localhost:3000'},
getpocket: {key:'...', callback:'/getpocket_callback'}
}
var Grant = require('grant-express')
, grant = new Grant(options)
var app = express()
app.use(session({secret:'very secret'}))
app.use(grant)
app.get('/getpocket_callback', function (req, res) {
console.log(req.query)
res.end(JSON.stringify(req.query, null, 2))
})
app.listen(3000, function () {
console.log('Express server listening on port ' + 3000)
})
}
Purest
Then you can use Purest to make requests to the Pocket's REST API.
var getpocket = new Purest({provider: 'getpocket'})
getpocket.query()
.post('get')
.auth('[API_KEY]', '[ACCESS_TOKEN]')
.request(function (err, res, body) {
// body is the parsed JSON response
})
For anyone reading this in 2021 or later, wanting to make a simple script to add articles to their pocket, I came up with this:
1: get your consumer key, via pocket's site.
2: get you access token, using this tool it's very simple. If you want to make an app or something that'll work without it, I guess the above (old) answer might work, didn't test it.
3: Use the following code to add an article:
var request = require('request');
request.post({
url: 'https://getpocket.com/v3/add',
form: {
url: 'https://articleToAdd.com',
consumer_key: '123456-12abcd1234a1ab12a12abc12',
access_token: '12345a1a-1ab1-1a12-12a1-1a1234'
}
},
function(err, httpResponse, body) { console.log(httpResponse.body) }
)
Hope it helps someone that is looking to do the same. Retrieving/modifying articles is similar, look here for the specifics.
I am trying to setup a node.js application to use the Elance API using OAuth2.0
I am using passport.js to connect to the elance api and so far am able to get the code properly.
Before using the api methods, I then need to obtain the request token using a post request.
However, I am getting a 'Code Already Used' error.
Here's my callback code
app.get('/callback',
passport.authenticate('elance', { failureRedirect: '/failure' }),
function(req, res) {
console.log('CODE : ' + req.query.code); // this is getting displayed properly
var payload = {
code: req.query.code,
grant_type:'authorization_code',
client_id: auth.CLIENT_ID,
client_secret: auth.CLIENT_SECRET
};
request.post('https://api.elance.com/api2/oauth/token/', payload)
.then(function(response) {
var x = response.getBody();
console.log('resp::::'+x);
res.redirect('/success');
});
});
I am using requestify to perform the post request and am not using/calling to the server with the temporary code.
Here's the generated error:
... [Sat, 29 Mar 2014 05:54:15 GMT] "GET /callback?code=F9t-zztOLJ3IOlbGXlsqous686HstXqkv7etrvEnF11Vg4M HTTP/1.1" - - "-" "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:30.0) Gecko/20100101 Firefox/30.0"
InternalOAuthError: Failed to obtain access token (status: 401 data: {"errors":[{"code":null,"description":"Code already used."}]})
Perhaps the proper way of implementing this with Elance is to write a strategy. Just use one of the other ones published like Facebook as a model, or a simpler one like GitHub. It should be fairly straightforward, and better encapsulated. Here's a complete list: http://passportjs.org/ if you want to explore. Better yet, making this module reusable, others can then benefit from it in a more standard way.
Strategies will exchange the code for the token for you. Likely the reason the code is used and you get that error. That is part of the the standard OAuth2 flow.
I had the same problem, and had to work with Elance support to get it figured out. This error occured for me because multiple simultaneous requests were coming in for the same user from multiple threads/servers with the same API key.
In my case, it was a pool of server threads that were doing background work and we needed to synchronize the login so it only happened once. Multiple threads can re-use the same access_token, just not apply for a code and then an access_token/refresh_token in parallel.
This could also happen because you have multiple people / build servers running test cases which are asking for codes and then access_tokens in parallel.
Following are the points which seems to be vital in order to receive the token:
Setting the correct content-type and Content-Length values.
The request should be a HTTPS request.
Method should be POST.
I also installed and used Open SSL, just to avoid any issue caused due to non secure calls made by the server.
Calls made using Requestify were failing each time, even when I set the identical header information mentioned in #1 above. As visible in the attached screenshot, it works fine with normal https request calls.
JSON response from subsequent queries was obtained from the following code:
var request = require("request");
var jobURL = 'https://api.elance.com/api2/jobs/my?access_token=' + _token;
request(jobURL, function (error, response, body) {
res.write(body);
res.end();
});