Coinbase web API through Node fetch "invalid API key" (not Coinbase pro) - node.js

Just trying to get my Coinbase balance. I have tried making a bunch of different API keys, keep getting the same error:
{
"errors": [{
"id": "authentication_error",
"message": "invalid api key"
}]
}
Im using Node.js through Netlify Lambda functions.
Here's my code:
import fetch from "node-fetch"
import crypto from "crypto"
const mykey = '<KEY>'
const mysecret = '<SECRET>'
exports.handler = async (event, context) => {
const url = `https://api.coinbase.com/v2/accounts`
var nonce = Math.floor(new Date().getTime() * 1e-3)
var my_hmac = crypto.createHmac('SHA256', nonce+'POST'+'v2/accounts', mysecret)
my_hmac.update(nonce + url)
var signature = my_hmac.digest('hex')
var msg;
return fetch(url, { headers:
{
'CB-ACCESS-KEY' : mykey,
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': nonce,
'Content-Type': 'application/json'
}
}).then(res => {
// console.log(res)
res.json
})
.then(data => {
return ({
statusCode: 200,
body: JSON.stringify(data)
})
})
}

You are using the wrong names for the tokens.
ACCESS_KEY is supposed to be CB-ACCESS-KEY
ACCESS_SIGNATURE is supposed to be CB-ACCESS-SIGN
I couldn't find info about the nonce. I found this over here.
Update:
signature looks like it is not made properly:
The nonce+'POST'+'/v2/accounts' is supposed to be the value in my_hmac.update
In turn for the createHmac it is only supposed to be SHA256 and mysecret
The signature pre-hash value is supposed to have a / at the beginning
A useful reference is
here (be sure to click node.js at the top).

Related

Authenticating FTX API SHA256 HMAC with Node

I am lost with using HMAC SHA256 for api authentication. This is my first time using it and I'm not sure what I am missing although I suspect it has to do with the timestamp. Can someone please help me identify what it is I am missing?
Everytime I try and make an API call I get a response stating
data: { success: false, error: 'Not logged in: Invalid signature' }
Here are the requirements for making the API call including the HMAC SHA256.
Here is the code I am using currently:
const axios = require('axios');
var forge = require('node-forge');
require('dotenv').config()
// get timestamp
var time = new Date().getTime();
// generate and return hash
function generateHash(plainText,secretKey)
{
var hmac = forge.hmac.create();
hmac.start('sha256', secretKey);
hmac.update(plainText);
var hashText = hmac.digest().toHex();
return hashText
}
// set axios config
var config = {
url:"https://ftx.us/api/wallet/all_balances",
method:"GET",
headers :{
"FTXUS-KEY":process.env.FTX_API_KEY,
"FTXUS-TS":time,
"FTXUS-SIGN":generateHash(`${new Date()}${"GET"}${"/wallet/all_balances"}`,process.env.FTX_API_SECRET)
}
}
axios(config)
.then(response => {
console.log(response.data)
}).catch(function (error) {
console.log(error);
})
I had to go through the same issue, so here goes my code.
import * as crypto from "crypto";
import fetch from "node-fetch";
// a function to call FTX (US)
async function callFtxAPIAsync(secrets, method, requestPath, body) {
const timestamp = Date.now();
const signaturePayload = timestamp + method.toUpperCase() + "/api" + requestPath + (method.toUpperCase() == "POST" ? JSON.stringify(body) : "");
const signature = crypto.createHmac('sha256', secrets.secret)
.update(signaturePayload)
.digest('hex');
const response = await fetch("https://ftx.us/api" + requestPath, {
method: method,
body: body != null ? JSON.stringify(body) : "",
headers: {
'FTXUS-KEY': secrets.key,
'FTXUS-TS': timestamp.toString(),
'FTXUS-SIGN': signature,
"Content-Type": "application/json",
"Accepts": "application/json"
}
});
return await response.json();
}
then call a post endpoint as for example:
let resultQuote = await callFtxAPIAsync(secrets, "post", "/otc/quotes",
{
"fromCoin": "USD",
"toCoin": "ETH",
"size": usd
});
or a get one:
let resultQuote = await callFtxAPIAsync(secrets, "get", "/otc/quotes/1234");
I hope it helps 😄
You need to add the full URL path, except the domain, in your case /api is missing. Try this:
"FTXUS-SIGN":generateHash(`${new Date()}${"GET"}${"/api/wallet/all_balances"}`,process.env.FTX_API_SECRET)

Creating Wallet Address with coinbase API Using Axios Request

Hello I'm trying to use the coinase api using axios to make request. I have set up the neccessary api authentication using SHA256 HMAC. I have been able to make some GET Request and got response. I have been trying to make a POST Request but i have been getting 401 status code.
const name = "Test BTC Address";
const body = {
name: name
}
var encHash = {
baseUrl: 'https://api.coinbase.com',
method: 'POST',
path: '/v2/accounts/bbc2e3f7-a851-50ab-b4b3-a0f2a700846f/addresses',
body: body,
scopes: "wallet:addresses:create"
};
const sign = generateHashKey(encHash, key.APISECRET);
console.log(sign);
const config = {
headers: {
'CB-ACCESS-SIGN': sign.signature,
'CB-ACCESS-TIMESTAMP': sign.timestamp,
'CB-ACCESS-KEY': key.APIKEY,
'CB-VERSION': '2021-10-15'
}
}
const url = `${encHash.baseUrl}${encHash.path}`
console.log(url);
var options = await axios.post(url, body, config);
return res.send({data: options.data})
} catch (error) {
// console.error(error);
return res.send({error})
} ```

x-www-form-urlencoded format - using https in node.js

I am currently writing to an API to try and get a token. I'm nearly there but fallen at the last hurdle..
const fs = require('fs');
const https = require('https');
const ConfigParams = JSON.parse(fs.readFileSync('Config.json', 'utf8'));
const jwt = require('jsonwebtoken');
const apikey = ConfigParams.client_id;
var privateKey = fs.readFileSync(**MY KEY**);
var tkn;
const jwtOptions = {
algorithm: 'RS512',
header: { kid: 'test-1' }
}
const jwtPayload = {
iss: apikey,
sub: apikey,
aud: **API TOKEN ENDPOINT**,
jti: '1',
exp: 300
}
jwt.sign(jwtPayload,
privateKey,
jwtOptions,
(err, token) => {
console.log(err);
//console.log(token);
tkn = token;
let = tokenPayload = {
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer/',
client_assertion: tkn
}
tokenAuthOptions = {
payload: tokenPayload,
host: **HOST**,
path: **PATH**,
method: 'POST',
}
https.request(
tokenAuthOptions,
resp => {
var body = '';
resp.on('data', function (chunk) {
body += chunk;
});
resp.on('end', function () {
console.log(body);
console.log(resp.statusCode);
});
}
).end();
}
)
the encoded token comes back fine for the first part, the https request though returns a problem.
the response I get back is grant_type is missing, so I know I have a formatting problem due to this x-www-form-urlencoded, but I can't figure out how to fix it.
here is what the website said:
You need to include the following data in the request body in
x-www-form-urlencoded format:
grant_type = client_credentials client_assertion_type =
urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion = <your signed JWT from step 4> Here's a complete
example, as a CURL command:
curl -X POST -H "content-type:application/x-www-form-urlencoded"
--data \ "grant_type=client_credentials\ &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion="
END POINT
Ideally I want a solution using the https request, but if that's not possible I'm open to other solutions.
Any help is greatly appreciated.
Thanks,
Craig
Edit - I updated my code based on a suggestion to:
const params = new url.URLSearchParams({
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer/',
client_assertion: tkn
});
axios.post("URL", params.toString()).then(resp => {
console.log("response was : " + resp.status);
}).catch(err => {
console.log("there was an error: " + err);
})
But I'm still getting an error code 400, but now with less detail as to why. (error code 400 has multiple message failures)
Postman is the best.
Thank for #Anatoly for your support which helped to point me in the right direction. I had no luck so used postman for the first time, and found it had a code snippet section, with four different ways of achieving this using node.js.
The solution with Axion was:
const axios = require('axios').default;
const qs = require('qs');
var data = qs.stringify({
'grant_type': 'client_credentials',
'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion': tkn
});
var config = {
method: 'post',
url: '',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.status));
})
.catch(function (error) {
console.log(error);
});
I believe the issue was that I was not passing the information into 'data:' in combination with the querystring problem. Using qs.stringify to format the object, then passing this into the data: key solved the problem.

How to call a restricted firebase function with a service account key file?

I am trying to setup a restricted firebase function that can be called from another client application that runs outside GCP. So far I failed to setup the client application authentication to get passed the restricted access on the firebase funciton.
Here is what I did and tried:
I created and deployed a simple helloWorld firebase function and verified that the function could be called from the client application with the default public access.
I removed allUsers from the helloWorld permissions on GCP and verified that the function could no longer be called from the client application (I get "403 Forbidden" in the response).
I created a new service account and added it as a member of "Cloud functions invoker" in the permissions panel of helloWorld on the GCP.
I created a new private json key file for this service account.
Then I followed the documentation to setup the client application authentication (see code below).
const fetch = require('node-fetch');
const jwt = require('jsonwebtoken');
async function main(){
// get unix timestamp in seconds
const current_time = Math.floor(Date.now() / 1000)
// get the service account key file
const service_account = require('./service_account.json');
// create the jwt body
const token_body = {
"iss": service_account.client_email,
"scope": "https://www.googleapis.com/auth/cloud-platform",
"aud": "https://oauth2.googleapis.com/token",
"exp": current_time + 3600,
"iat": current_time
}
// sign the token with the private key
const signed_token = jwt.sign(
token_body, service_account.private_key, { algorithm: 'RS256' }
)
// get an access token from the authentication server
const access_token = await fetch(
'https://oauth2.googleapis.com/token',
{
method: 'POST',
body: ''
+ 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer'
+ '&'
+ 'assertion=' + signed_token,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
).then(res => res.json()).then(body => body.access_token)
// call the firebase function with the Authorization header
return fetch(
url_hello_world, { headers: { 'Authorization': 'Bearer ' + access_token } }
).then(res => res.text()).then(console.log)
}
main().catch(console.error)
Unfortunately when I run the previous code I get "401 Unauthorize" with the following header:
www-authenticate: Bearer error="invalid_token" error_description="The access token could not be verified"
After that I tried another approach with the following tutorial (see code below).
const fetch = require('node-fetch');
const util = require('util');
const exec = util.promisify(require("child_process").exec)
async function main(){
// activate a service account with a key file
await exec('gcloud auth activate-service-account --key-file=' + key_file)
// retrieve an access token for the activated service account
const {stdout, stderr} = await exec("gcloud auth print-identity-token")
// get the access token from stdout and remove the new line character at the
// end of the string
const access_token = stdout.slice(0,-1)
// call the firebase function with the Authorization header
const response = await fetch(
url_hello_world,
{ headers: { 'Authorization': 'Bearer ' + access_token } }
)
// print the response
console.log(await response.text())
}
main().catch(console.error)
When I run this code, I get the expected response "Hello World" so the previous code can call the firebase function with the service account permission.
However, the client application that I target cannot rely on the gcloud cli and I am stuck to the point where I tried to understand what does not work in the first version above and what I need to change to make it works.
As you're using a Service Account JSON file, the following code can be helpful.
package.json
{
"name": "sample-call",
"version": "0.0.1",
"dependencies": {
"googleapis": "^62.0.0",
"node-fetch": "^2.6.1"
}
}
index.js
var { google } = require('googleapis')
const fetch = require('node-fetch')
const fs = require('fs');
let privatekey = JSON.parse(fs.readFileSync('key.json'));
let url_hello_world = 'CLOUD_FUNCTION_URL';
let jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
url_hello_world
)
async function main(){
jwtClient.authorize( async function(err, _token) {
if (err) {
console.log(err)
return err
} else {
const response = await fetch(
url_hello_world,
{
headers: { 'Authorization': 'Bearer ' + _token.id_token }
}
)
console.log(await response.text())
}
})
}
main().catch(console.error)

Nodejs googleAPI how to authenticate JWT client with only GET request

its known that the GoogleAPI is pretty much just built on GET requests, the helper node.js module googleapis seemingly just lets you send those GET requests in an easier way, for example:
fetch("https://www.googleapis.com/drive/v3/files/MYID/export?mimeType=text/plain",
{
method:"GET",
headers: {
'Accept-Encoding': 'gzip',
'User-Agent': 'google-api-nodejs-client/0.7.2 (gzip)',
Authorization:t.token_type +" "+ t.access_token,
Accept:"application/json"
}
})
.then(res => res.text())
.then(body => console.log(body));
accomplishes the same as using their built in method:
var drive = google.drive("v3");
drive.files.export({
auth:j,
fileId:"1oBL8sBylvODW1Md0gjXf2UgwBHtb6aRcgyGOYr0kRvY"
}, (e,r) => {
if(e) console.log(e);
else {
console.log(r,"data:::::",r.data);
}
});
So the question: When I'm validating the JWT client in nodejs using a custom service key, I do this:
var google = require("googleapis").google,
creds = require("./mycreds.json"),
fetch = require("node-fetch");
var j = new google.auth.JWT(
creds.client_email,
null,
creds.private_key,
[
"https://www.googleapis.com/auth/drive"
]
);
console.log(google.auth.JWT, "END this");
j.authorize((r,t) => {
doOther(t);
});
I suspect that this is, like every other part of the google API I've encountered so far, just an easier way to send a GET request to a certain URL with certain headers (containing the authorization key).
The question: What exactly is that URL and the headers necessary to do the same thing as j.authorize ? I couldn't find where to look in the reference.
HINT: from line 159 in jwtclient.js file:
createGToken() {
if (!this.gtoken) {
this.gtoken = new gtoken_1.GoogleToken({
iss: this.email,
sub: this.subject,
scope: this.scopes,
keyFile: this.keyFile,
key: this.key,
additionalClaims: this.additionalClaims
});
}
return this.gtoken;
}
still looking though at gtoken_1.GoogleToken etc..

Resources