Skip to Content
Technical Articles
Author's profile photo Carlos Roggan

HTTP request with client certificate in Node.js

Need to fire request with client certificate?
This blog post contains sample code (node.js) showing how to execute an HTTP request that authenticates with client certificate instead of user/password.
There are 4 samples:
1. using axios
2. using node-fetch
3. using native https module
4. using @sap/xssec module

Scenario

As example setup, we’re using the SAP Business Technology Platform (SAP BTP, fka SAP Cloud Platform).

The scenario:
We’re executing an HTTP request from client to server.
The server which receives our HTTP request is the XSUAA service.
The HTTP request we’re sending is used to fetch a token.
The credentials which we need (certificate) are given to us by the service instance.
The HTTP request is successful (and we’re satisfied) if the request returns a valid response (containing a token).

I’ve chosen this scenario because it is real use case and it is easy to reproduce for anybody.
Our client code is just a script which we can run from our local laptop.
We don’t need to deploy an app, as such we can keep the code minimalistic.
However, if desired, it is easy to modify the code such that it runs in the cloud.

I’m posting 4 samples that are based on different libraries, including the native way.
I hope to somewhat meet your expectations….
If not, please add your sample code in the comment section.

Prerequisites

This blog post assumes that you’re Node.js user and a bit familiar with certificates.
You can find basic information about certificates in this blog post.

In order to follow the description, you need access to SAP BTP, and permission/quota for creating the service instance.
This tutorial is based on trial account in SAP BTP.
Everyone can create such trial account.
However, the code should work with any other server as well, so you can just copy the snippets and don’t need extra cloud access.

Preparation

For those who wish to follow the example, here’s the description:

We need to create an instance of the XSUAA service, which will act as server and which will provide us with the required credentials.
We want to run our code locally, to make things easier.
However, this means that we need to create a service key in addition to the service instance.
Below is the description for creating both.
The description is based on the command line tool for Cloud Foundry.
Alternatively, the cockpit of SAP BTP can be used to create the service instance and service key.

Create Project

To host our files, we create a folder, e.g. C:\app
It contains
– the config file for node
package.json
– The 4 scripts containing the 4 samples
script_axios.js
script_fetch.js
script_https.js
script_xssec.js
– the config file for the xsuaa instance
xs-security.json

The content of the files can be found in the appendix.
For your convenience, I’m pasting a screenshot:

Local installation

After creating the files, we need to install the modules locally.
We open command prompt, jump into c:\app and run npm install

Create instance of xsuaa service

Now that we have the config file for XSUAA in place, we can create the instance.
We jump into c:\app and execute the following command:
cf cs xsuaa application myXsuaaInstance -c xs-security.json

Note:
We need the config file in order to configure the XSUAA instance to use client certificate:

{
   "xsappname": "clicertxsappname",
   "oauth2-configuration": {
      "credential-types": ["x509"]
   }
}

See appendix for all snippets

After the xsuaa instance is created, we need to create a service key.
The service key is used to access the credentials of the service instance.
This is required, because we’re working locally.
If you’re going to deploy your app to the cloud, you’ll get the credentials in the binding and then you don’t need a service key.

The command:
cf csk myXsuaaInstance sk
We need to view the content of the service key:
cf service-key myXsuaaInstance sk

The credentials are printed to the console:

We can see that it is a JSON object.
We want to use this JSON object in our code.
As such, we copy the whole JSON object into our clipboard, to paste it later into the code.
If we have a closer look, we’ll see all config information that we need:
URL, certificate, key, etc
Yes, the URL is the url of the server to which we send our request with certificate.
The etc contains lot of information that we don’t need.

Note:
You’ll need the deletion commands:
cf dsk myXsuaaInstance sk -f
cf ds myXsuaaInstance -f

OK.
After we have all the info, now we can start with the code.

Request 1 using axios lib

We open the file c:\app\script_axios.js.
We copy the content of the service key into the variable CREDENTIALS.
It will look like this:

Note:
In my screenshot, I’ve removed all unnecessary properties, to make it more comprehensive.
Axios is configured with an agent that contains the certificate and the private key, which we take from the credentials object.

And that’s already all what I wanted to show.
In my XSUAA-example, we need to specify the request as POST with request body as form. That’s required by the client credentials oauth flow.
We send the request to the token endpoint of XSUAA server.

const options = {
    url: CREDENTIALS.certurl + '/oauth/token',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    method: 'POST',
    data : `grant_type=client_credentials&response_type=token&client_id=${CREDENTIALS.clientid}`,
    httpsAgent : new https.Agent({
        cert: CREDENTIALS.certificate,
        key: CREDENTIALS.key
    })
}

See appendix 1 for full sample code.

To execute the script, we change to the command prompt and run
node script_axios.js
The result should look like this:

It looks strange – but it is what we wanted, so it is successful.

Request 2 using node-fetch lib

Now we copy the credentials JSON object into the file c:\app\script_fetch.js.
Using the node-fetch module looks pretty much the same as above.
The request is configured with an agent that contains the certificate and the key.
With respect to the request body, we’re now using the native Now we copy the credentials JSON object into theURLSearchParams object which takes care to properly build the request body, hence less error prone.
The header x-www-form-urlencoded can be removed, if the URLSearchParams is used. But I leave it to make things more explicit.

// configure request
const url = CREDENTIALS.certurl + '/oauth/token'
const params = new URLSearchParams()
params.append('grant_type', 'client_credentials')
params.append('response_type', 'token')
params.append('client_id', CREDENTIALS.clientid)

const options = {
    method: 'POST',
    body: params,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    agent : new https.Agent({
        cert: CREDENTIALS.certificate,
        key: CREDENTIALS.key
    })
}

See appendix 2 for full sample code.

Now we execute the script
node script_fetch.js
and get the same result as above.

Request 3 using native https module

Usually I’m using native code in my tutorials, to avoid depending on dependencies, which means I’m free of adapting dependencies when they become deprecated or similar.
And here is the snippet for the request with the built-in node module https, in file c:\app\script_https.js

const options = {
    cert:  CREDENTIALS.certificate,
    key: CREDENTIALS.key,
    host: CREDENTIALS.certurl.replace('https://', ''),
    path: '/oauth/token',
    method: 'POST',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}

See appendix 3 for full sample code.

Request 4 using @sap/xssec lib

The last code sample is dedicated to the xssec library.
It has been tailored for my use case: requesting JWT token from XSUAA.
As such, I cannot omit this sample, but it might not be interesting for you, if you have different use case.
I’m not angry if you stop reading here….

The code is very short:

xssec.requests.requestClientCredentialsToken(null, CREDENTIALS, null, null, (error, token)=>{            
    console.log(`Result: ${token}`)
})

 

We open the file c:\app\script_axios.js
See appendix 4 for full sample code

That’s all.
The library will check if the given credentials contain a certificate, then it will build the request accordingly and execute it and hand the token over to the callback.

Summary

In this blog post we’ve learned how to pass client certificate and private key to an HTTP request.

Links

https://axios-http.com

https://github.com/bitinn/node-fetch

https://nodejs.org/api/https.html

https://www.npmjs.com/package/@sap/xssec

Appendix 0: Sample Code preparation

xs-security.json

{
  "xsappname": "clicertxsappname",
  "oauth2-configuration": {
    "credential-types": ["x509"]
  }
}

package.json

{
  "dependencies": {
    "axios": "0.24.0",
    "@sap/xssec": "3.2.11",
    "node-fetch": "2.6.2"
  }
}

Appendix 1: Sample Code axios

script_axios.js

const https = require('https')  
const axios = require('axios');

// credentials containing client certificate
const CREDENTIALS = {
    "certificate": "-----BEGIN CERTIFICATE-----\nMIIFtTCCA52gAwIBAgIRAL6nYMD34UBydTLYLj81+MQwDQYJKoZIhvcNAQELBQAw\neTELMAkGA1UEBhMCREUxDTALBgNVBAcMBFVsjDhyuGaEMCQLQciRfFu8aw6UiVu6lHKyKWdDNb4po7QcAjTCQyALJI7qWRQ\n+uae8UrAx4ZHklT62U1w8JENcckO443jXlTrFvemc47e0rsrZuvsNrjWomz8AZ7D\nMv94YSJQwOZCNDzoYiDf76eqy6y7dEGZzg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGYDCCBEigAwIBAgITcAAAAAinzst7Sn3MVgAAAAAACDANBgkqhkiG9w0BAQsF\nADBNMQswCQYDVQQGEwJERTERMA8GA1UEBwwDSBK4w2B+bom+dp\nwiokUHs3zqcnJimjoV5+bYaQuA8KEDpUoSyWbu0CnvqiFn4UUvh5/7RM8xlNYAbf\n/VvkzA==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFZjCCA06gAwIBAgIQGHcPvmUGa79M6pM42bGFYjANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJERTERMA8GA1UEBwwIV2FhNDM3rMsLu06agF4JTbO8ANYtWQTx0PVrZKJu+8fcIaUp7MVBIVZ\n-----END CERTIFICATE-----\n",
    "certurl": "https://1234abcdtrial.authentication.cert.us10.hana.ondemand.com",
    "clientid": "sb-clicertxsappname!t12345",
    "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAoG0ENBX+IxI+eFYeg0HeQe+WUUbcj6m5kdu2EQpC76yIYXxf\nBKsdBDZvL2HU/zL0F95n6ePslpmCiRhvC8oYAwXf7CCQJFRczSCRPSMc+HvU7iBmMcSkDfXfX/\n1OAvPsVkkoExhlL9S8hS2ie/Fq07rtfGR6M0ZU2Uahafyz7q/ewu\n-----END RSA PRIVATE KEY-----\n"
}

// configure request
const options = {
    url: CREDENTIALS.certurl + '/oauth/token',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    method: 'POST',
    data : `grant_type=client_credentials&response_type=token&client_id=${CREDENTIALS.clientid}`,
    httpsAgent : new https.Agent({
        cert: CREDENTIALS.certificate,
        key: CREDENTIALS.key
    })
}

// execute request
axios(options).then(result => {
    console.log(`Result: ${JSON.stringify(result.data)}`)
})

Appendix 2: Sample Code node-fetch

script_fetch.js

const https = require('https')  
const fetch = require('node-fetch')

// credentials with client certificate
const CREDENTIALS = {
    "certificate": "-----BEGIN CERTIFICATE-----\nMIIFtTCCA52gAwIBAgIRAL6nYMD34UBydTLYLj81+MQwDQYJKoZIhvcNAQELBQAw\neTELMAkGA1UEBhMCREUxDTALBgNVBAcMBFV47e0rsrZuvsNrjWomz8AZ7D\nMv94YSJQwOZCNDzoYiDf76eqy6y7dEGZzg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGYDCCBEigAwIBAgITcAAAAAinzst7Sn3MVgAAAAAACDANBgkqhkiG9w0BAQsF\nADBNMQswCQYDVQQGEwJERTERMA8GA1UEBwwDSBK4w2B+bom+dp\nwiokUHs3zqcnJimjoV5+bYaQuA8KEDpUoSyWbu0CnvqiFn4UUvh5/7RM8xlNYAbf\n/VvkzA==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFZjCCA06gAwIBAgIQGHcPvmUGa79M6pM42bGFYjANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJERTERMA8GA1UEBwwIV2FhNDM3rMsLu06agF4JTbO8ANYtWQTx0PVrZKJu+8fcIaUp7MVBIVZ\n-----END CERTIFICATE-----\n",
    "certurl": "https://abcd1234trial.authentication.cert.us10.hana.ondemand.com",
    "clientid": "sb-clicertxsappname!t12345",
    "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAoG0ENBX+IxI+eFYeg0HeQe+WUUbcj6m5kdu2EQpC76yIYXxf\nBKsdBDZvL2HU/zL0F95n6ePslpmCiRhvC8oYAwXf7CCQJFRczSCRPSMc+HvU7iBmMcSkDfXfX/\n1OAvPsVkkoExhlL9S8hS2ie/Fq07rtfGR6M0ZU2Uahafyz7q/ewu\n-----END RSA PRIVATE KEY-----\n"
}

// configure request
const url = CREDENTIALS.certurl + '/oauth/token'
const params = new URLSearchParams()
params.append('grant_type', 'client_credentials')
params.append('response_type', 'token')
params.append('client_id', CREDENTIALS.clientid)

const options = {
    method: 'POST',
    body: params,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    agent : new https.Agent({
        cert: CREDENTIALS.certificate,
        key: CREDENTIALS.key
    })
}

// execute request
fetch(url, options).then(response => {
    response.json().then (data => {
        console.log(`Result: ${JSON.stringify(data)}`)
    })
}) 

Appendix 3: Sample Code native

script_https.js

const https = require('https')  

const CREDENTIALS = {
    "certificate": "-----BEGIN CERTIFICATE-----\nMIIFtTCCA52gAwIBAgIRAL6nYMD34UBydTLYLj81+MQwDQYJKoZIhvcNAQELBQAw\neTELMAkGA1UEBhMCREUxDTALBgNVBAcMBFV47e0rsrZuvsNrjWomz8AZ7D\nMv94YSJQwOZCNDzoYiDf76eqy6y7dEGZzg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGYDCCBEigAwIBAgITcAAAAAinzst7Sn3MVgAAAAAACDANBgkqhkiG9w0BAQsF\nADBNMQswCQYDVQQGEwJERTERMA8GA1UEBwwDSBK4w2B+bom+dp\nwiokUHs3zqcnJimjoV5+bYaQuA8KEDpUoSyWbu0CnvqiFn4UUvh5/7RM8xlNYAbf\n/VvkzA==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFZjCCA06gAwIBAgIQGHcPvmUGa79M6pM42bGFYjANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJERTERMA8GA1UEBwwIV2FhNDM3rMsLu06agF4JTbO8ANYtWQTx0PVrZKJu+8fcIaUp7MVBIVZ\n-----END CERTIFICATE-----\n",
    "certurl": "https://1234abcdtrial.authentication.cert.us10.hana.ondemand.com",
    "clientid": "sb-clicertxsappname!t12345",
    "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAoG0ENBX+IxI+eFYeg0HeQe+WUUbcj6m5kdu2EQpC76yIYXxf\nBKsdBDZvL2HU/zL0F95n6ePslpmCiRhvC8oYAwXf7CCQJFRczSCRPSMc+HvU7iBmMcSkDfXfX/\n1OAvPsVkkoExhlL9S8hS2ie/Fq07rtfGR6M0ZU2Uahafyz7q/ewu\n-----END RSA PRIVATE KEY-----\n"
}

const options = {
    cert:  CREDENTIALS.certificate,
    key: CREDENTIALS.key,
    host: CREDENTIALS.certurl.replace('https://', ''),
    path: '/oauth/token',
    method: 'POST',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}

const requestBody = `client_id=${CREDENTIALS.clientid}&grant_type=client_credentials&response_type=token`
const req = https.request(options, (res) => {
    let response = ''
    res.on('data', (chunk) => {
        response += chunk
    })
    res.on('end', ()=>{
        console.log(`Result: ${response}`)
    })
})
        
req.write(requestBody)
req.end()           

Appendix 4: Sample Code xssec

script_xssec.js

const xssec = require('@sap/xssec')

const CREDENTIALS = {
    "certificate": "-----BEGIN CERTIFICATE-----\nMIIFtTCCA52gAwIBAgIRAL6nYMD34UBydTLYLj81+MQwDQYJKoZIhvcNAQELBQAw\neTELMAkGA1UEBhMCREUxDTALBgNVBAcMBFV47e0rsrZuvsNrjWomz8AZ7D\nMv94YSJQwOZCNDzoYiDf76eqy6y7dEGZzg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGYDCCBEigAwIBAgITcAAAAAinzst7Sn3MVgAAAAAACDANBgkqhkiG9w0BAQsF\nADBNMQswCQYDVQQGEwJERTERMA8GA1UEBwwDSBK4w2B+bom+dp\nwiokUHs3zqcnJimjoV5+bYaQuA8KEDpUoSyWbu0CnvqiFn4UUvh5/7RM8xlNYAbf\n/VvkzA==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFZjCCA06gAwIBAgIQGHcPvmUGa79M6pM42bGFYjANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJERTERMA8GA1UEBwwIV2FhNDM3rMsLu06agF4JTbO8ANYtWQTx0PVrZKJu+8fcIaUp7MVBIVZ\n-----END CERTIFICATE-----\n",
    "certurl": "https://1234abcdtrial.authentication.cert.us10.hana.ondemand.com",
    "clientid": "sb-clicertxsappname!t45183",
    "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAoG0ENBX+IxI+eFYeg0HeQe+WUUbcj6m5kdu2EQpC76yIYXxf\nBKsdBDZvL2HU/zL0F95n6ePslpmCiRhvC8oYAwXf7CCQJFRczSCRPSMc+HvU7iBmMcSkDfXfX/\n1OAvPsVkkoExhlL9S8hS2ie/Fq07rtfGR6M0ZU2Uahafyz7q/ewu\n-----END RSA PRIVATE KEY-----\n",
    "url": "https://1234abcdtrial.authentication.us10.hana.ondemand.com",
}
   
// request JWT token
xssec.requests.requestClientCredentialsToken(null, CREDENTIALS, null, null, (error, token)=>{            
    console.log(`Result: ${token}`)
})

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.