Not able to get Apple Pay payment session - node.js

We are trying to implement ApplePay on the web in my project. As per apple documentation i am getting the validation url from applejs api onvalidatemerchant function and i am passing this validation url to a node js route to get the apple payment session. This is the documentation i am following to get the apple payment session(https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/requesting_an_apple_pay_payment_session).
The below is the custom node js code i have written to get the apple payment session. The validation url ie)req.query.url passed to this node js route code is https://apple-pay-gateway-cert.apple.com/paymentservices/startSession.
app.get('/getAppleSession2', function (req, res) {
var endpointURL = req.query.url;
var cert_path = './apple_pay.pem';
var cert = fs.readFileSync(cert_path);
console.log('endpointURL is ' + endpointURL);
console.log('cert_path is ' + cert_path);
console.log('cert is'+cert);
const options = {
url: endpointURL,
method: 'post',
cert: cert,
key: cert,
body: {
merchantIdentifier: "xxxxx.xxxxx.xxxxx.xxxxx.xxxxx",
displayName: "xxxxx.xxxxx.xxxxx.xxxxx.xxxxx",
initiative: "web",
initiativeContext: "xxxxx.xxxx.xxxx.xxxx.com"
},
json: true,
};
//console.log("body" + body);
request(options, function (error, response, body) {
console.log('body of getAppleSession' + body);
console.log('Response from getAppleSession' + response);
console.error('Error object ' + error);
res.send(body);
});
});
But this is the response i am getting for this route
body of getAppleSession undefined
Response from getAppleSession undefined
Error object Error: error:0906D06C:PEM routines:PEM_read_bio:no start line
Not sure what is wrong here as i am doing this as per apple's documentation. I suspect if it has anything to do with how i am passing the certs(Merchant Identity certificate)to this nodejs route. I generated the certificate by downloading the Merchant Identity certificate in .cer format from Apple Development portal and converted the certificate i downloaded from Apple portal to .pem format by importing the .cer file in KeyChain access in my mac and exporting it to .pem in keychain access. I then placed the .pem file('./apple_pay.pem') in same directory of my node js route. Is there anything wrong in how i am generating the certificates or passing them in my node js route?
Not sure what is wrong here. Any code sample or pointers will be really helpful.

Seems like this is due to certificate validity related issue. Would you please ensure whether a self-signed certificate is valid.
Hope this may helps.

I might be late, but leaving a working solution here for anyone else who stumbles upon this same issue:
Before beginning with the creation of api for requesting the Apple Pay Session, you need to the create the payment processing and merchant identifier certificates. A detailed explanation can be found at this link:
At the end of the merchant identifier certificate process, you will be left with a .cer file. Double click on this file to add it to your keychain app.
After this, Go to your keychain, right click on certificate, and export it as PKCS #12 (.p12). You can then use openssl to convert this to a .pem file or you can use the .p12 directly (in nodejs with request. You need to set agentOptions as {pfx: p12File, and passphrase: '***'}.
My working solution for NodeJS is as below:
async validateMerchant(ctx) {
let response = {};
try {
const options = {
url: ctx.query.validationURL,
agentOptions: {
pfx: fs.readFileSync(
path.resolve(__dirname, 'MerchantIDCertificate.p12')
),
passphrase: '********',
},
method: 'post',
body: {
merchantIdentifier: 'merchant.***.***.*****',
displayName: 'Your Store Name',
initiative: 'web',
initiativeContext: 'Verified.domain.com',
},
json: true,
};
response = await this.promisifyRequest(options);
} catch (error) {
logger.error(error);
}
return response;
}
promisifyRequest(options) {
return new Promise((resolve, reject) => {
request(options, (error, response, body) => {
if (body) {
console.log(response);
return resolve(body);
}
if (error) {
return reject(error);
}
});
});
}

Look here, how they use apple pay certificate:
Plus their docs are great, look here
Looks like we can also generate .pem file without .p12 file (according to their docs), which was an issue for me (because macOS disabled this format export in KeyChain Access, don't know why).
Another good tutorial:
https://ionutghisoi.medium.com/apple-pay-example-payments-2-86ff893fdc9c
and their example of code how to get payment session:
app.post('/validateSession', async (req, res) => {
const { appleUrl } = req.body;
// use set the certificates for the POST request
httpsAgent = new https.Agent({
rejectUnauthorized: false,
cert: fs.readFileSync(path.join(__dirname, './certificate.pem')),
key: fs.readFileSync(path.join(__dirname, './sandbox.key')),
});
response = await axios.post(
appleUrl,
{
merchantIdentifier: 'your.apple.merchant.id',
domainName: 'yourdomain.com',
displayName: 'Your Shop Name',
},
{
httpsAgent,
}
);
res.send(response.data);
});

Related

Reactjs + Express Twitter API "Could not authenticate you."

I am trying to create a very simple app that allows me to post a tweet. I am currently using React running on port 3000 and express server.js running on port 5000
my server.js has the following:
app.post("/twitter/message", async(req, res) => {
const tweet = req.body.tweet;
try {
const response = await postToTwitter(tweet);
res.json({
message: 'Tweet successfully posted to twitter'
});
} catch (error) {
res.status(500).json({
message: 'Not able to post'
});
}
});
function postToTwitter(tweet) {
client.post(
"statuses/update",
{ status: tweet },
function (error, tweet, response) {
if (error) log(error);
/* log(tweet); // Tweet body. */
}
);
}
I am then using a script on the index.html page to post the input tweet:
<script>
$('button').on('click', (event) => {
event.preventDefault();
const tweet = $('#tweet').val();
// Post Tweet
$.ajax({
url: '/twitter/message',
method: 'POST',
data: {
tweet
}
})
.then(() => {
alert('Data successfully posted');
console.log('Data successfully posted');
})
.catch((error) => {
alert('Error: ', error);
console.log('Error: ', error);
});
})
</script>
This however is giving me the bellow error when I hit the post button:
[ { code: 32, message: 'Could not authenticate you.' } ]
If I use this exact same setup with just express it works perfectly fine, the issue occurs when trying to use react. Any help would be amazing.
It is possibly a CORS issue (which would show up in the frontend but not in Node/Backend).
If you're using some sort of API key to make the API request you're not showing it in this sample (don't show people your API key). By similar logic, do not have your API key on the client side, as anyone downloading your website would then have your Twitter API key. Instead, for multiple reasons it is better to have the backend be the one to make the API requests with your API key.
On the other hand if users are supposed to authenticate via O-Auth and you're supposed to pass a cookie with your authentication make sure you useCredentials on the request. axios.post(BASE_URL + '/api', { withCredentials: true }); . Looks like you're using jquery so add the same withCredentials:
Try adding this to your options:
crossDomain: true,
xhrFields: {
withCredentials: true
},
If you don't see a cookie when you type document.cookie in the browser that's probably a sign you're not authenticated in your computer.

Could not obtain grant code: Error: self signed certificate in certificate chain - NodeJS adapter Keycloak

I have application written in nodeJS. I'm using keycloak-connect from npm to log in using keycloak.
When I want log in app redirects me to the keycloak website, then I enter the login details, after that I got error:
"Could not obtain grant code: Error: self signed certificate in certificate chain"
post-auth.js:58
I know i can use process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; but it's not safe.
How i can set CA cert in nodeJS / keycloak-adapter?
post-auth.js:
keycloak.getGrantFromCode(request.query.code, request, response)
.then(grant => {
let urlParts = {
pathname: request.path,
query: request.query
};
delete urlParts.query.code;
delete urlParts.query.auth_callback;
delete urlParts.query.state;
delete urlParts.query.session_state;
let cleanUrl = URL.format(urlParts);
request.kauth.grant = grant;
try {
keycloak.authenticated(request);
} catch (err) {
console.log(err);
}
response.redirect(cleanUrl);
}).catch((err) => {
keycloak.accessDenied(request, response, next);
console.error('Could not obtain grant code: ' + err);
});
keycloak.js:
Keycloak.prototype.getGrantFromCode = function (code, request, response) {
if (this.stores.length < 2) {
// bearer-only, cannot do this;
throw new Error('Cannot exchange code for grant in bearer-only mode');
}
var sessionId = request.session.id;
var self = this;
return this.grantManager.obtainFromCode(request, code, sessionId)
.then(function (grant) {
self.storeGrant(grant, request, response);
return grant;
});
};
You should just fix this at the environment level by configuring the NODE_EXTRA_CA_CERTS environment variable to point to a file location containing trusted root authorities.
My blog post has some more info on this:
NodeJS SSL Trust Configuration
Trust your own Self Signed Root Authority
Typically you would just make this configuration change in the early stages of your deployment pipeline, such as on a developer PC. Of course it should not be made in production environments.

NodeJS SOAP client request with certificate

I'm trying to connect to a SOAP service which requires a certificate on my NodeJS project.
I'm using soap and this is how I'm trying to access to it:
const soap = require('soap');
(...)
let sslOptions = {
key: fs.readFileSync(keyPath),
cert: fs.readFileSync(certPath)
};
let sslHeaders = {
Authorization: {
user: 'Z100079',
pass: password
}
};
Service.GetLossRatio = function (contractID, start, end, asOf, cb) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
let args = {
contractID: contractID,
start: start,
end: end,
asOf: asOf
};
soap.createClientAsync(url,
{
//request : specialRequest,
wsdl_options: sslOptions,
wsdl_headers: sslHeaders
})
.then((client) => {
client.setSecurity(
new soap.ClientSSLSecurityPFX(
certCerPath
)
);
client.LossratioService.LossratioPort.calculate(args, (err, res) => {
if (err) {
console.log(err);
return cb(err);
}
console.log(res);
return cb(null, res);
});
})
.catch((e) => {
console.log(e);
cb(e);
});
};
And I'm getting a "Wrong Tag" Error when the LossratioPort.calculate() occurs.
I've no idea what that error means, I can't find much documentation about this specific situation, the "soap" documentation only show a brief explenation on how to make a request with certificates but there's not much more
I know the certificates are valid and I've tried with all the generated certificates from the .pem file (.p12, and .cer). I just want to be sure I'm getting something from the service. Either what I really want, or an error from the server, not from the api.
Any help?
Thanks in advance.
UPDATE:
I'm able to get the service description thorugh client.describe() though:
{"LossratioService":{"LossratioPort":{"calculate":{"input":"tns:calculate","output":"tns:calculateResponse"},"ping":{"input":"tns:ping","output":"tns:pingResponse"},"shakedownTest":{"input":"tns:shakedownTest","output":"tns:shakedownTestResponse"}}}}
I've also confirmed the inputs, and I'm sending as the service requires.

Authentication Error when Retrieving and Editing Device Configuration on IoT-Core

I'm trying to use a backend nodeJS server to access (and edit) the device configuration on IoT-Core referring to this API docs
However, I keep getting error:
code 401 with error message "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED".
I created a service account and a key from Google IAM, and gave it Cloud IoT Device Controller permissions, which could update device configurations but not create or delete. Subsequently, I changed it to Cloud IoT Admin and even Project Editor permissions, but still saw the same error message. Am I getting the keys all wrong, or not doing something else I should be doing?
Code below was how I invoked the request
function createJwt (projectId, privateKeyFile, algorithm) {
// Create a JWT to authenticate this device. The device will be disconnected
// after the token expires, and will have to reconnect with a new token. The
// audience field should always be set to the GCP project ID.
const token = {
'iat': parseInt(Date.now() / 1000),
'exp': parseInt(Date.now() / 1000) + 20 * 60, // 20 minutes
'aud': projectId
};
const privateKey = fs.readFileSync(privateKeyFile);
return jwt.sign(token, privateKey, { algorithm: algorithm });
}
app.get('/', function(req, res){
let authToken = createJwt('test-project', './keys/device-config.pem', 'RS256');
const options = {
url: 'https://cloudiot.googleapis.com/v1/projects/test-project/locations/us-central1/registries/dev-registry/devices/test-device',
headers: {
'authorization': 'Bearer ' + authToken,
'content-type': 'application/json',
'cache-control': 'no-cache'
},
json: true
}
request.get(options, function(error, response){
if(error) res.json(error);
else res.json(response);
})
});
For backend servers to interact with IoT-Core, the authentication method is not the same as for device MQTT or HTTP connections. Reference: https://cloud.google.com/iot/docs/samples/device-manager-samples#get_a_device
I was able to retrieve and update device configurations using the code below
function getClient (serviceAccountJson, cb) {
const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountJson));
const jwtAccess = new google.auth.JWT();
jwtAccess.fromJSON(serviceAccount);
// Note that if you require additional scopes, they should be specified as a
// string, separated by spaces.
jwtAccess.scopes = 'https://www.googleapis.com/auth/cloud-platform';
// Set the default authentication to the above JWT access.
google.options({ auth: jwtAccess });
const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
const API_VERSION = 'v1';
const discoveryUrl = `${DISCOVERY_API}?version=${API_VERSION}`;
google.discoverAPI(discoveryUrl, {}, (err, client) => {
if (err) {
console.log('Error during API discovery', err);
return undefined;
}
cb(client);
});
}
function getDevice (client, deviceId, registryId, projectId, cloudRegion) {
const parentName = `projects/${process.env.GCP_PROJECT_ID}/locations/${cloudRegion}`;
const registryName = `${parentName}/registries/${registryId}`;
const request = {
name: `${registryName}/devices/${deviceId}`
};
const promise = new Promise(function(resolve, reject){
client.projects.locations.registries.devices.get(request, (err, data) => {
if (err) {
console.log('Could not find device:', deviceId);
console.log(err);
reject(err);
} else {
console.log(data.config.binaryData);
resolve(data);
}
});
});
return promise;
}
app.get('/', function(req, res){
const cb = function(client){
getDevice(client, 'test-device', 'dev-registry', process.env.GCP_PROJECT_ID, 'us-central1')
.then(function(response){
let decoded = new Buffer(response.config.binaryData, 'base64').toString();
res.json(decoded);
})
.catch(function(error){
res.json(error);
})
}
getClient(serviceAccountJson, cb);
});
I think what you're looking to do is best accomplished using the client library for NodeJS.
First, retrieve an API client object as done in the sample. This will take in the service account credentials you used and will authenticate against Google API Core servers.
At the point in the referenced code where cb(client); is invoked, you'll have your client object and are ready to update your device. Add the imports and API constants from the sample and replace the code where you have a client object with the following code and you should be set.
Use some strings for your device identifiers:
const projectId = 'my-project';
const cloudRegion = 'us-central1';
const registryId = 'my-registry';
const deviceId = 'my-device;
const config = '{fan: 800}';
Next, form your device String:
const deviceId = `projects/${projectId}/locations/${cloudRegion}/registries/${registryId}/devices/${deviceId}`;
const binaryData = Buffer.from(config).toString('base64');
Now you form your request object and execute:
const request = {
name: `${registryName}`,
versionToUpdate: 0,
binaryData: binaryData
};
console.log(request);
client.projects.locations.registries.devices
.modifyCloudToDeviceConfig(
request,
(err, data) => {
if (err) {
console.log('Could not update config:', deviceId);
console.log('Message: ', err);
} else {
console.log('Success :', data);
}
});
Your configuration is updated. If your device is subscribed to the config topic on MQTT it will receive the latest configuration, otherwise, you can poll for the configuration with HTTP from your device.
Just to confirm, when you created the SSL key pair, and when you registered the device with the Cloud IoT Core registry, did you match the type of key created with the radio button you registered it with?
Also to confirm, you put the Google root certificate on the device in the same directory as the private key: ./keys/device-config.pem ? If not you can fetch it with: wget https://pki.google.com/roots.pem.

node-soap - How to pass certificates and basic Authorization header with every request?

I am using node-soap lib for SOAP services and using it for first time. I am having requirement that I need to pass both Certificates and Basic Authorization header with every request compulsory.
I have implemented my code as follow :
var options = {
wsdl_options: {
key: fs.readFileSync(path.resolve("./xxx.key")),
cert: fs.readFileSync(path.resolve("./xxx.crt")),
ca: fs.readFileSync(path.resolve("./xxx.pem")),
},
wsdl_headers : {
Authorization : 'Basic ' + new Buffer(username +':'+ password ).toString('base64')
},
"overrideRootElement": {
"namespace": "con",
},
envelopeKey : 'soapenv'
};
soap.createClient(url, options, function(err, client) {
if(err){
console.log("Error ::: >",err);
res.json({message : err});
}
if(client){
console.log(JSON.stringify(client.describe()));
var data = actualRequestObject
client.setSecurity(new soap.ClientSSLSecurity(
fs.readFileSync(path.resolve("./XXX.key")),
fs.readFileSync(path.resolve("./XXX.crt")),
fs.readFileSync(path.resolve("./XXX.pem"))
));
client.setSecurity(new soap.BasicAuthSecurity(username, password));
client.IndicativeEnrichment(data, function(err, result){
console.log("lastRequest :::: >>>>> ",client.lastRequest);
if(err){
console.log("ERROR Enrichment :::: >>> ", err);
}
if(result){
console.log("RESULT ::: >>>>", result);
}
})
}
});
When I am trying to set Basic auth and Certs both using setSecurity() method. It overrides the first thing that I've set using setSecurity(). I am getting unauthorized error if I don't pass any one of these.
Please help me with providing the solution the solution.
A good way to get both client certificates and basic authentication would be to implement your own node-soap security protocol. You can either get inspired by the existing node-soap security protocols and combine them, or write one which is generic enough to chain two (or more) existing security protocols. It would of course be even better to create a pull request with the solution, so it can be considered for inclusion directly in node-soap.
Personally ended up passing additional options with a configured https.Agent(...) to the BasicAuthSecurity constructor/class.
var https = require('https');
var options = {
// https://nodejs.org/api/https.html#https_class_https_agent
agent: new https.Agent({
key: someKeyBuffer,
cert: someCertBuffer,
ca: [
someCACertBuffer,
]
});
}
client.setSecurity(new soap.BasicAuthSecurity('username', 'password', options));
This way can also be used to combine basic authentication with .pfx or .p12 files used in ClientSSLSecurityPFX

Resources