How do we send post request with Kerberos authentication with axios on linux? - node.js

we are trying to call POST api to generate certificate for renewal of certification. However as per the 3rd party API reqirement, we need to validate Kerberos authentication. We have tried many thhings with no luck.
We are getting 401 unauthorized error when we are trying to run it.
Tech stack: Node.js, Javascript, Kerberos auth, Linux OS.
Code snippet:
const axios = require('axios');
const data = {
Request: "-----BEGIN CERTIFICATE REQUEST-----<csr key>-----END CERTIFICATE REQUEST-----",
CertificateTemplateName: "DBAPI1Y",
PrimaryEmail: "test#test.com"
};
axios.post('http://dummyurl.com/webapi/SubmitRequest', data, "Negotiate")
.then((res) => {
console.log(`Status: ${res.status}`);
console.log('Body: ', res.data);
}).catch((err) => {
console.error(err);
});
Tried this approach with no luck: How do I use Negotiate or Kerberos authentication with axios?
Can someone please help?

I was unable to find any traces of Negotiate support within axios. Your linked post says that in browsers it would work automatically, which is indeed true for any in-browser JavaScript (that uses Fetch or XMLHTTPRequest behind the scenes) – but the node CLI is a different world. When run through Node CLI, axios will use the HTTP client provided by Node, which doesn't do Kerberos.
It seems that you will need to implement this manually, by using the krb5 module to get a token, then telling axios to send it in the Authorization header – similar to Bearer tokens.
The following example seems to kind of work, although not very pretty – it cannot cope with '30x' redirects:
const axios = require("axios");
const krb5 = require("krb5");
// The service is "HTTP" (uppercase), regardless of the URL being http or https.
token = await krb5.spnego({hostbased_service: "HTTP#dummyurl.com"});
resp = await axios.post("https://dummyurl.com/webapi/SubmitRequest",
data,
{
headers: {
"Authorization": `Negotiate ${token}`,
},
// SPNEGO tokens are single-use, so if Axios tries to
// follow redirects, the 2nd request will be rejected
// as a "replay". So tell it to not even try.
maxRedirects: 0,
});
(I have no experience with writing "proper" Node.js code, especially async-based code, and while in theory it should be possible to use axios' transformRequest to dynamically get a new token for each request, I was unable to figure out how to do it within a sync function.)
In general, I would probably do the task in Python instead (which has well-maintained Kerberos integration using requests-gssapi or httpx-gssapi for Requests or httpx respectively).
Note that Kerberos doesn't ensure data integrity for HTTP, so you must still use HTTPS with it, otherwise someone could still simply MitM the requests.

Apart from the above approach suggested by #user1686, I have solved the problem using NTLM authentication. Specifically, I have used the httpntlm module.
By using the httpntlm package, we don't need to deal with kerberos packages as all the kerberos packages have many dependencies on node-gyp.
Thus, it's better to use some other solution apart from the kerberos related packages.
Note: This solution worked for me, but it will vary from use case to use case.

Related

403 error with axios but works on postman and browser

I get Nfts on magic eden with this third party api.
http://api-mainnet.magiceden.io/rpc/getGlobalActivitiesByQuery?q=%7B%22%24match%22%3A%7B%22txType%22%3A%22initializeEscrow%22%2C%22blockTime%22%3A%7B%22%24gt%22%3A1643468983%7D%7D%2C%22%24sort%22%3A%7B%22blockTime%22%3A-1%7D%7D
It responses with results on postman and browser but causes 403 error with axios in node.js.
How can I get data in node.js?
const res = await axios.get(
'http://api-mainnet.magiceden.io/rpc/getGlobalActivitiesByQuery?q=%7B%22%24match%22%3A%7B%22txType%22%3A%22initializeEscrow%22%2C%22blockTime%22%3A%7B%22%24gt%22%3A1643468983%7D%7D%2C%22%24sort%22%3A%7B%22blockTime%22%3A-1%7D%7D',
{
headers : {
"Content-Type": "application/json",
"Access-Control-Allow-Credentials": "*"
}
}
);
return res.data;
Using a proxy here won't help as they have Cloudflare protection set up against bots/scripts and it requires cookies to be present: screenshot 1, screenshot 2
You should get in touch with their support and ask for the API key (they have a public API v2 coming soon), and then use it in Authorization: Bearer <token>
Few things to note here: Their API v2 is still in works and lacks some basic features. Using their current v1 API does not require having an API key (that's what their support personnel said) but it does have bot protection against scripted attacks.
I'm hesitant to go with the API v2 since it lacks even the most basic stuff and I don't expect it to come out anytime soon. Personally, I'm looking to get in touch with people who managed to integrate the v1 into their applications, to see what necessary steps they followed in order to be able to do it.
If you managed to find some new info on that regard let me know. I'll also edit this comment in case I find out how to set up the v1 connection properly.
EDIT: managed to get it working by using the https://github.com/puppeteer/puppeteer library. Started a small headless instance of Chrome and I hit the ME API with that browser like so: screenshot 3
It can be because of CORS error.
You can use Cors proxy to fix it.
Try it,please.
const CORS_PROXY_API = `https://cors.ryanking13.workers.dev/?u=`;
const magicedenAPI = `http://api-mainnet.magiceden.io/rpc/getGlobalActivitiesByQuery?q=%7B%22%24match%22%3A%7B%22txType%22%3A%22initializeEscrow%22%2C%22blockTime%22%3A%7B%22%24gt%22%3A1643468983%7D%7D%2C%22%24sort%22%3A%7B%22blockTime%22%3A-1%7D%7D`
const { data } = await axios({
method: 'get',
url: `${CORS_PROXY_API}${magicedenAPI}`
});

Cookie not being set from node typescript request

I'm trying to set a cookie in a node request. I have tried using packages like js-cookie, cookie-js, cookie and cookie-manager but none work.
The way I have tried it is very straight-forward, whenever my endpoint gets called i.e. https://develop.api/sess/init, I set the cookie at the very beggining of the endpoint with the following code
import * as Cookies from 'js-cookie';
export const init = async (event: APIGatewayEvent, context: Context) => {
...
Cookies.set('hello', 'hello');
...
}
As my endpoint has an auth header, I can not directly call it into my browser URL due to missing permissions, so I tried generating the fetch function with postman and pasting it into my browser's console. The function is the following
var myHeaders = new Headers();
myHeaders.append("Referer", "accepted.referer.com");
myHeaders.append("key", "somekey");
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
fetch("https://develop.api/sess/init", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
Once called, my request successfully returns the expected response, but it never shows a Set-Cookie header in the network section, neither shows my cookie in the Application section.
I have to mention that I also tried looking for the cookie when making the call within Postman, but it never sets it neither.
Also, I have tried starting the application in localhost, and I have a successful response, but my cookie is still not being set.
About the package showed in the code, I said I have tried it with different ones and their implementations, so I don't think a broken package is the problem.
I'm starting to think that I have a wrong idea about how cookies work, or that someway I am completely blocking the sending of cookies within my code.
Environment
If it helps in any way, my endpoint is being hosted in a AWS Lambda application.
I know this should be trivial, but being battling with it for a day now.
I finally answered my own issue. The key here is that I'm using AWS lambdas as the proxy, therefore, the headers I were using to send the cookies were wrong, I was sending the cookies with the endpoint instead of within the lambda. Let me explain myself.
I was adding 'Set-Cookie':'cookieKey:cookieVal' in the headers of the Postman Call that I was using to test both my local and develop environments.
Instead of that, I needed to send the request within the response of the lambda for the cookies to be registered.
Please check at the following links for similar cases ->
https://aws.amazon.com/blogs/compute/simply-serverless-using-aws-lambda-to-expose-custom-cookies-with-api-gateway/
https://forum.serverless.com/t/how-to-send-a-cookie-as-a-response/1312/7

How do you include an SSL certificate in an API request in Node?

Here is the situation: I have a certificate, key, public key, and private key. I am building an app that integrates with another system. My system is a Next.js app running on Azure. It makes API requests to an external server running somewhere else. I know that my certificate/keys are good because the request works when I use the Insomnia API Client (200 response and data coming back).
However, trying to do this in Node, I keep getting 400 errors. I have tried doing this with core fetch and https functionalities and using the node-fetch package. I also looked at the request package (as there are examples of this on the internet), but that package was deprecated over a year ago, so I think it should not be used.
Here's an example of some of the code I've tried using. I'm storing the certs/keys in /tmp/certs on my local system & they are loading (I've been logging heavily). I've used a wide variety of different requestOptions, but nothing seems to be doing the trick.
import fs from 'fs'
import fetch from 'node-fetch'
const requestOptions = {
method: 'GET',
port: 443,
cert: fs.readFileSync("/tmp/certs/ct.crt", 'utf-8'),
key: fs.readFileSync("/tmp/certs/ct.key", 'utf-8')
};
const res = await fetch("https://url.com?unique_ID=1234&key=abcd", requestOptions);
Examining the res always turns up a 400. I have tried doing this a number of different ways at this point. This seems like it should be a trivial, common use case... What am I doing wrong and how should I be structuring this request?
Help me, Stack Overflow, you're my only hope!

Can I make a request to an HTTP API server without opening myself up to vulnerabilities?

I am building an API server using Node.js and Express. My server has a route which requires important data from a 3rd party, so I would like to make a request to their API using axios.
My problem is that the 3rd party's API endpoints are http://..., not https://. As a result, I get the following error:
(node:7088) UnhandledPromiseRejectionWarning: Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames: Host: api.<3rdpartysite>.com. is not in the cert's altnames: DNS:www.<3rdpartysite>.com, DNS:<3rdpartysite>.com
I know that it is up to the 3rd party to fix their API so I can access it via HTTPS. I have reached out to the API developer repeatedly for weeks with no response.
I can make things work by setting the environment variable NODE_TLS_REJECT_UNAUTHORIZED=0 , but I know that opens up significant security risks.
Here is the code I am currently using to make the request:
app.post("/api/v0/myRoute", async (req, res) => {
const response = await axios.get(
`http://api.<3rdpartysite>.com/v0/theirRoute?
apikey=${keys.thirdpartysite}`
);
console.log(response);
});
Changing the http above to https gives me the exact same error message.
I want to make this request without putting my own API server at risk. Is there a way?

Token already used error while fetching access token

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

Resources