Nodejs googleAPI how to authenticate JWT client with only GET request - node.js

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..

Related

Nodejs - Axios not using Cookie for post request

I'm struggling with AXIOS: it seems that my post request is not using my Cookie.
First of all, I'm creating an Axios Instance as following:
const api = axios.create({
baseURL: 'http://mylocalserver:myport/api/',
header: {
'Content-type' : 'application/json',
},
withCredentials: true,
responseType: 'json'
});
The API I'm trying to interact with is requiring a password, thus I'm defining a variable containing my password:
const password = 'mybeautifulpassword';
First, I need to post a request to create a session, and get the cookie:
const createSession = async() => {
const response = await api.post('session', { password: password});
return response.headers['set-cookie'];
}
Now, by using the returned cookie (stored in cookieAuth variable), I can interact with the API.
I know there is an endpoint allowing me to retrieve informations:
const readInfo = async(cookieAuth) => {
return await api.get('endpoint/a', {
headers: {
Cookie: cookieAuth,
}
})
}
This is working properly.
It's another story when I want to launch a post request.
const createInfo = async(cookieAuth, infoName) => {
try {
const data = JSON.stringify({
name: infoName
})
return await api.post('endpoint/a', {
headers: {
Cookie: cookieAuth,
},
data: data,
})
} catch (error) {
console.log(error);
}
};
When I launch the createInfo method, I got a 401 status (Unauthorized). It looks like Axios is not using my cookieAuth for the post request...
If I'm using Postman to make the same request, it works...
What am I doing wrong in this code? Thanks a lot for your help
I finally found my mistake.
As written in the Axios Doc ( https://axios-http.com/docs/instance )
The specified config will be merged with the instance config.
after creating the instance, I must follow the following structure to perform a post requests:
axios#post(url[, data[, config]])
My requests is working now :
await api.post('endpoint/a', {data: data}, {
headers: {
'Cookie': cookiesAuth
}
});

Syntax error when trying to fetch jwt key in Node.js and React

I have been trying to get jwt token from REST API I implemented, but every time I try to do it accordingly to tutorials I found online I get an error. I have been searching for this problem 4 hours now but I was unable to find a solution. I read all other questions and tutorials like: How do I store JWT and send them with every request using react. Sadly every found foulution didn't work in my case. I would be grateful if you could at least tell me where can I learn to do it. I have been using REST API I created with Node.js Express in which there is method to generate JWT:
userSchema.methods.generateAuthenticationToken = function () {
const token = jwt.sign(
{ _id: this._id, admin: this.admin, premium: this.premium },
config.get(`jwtPrivateKey`)
);
return token;
};
I try to fetch data in React like this:
await fetch("http://localhost:1234/api/auth", {
method: "POST",
body: JSON.stringify(item),
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then(function (response) {
return response.json();
})
.then(function (data) {
localStorage.setItem("user-info", data.token);
});
Here is error I receive:
Uncaught (in promise) SyntaxError: Unexpected token e in JSON at position 0
And here is JWT key that was generated:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MGE2YTg4YzQzZDU2MzIwMjg1NWZlZjIiLCJhZG1pbiI6dHJ1ZSwicHJlbWl1bSI6dHJ1ZSwiaWF0IjoxNjIyMTMzMzAwfQ.CmQRtSaZc8g3TEF6R7flpJ9499QnfzlfgPNVazFaUsY
Try returning an object instead of a string from your express endpoint, for example:
const token = jwt.sign(
{ _id: this._id, admin: this.admin, premium: this.premium },
config.get(`jwtPrivateKey`)
);
return { token };
};

How to CORRECTLY get token for Cloud Functions in NodeJS running on AppEngine?

I am having problems getting the correct token for triggering my cloud function.
When testing through POSTMAN I get the token by running the following command:
gcloud auth print-identity-token
and my functions works correctly.
But on my server I am using the following code. I also do see the token but I get 401 with this token.
// Constants------------
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
async function gToken(){
let token='';
try{
// Fetch the token
const tokenResponse = await fetch(metadataServerTokenURL + 'https://'+process.env.CLOUD_URL, { //URL WITHOUT THE PATH
headers: {
'Metadata-Flavor': 'Google',
},
});
token = await tokenResponse.text();
} catch (err){
console.log(err);
}
return token;
}
---------EDIT-------
The calling function::
app.get("/", async function(req , res){
try {
const token = await getToken();
console.log(`Token: ${token}`);
const functionResponse = await fetch('https://'+process.env.CLOUD_URL+process.env.PATH_TO_FUNC, {
method: 'post',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`},
});
console.log(`Status: ${await functionResponse.status}`);
res.sendStatus(200);
} catch (err){
console.log(err);
res.status(400).send('Something went wrong')
}
})
My server is my NodeJS code running on AppEngine.
What am I doing wrong please?
----------EDIT 2--------------
I entered the two tokens received using two different ways, they show different information for some reason. Please see below::
Token from the server
Token using gcloud command locally (which works)::
Server code and cloud functions are both hosted in the same region, and are a part of the same project.
process.env.CLOUD_URL > "e****-***2-c******-e******2.cloudfunctions.net"
What #Charles and #John mentioned in the comment is correct. You should include the name of the receiving function in the audience:
As mentioned in the docs:
In the calling function, you'll need to create a Google-signed OAuth ID token with the audience (aud) set to the URL of the receiving function.
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
...
// Fetch the token
const tokenResponse = await fetch(metadataServerTokenURL + `https://${process.env.CLOUD_URL}/${FUNCTION_NAME}`, {
headers: {
'Metadata-Flavor': 'Google',
}
The audience should look like your HTTP trigger URL. If you decode your JWT ID token, aud looks like this:
{
"aud": "https://[REGION]-[PROJECT_ID].cloudfunctions.net/func-post",
"azp": "117513711437850867551",
"exp": 1614653346,
"iat": 1614649746,
"iss": "https://accounts.google.com",
"sub": "117513711437850867551"
}

Coinbase web API through Node fetch "invalid API key" (not Coinbase pro)

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).

oauth-1.0a Node package returning 401 for Zotero API (NodeJS)

I'm using the oauth-1.0a Node package to implement OAuth with the Zotero API.
I'm using the code found in the zotero-oauth-example repo.
Running the code in the repo mentioned above works. I think my implementation is not working because I've split up the single function in the repo into two functions --> the example repo doesn't handle redirecting the client and handling the callback.
What I think the issue is:
I think that instantiating two different OAuth objects (one is step 1, the other in step 3) is breaking something. Note that I'm temporarily persisting the hash produced in step 1's hash_function which is used in step 3.
I'm not familiar with OAuth. Do you have any suggestions on what I should try?
1) Request Token (server)
// omitting includes packages
const tokenRequestConfig = {
url: 'https://www.zotero.org/oauth/request',
method: 'POST',
data: {
oauth_callback: baseURL, // redirect to baseURL
},
},
initZoteroIntegration = async () => {
let oAuthHash;
const oauth = OAuth({
consumer: {
key: process.env.ZOTERO_APP_CLIENT_KEY,
secret: process.env.ZOTERO_APP_CLIENT_SECRET,
},
signature_method: 'HMAC-SHA1',
hash_function(base_string, key) {
oAuthHash = crypto.createHmac('sha1', key).update(base_string).digest('base64');
return oAuthHash;
},
}),
tokenRequestResponse = await fetch('https://www.zotero.org/oauth/request', {
headers: oauth.toHeader(oauth.authorize(tokenRequestConfig)),
method: 'post',
}),
tokenRequestData = await tokenRequestResponse.text(),
obj = {};
tokenRequestData.replace(/([^=&]+)=([^&]*)/g, (m, key, value) => {
obj[decodeURIComponent(key)] = decodeURIComponent(value);
});
const oAuthToken = obj.oauth_token,
oAuthTokenSecret = obj.oauth_token_secret,
url = `https://www.zotero.org/oauth/authorize?oauth_token=${oAuthToken}&library_access=1&notes_access=1&write_access=1&all_groups=write`;
/* the url should be returned to the client */
return ({
url,
oAuthToken,
oAuthTokenSecret,
oAuthHash,
});
};
2) Client is directed to Zotero app to log in in and authorize permissions.
3) Token Exchange (server)
// omitting includes packages
const confirmIntegration = async ({oAuthToken, oAuthTokenSecret, oAuthVerifier, oAuthHash}) => {
const oauth = OAuth({
consumer: {
key: process.env.ZOTERO_APP_CLIENT_KEY,
secret: process.env.ZOTERO_APP_CLIENT_SECRET,
},
signature_method: 'HMAC-SHA1',
hash_function() {
return oAuthHash;
},
}),
tokenExchangeConfig = {
url: `https://www.zotero.org/oauth/access?oauth_token=${oAuthToken}`,
method: 'POST',
data: {
oauth_verifier: oAuthVerifier,
oauth_callback: baseURL,
},
},
tokenExchangeResponse = await fetch(`https://www.zotero.org/oauth/access?oauth_token=${oAuthToken}`, {
headers: oauth.toHeader(oauth.authorize(tokenExchangeConfig, {
public: oAuthToken,
secret: oAuthTokenSecret,
})),
method: 'post',
}),
tokenExchangeData = await tokenExchangeResponse.text();
try {
const username = tokenExchangeData.match(/username=(\w+)/)[1],
userID = tokenExchangeData.match(/userID=([0-9]+)/)[1],
userAPIKey = tokenExchangeData.match(/oauth_token_secret=([a-zA-Z0-9]+)/)[1];
return {
username,
userID,
userAPIKey,
};
} catch (e) {
// TODO throw some error
return null;
}
};
On the 'Token Exchange' step I'm getting a response with a 401 status code ("Unauthorized").
tokenExchangeResponse.text() is returning oauth_problem=signature_invalid
Here's the raw output: 
"https_sig_error=1&z_debug_sbs=POST&https%3A%2F%2Fwww.zotero.org%2Foauth%2Faccess&oauth_consumer_key%3D9a016199db19772cb220%26oauth_nonce%3DDs3iXBVWF4izl1qfX0mk0JXIvZkl7N5o%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1545238813%26oauth_token%3D6f6ade01f30625feeb36%26oauth_verifier%3D02469ed77305b02befd8%26oauth_version%3D1.0&oauth_problem=signature_invalid&debug_sbs=POST&https%3A%2F%2Fwww.zotero.org%2Foauth%2Faccess&oauth_consumer_key%3D9a016199db19772cb220%26oauth_nonce%3DDs3iXBVWF4izl1qfX0mk0JXIvZkl7N5o%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1545238813%26oauth_token%3D6f6ade01f30625feeb36%26oauth_verifier%3D02469ed77305b02befd8%26oauth_version%3D1.0"
*Fix
Ok, I found a solution. I'm using node-cache to cache the OAuth object for a limited time.
Thank you #tnajdek for clarifying that the above usage of OAuth was the issue.
Note that I'm temporarily persisting the hash produced in step 1's
hash_function which is used in step 3.
This will not work.
The hash_function argument to OAuth constructor takes a base_string (which is a serialized summary of your request) and a key to produce its result. Both the base_string and the key will differ between steps 1 and 3, however your hash_function returns a cached results from step 1 as a result for completely different arguments base_string and key in step 3.
I'm not sure how the rest of your app looks like but I'd create OAuth instance just once and re-use it in the initial request and within the callback request inside your server request handling routines. If that's not an option, you can recreate OAuth using the same arguments and it should work fine.

Resources