How to run a post request to an API from an HTML page (using a button linked to javascript code) when the API require certificate and user/password?

Hi! I want to add some javascript code into a HTML page that allow any user (with access to that web page) to run some post requests available there to an API. These post requests run well in Postman but I’m not sure how can I run this kind of requests using javascript since this API requires not only a certificate (with a passphrase) but also an additional credential pair (username and password) which in Postman are configured in a very intuitive way on the appropriate menus.

I’ve the following javascript code where I deal with the username and password but I don’t know how to dealt with the certificate/passphrase:

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", "Basic 123xpto321");

var raw = JSON.stringify({
  "path": "/Customers/Organisation/organisation_adm/Account Management/Account Balance/Accounts",
  "outputType": "pdf"
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.url/my-endpoint", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Can someone help me on how I can pass the certificate to reach this API from an ordinary web page?

Thanks in advance!

First of all: are you sure you want to make your credentials public to everyone? Because if you put it in JavaScript, everyone can see them and use them wherever they want.

Second: if you feel sure, you can easily open the postman console and check the send command as raw data. There you will find any information you need how to configure your fetch request

Hi @Thallius !
You are right. From a security point of view this is a bad approach. Do you recommend another way to do this so we could run the requests from a web page?

Aside the security issues, I tried nevertheless to solve the certificate issue (for learning purposes) but still got no luck. Below my two approaches and their errors:

Postman Console Output regarding the certificate:

Client Certificate
	id: "xpto_id"
	matches: [1]
		0: {…}
			pattern: "https://api.xpto-url.com:443/*"
	key: {…}
		src: ""
	cert: {…}
		src: ""
	pfx: {…}
		src: "/C:/Users/xptoUser/path/mycertificate.p12"
	passphrase: "xpto_passphrase"

My code, Method A:

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", "Basic asdasd");

var raw = JSON.stringify({
  "path": "/Customers/Organisation/organisation_adm/Account Management/Account Balance/Accounts"
  "outputType": "pdf"
});

var certificado = new Certificates();
certificado.append("id", "xpto_id");
certificado.append("matches", "pattern: https://api.xpto-url.com:443/*");
certificado.append("pfx", "src : /C:/Users/xptoUser/path/mycertificate.p12");
certificado.append("passphrase", "xpto_passphrase");

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  certificate: certificado,
  redirect: 'follow'
};

fetch("https://api.url/my-endpoint", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

ERROR: Uncaught ReferenceError: Certificates is not defined

My code, Method B:

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", "Basic xpto-asdasd");

var raw = JSON.stringify({
  "path": "/Customers/Organisation/organisation_adm/Account Management/Account Balance/Accounts"
  "outputType": "pdf"
});

var certificado = JSON.stringify({
  "id": "xpto_id",
  "matches":{
	"pattern": "https://api.xpto-url.com:443/*"
  },
   "pfx":{
	"src": "/C:/Users/xptoUser/path/mycertificate.p12"
  },
  "passphrase":"xpto_passphrase"
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  certificate: certificado,
  redirect: 'follow'
};

fetch("https://api.url/my-endpoint", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

ERROR: net::ERR_BAD_SSL_CLIENT_AUTH_CERT

Do you know how I can solve this?

To be honest, no I have no solution because I never did that from JavaScript.
Normally you should do this on the backend and just call a request on the backend not directly on the API.

The only advise I can give is what I already did.

Make your call with postman, open the postman console and check the raw data of the request you have sent. Then you will see what is nesessary to make a correct request.

That’s the way I do it all the time when I use new APIs

My suspicion is the api uses oauth and requires a user token to authenticate each request. You should look at the documentation for how to authenticate and see if oauth or tokens jwts are mentioned anywhere. With oauth you would first signin to exchange credentials for a token. Than you provide that token in authentication header for each request.

In Postman, I only configured the basic authentication credentials (username and password) and the path to the certificate and passphrase on the Postman settings menu. It worked there and I did not make any configuration related with token (as far as I know). Would this be something running on the ‘backend’ without any config requirement on Postman?

Thanks

fetch doesn’t have this option and it would be weird to have one because it makes your client certificate accessible for anyone. Instead, you should install your certificate into the browser.

Then add this line to the fetch options:

credentials: 'include',

Documentation says this option will force browser to attach your certificate to each request

Hi @veocode!
Thanks for tip.
I tested your procedure and I got a different response now:

Access to fetch at 'https://api.url/my-endpoint' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

So, I tried to set the request’s mode to ‘no-cors’ this way:
fetch("https://api.url/my-endpoint", requestOptions, { mode: 'no-cors'})

But I got the same error:
Access to fetch at 'https://api.url/my-endpoint' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Any ideia to solve this?

After the last attempt I found a guy with a similar issue and follow the detailed response there creating my own CORS Anywhere server running. I update my code accordingly (see below) but I still have the same response…

fetch("https://my.cors.anywhere.herokuapp.com/https://api.url/my-endpoint", requestOptions)

Error:
Access to fetch at 'https://my.cors.anywhere.herokuapp.com/https://api.url/my-endpoint' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.

Finally, when I try to access the url directly (https://my.cors.anywhere.herokuapp.com/https://api.url/my-endpoint) I get the following message:

Missing required request header. Must specify one of: origin,x-requested-with

Did you create the back-end API or is it from a third party?

Its from a third party.

Is the documentation available for public access. If so can you provide a url.

Just make your requests from the backend, not from the browser directly. Most REST APIs won’t allow you to call them from client side.

I didn’t find any public documentation. I will give another look but, in the meanwhile, should I search for any specific thing on the user guide I have?

Ok, as you probably already noticed, I’m kinda of a rookie on this. Can you detail a bit what you mean by running this on a backend? Do you mean on a web server?

Right. You would query the API from your web server in whatever server-side language you are using. Then instead of querying the API directly from your browser, you would query your backend, which would send a request to the API, then return some data to the browser (thus bypassing the CORS issue). And if you don’t have a backend, you can make a simple one for that purpose — a proxy, basically.

Thank you for the clarification. Since I dont have access/priveleges to the backend, could you detail how can I build the proxy?

Searching “npm express proxy” this is the top result. This looks really easy to use requiring minimal code just some basic configuration to map the proxy using middleware calls.

Thanks :+1:
I will look into it and give it a try.

How do you deploy your JavaScript code if you have no access to the server?