Im setting up a Twilio Sandbox for WhatsApp
for when a message comes in I set a webhook to my application’s link.
But my application requires a bearer token.
How can I set up twilio to send our bearer token together with the request it makes to my URL?
thank you
i make all test without the bearer token and it works fine.
but to go live, we need this token autentication for security reasons.
The webhook just triggers a GET or POST request to the registered URL, as you rightfully said. To be able to add custom parameters, such as a bearer token, you need to add an intermediate step in between. This can be achieved, for example, with any Serverless function.
Naturally, using Twilio Serverless would be the easiest option to do this:
const axios = require('axios');
exports.handler = async (context, event, callback) => {
// Create a new voice response object
const twiml = new Twilio.twiml.VoiceResponse();
try {
// Open APIs From Space: http://open-notify.org
// Number of people in space
const response = await axios.request({
method: "POST",
url: `http://api.open-notify.org/astros.json`,
headers: {
"Authorization": `Bearer ${request.body.token}`,
"Content-Type": "application/json; charset=utf-8"
},
});
const { number, people } = response.data;
const names = people.map((astronaut) => astronaut.name).sort();
// Create a list formatter to join the names with commas and 'and'
// so that the played speech sounds more natural
const listFormatter = new Intl.ListFormat('en');
twiml.say(`There are ${number} people in space.`);
twiml.pause({ length: 1 });
twiml.say(`Their names are: ${listFormatter.format(names)}`);
// Return the final TwiML as the second argument to `callback`
// This will render the response as XML in reply to the webhook request
// and result in the message being played back to the user
return callback(null, twiml);
} catch (error) {
// In the event of an error, return a 500 error and the error message
console.error(error);
return callback(error);
}
};
Related
Using Node & Axios
What I Want to Do
In my server.js file, I want to call an api for a token that always changes, use axios (or other solution) to create a global token variable, and provide the global token to an app.get request header within an object, again, all within my server.js file.
What I'm Trying
I get the data using...
var data = '<tsRequest>\r\n\t<credentials name="name" password="pword">\r\n\t\t<site contentUrl="" />\r\n\t</credentials>\r\n</tsRequest>';
var config = {
method: 'post',
url: 'https://url.uni.edu/api/3.13/auth/signin',
headers: {
'Content-Type': 'text/plain'
},
data : data
};
I try to create the global token variable (this is where I'm struggling)...
const token= axios(config)
.then((response) => {
console.log(response.data.credentials.token);
}).catch((err) => {
console.log(err);
});
console.log(token)
I have a functioning app.get request where instead of manually providing the token, I want to use the const token...
app.get('/gql', async (req, res) => {
var someObject = {
'method': 'POST',
'url': 'https://diffurl.uni.edu/api/metadata/graphql',
'headers': {
'X-Some-Auth': token,
'Content-Type': 'application/json'
},
The Current Results
What I have for the var data = and the var config = and the axios(config) all work to return the token via console.log, but I have 2 issues using axios.
The Axios Issues
In my hopes of creating a global token variable, I only understand how to get a console.log result instead of returning a 'useful data object'.
In just about every piece of documentation or help found, the example uses console.log, which is not a practical example for learners to move beyond just returning the data in their console.
What do I need to provide in axios to create a global token object in addition to or instead of console.log?
I realize 1. is my current blocker, but if I run my app, I get the following:
Promise { <pending> }
Express server running on port 1234
abc123 [the console.logged token via axios]
I'm not sure what the Promise { <pending> } means, how do I fix that?
Beyond The Axios Issues
If the axios problem is fixed, am I passing the const token correctly into my app.get... var someObject... headers?
Thank you for any help you can provide.
This is what Axios interceptors are for.
You can create an Axios instance with an interceptor that waits for the token request to complete before adding the token to the outgoing request.
A response interceptor can be added to handle 401 statuses and trigger a token renewal.
const data = "<tsRequest>...</tsRequest>";
let renew = true;
let getTokenPromise;
const renewToken = () => {
if (renew) {
renew = false; // prevent multiple renewal requests
getTokenPromise = axios
.post("https://url.uni.edu/api/3.13/auth/signin", data, {
headers: {
"content-type": "text/plain", // are you sure it's not text/xml?
},
})
.then((res) => res.data.credentials.token);
}
return getTokenPromise;
};
const client = axios.create();
// Request interceptor to add token
client.interceptors.request.use(async (config) => ({
...config,
headers: {
"X-Some-Auth": await renewToken(),
...config.headers,
},
}));
// Response interceptor to handle expiry
client.interceptors.response.use(
(res) => res,
(error) => {
if (error.response?.status === 401) {
// Auth expired, renew and try again
renew = true;
return client(error.config);
}
return Promise.reject(error);
}
);
// if putting this in a module...
// export default client;
The first time you try to make a request, the token will be retrieved. After that, it will continue to use the last value until it expires.
if you want to create a to send the token with every request in axios you should create a custom axios instance or change the global axios default
you will find the way to do it here, about promise problem you need to resolve it using .then
this how i think you should do it
// first create axios instance
// you can set config defaults while creating by passing config object see the docs
const instance = axios.create();
// then get the token from API
axios(config).then(response=>{
instance.defaults.headers.common["header you want to set"]=response.data.credentials.token
});
// then use the instance to make any request you want that should have the token
I tried to use Walmart API v4.2 to publish some items. I used "Bulk Item Setup" API method to create some feed. I used some types of ways to did it:
Send binary file (in the request body, for-data) with header "multipart/form-data" (this way was described in the Walmart API docs)
Send stringified object in the request body with header 'Content-Type': 'application/json',
Walmart API correctly returns me feedId.
But all of these ways didn't work! After feed creating, I saw "Submitted" status at the Walmart Seller Center. But this status was changed after few minutes to "Error". At the error column I see "ERROR TYPE: Data Error" with a description "Malformed data. Please check data file to ensure it is formatted properly.java.lang.NullPointerException".
I use my backend NodeJs app to do it. I use Axios for making a request.
My code example:
async createFeed(wdpId, wdpSecret, accessToken, feedsData) {
try {
const string = JSON.stringify(feedsData);
const file = Buffer.from(string);
const formData = new FormData();
formData.append('file', file);
const baseToken = WalmartService.getBaseAuthToken(wdpId, wdpSecret);
const options = {
params: {
feedType: 'MP_WFS_ITEM',
},
headers: {
Authorization: baseToken,
'WM_SEC.ACCESS_TOKEN': accessToken,
'WM_QOS.CORRELATION_ID': uuidv4(),
'WM_SVC.NAME': 'Walmart Marketplace',
Accept: 'application/json',
'Content-Type': 'application/json',
...formData.getHeaders(),
},
};
return (
axios
.post(`${process.env.WALMART_API_BASEURL}/feeds`, formData, options)
.then((response) => {
return response.data;
})
.catch((error) => {
console.error(error.message);
throw new BadRequestException('Walmart error, ', error.message);
})
);
} catch (error) {
throw new BadRequestException('Can not create listing');
}
}
It is difficult to identify the exact issue based on the information you provided. Few things that you might want to check
If you are appending/attaching a file (as I see it in the code), use Content-Type header as "multipart/form-data. Also, make sure the file name has a .json extension if you are sending data as a json string. If you don't use this, it might default to xml and you will get the same error as what you see.
Try invoking the API using a rest client like Postman and verify if that call is successful.
If you do want to send the data as HTTP body (instead of a file), that should work too with Content-Type as application/json. This has not been documented on their developer portal, but it works.
I'm building a fullstack app with nuxt + express and I have finally managed to include an authentication between my frontend/backend with passport and jwt.
I want to make additional api requests to my own github repo for fetching the latest releases (so a user gets a information that an update exists). This requets failed with a "Bad credentials" messages. I think this happens because my jwt token is sent with it (I can see my token in the request header).
My question is, is it possible to prevent axios from sending my JWT token in only this call? First, to make my request work and second, I don't want the token to be sent in external requests.
Example:
const url = 'https://api.github.com/repos/xxx/releases/latest'
this.$axios.get(url)
.then((res) => {
this.latestRelease = res.data
}).catch((error) => {
console.error(error)
})
transformRequest
You can override the Authorization for a specific call by passing an options object to your get request and transforming your request headers:
const url = 'https://api.github.com/repos/xxx/releases/latest';
this.$axios.get(url, {
transformRequest: (data, headers) => {
delete headers.common['Authorization'];
return data;
}
})
.then((res) => {
this.latestRelease = res.data;
}).catch((error) => {
console.error(error);
})
As explained in their GitHub readme:
transformRequest allows changes to the request data before it is sent to the server.
This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'.
The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
FormData or Stream.
You may modify the headers object.
Creating a specific instance
You can create an instance of axios for different scenarios. This allows you to separate your axios calls that require an authorization header and those who don't. Each instance has its own 'global' options:
const axiosGitHub = axios.create({
baseURL: 'https://api.github.com/',
timeout: 1000,
headers: {}
});
// won't include the authorization header!
const data = await axiosGithub.get('repos/xxx/releases/latest');
You could use this answer to have several instances of axios: https://stackoverflow.com/a/67720641/8816585
Or you could also import a brand new axios and use it locally like this
<script>
import axios from 'axios'
export default {
methods: {
async callFakeApi() {
const result = await axios.get('https://jsonplaceholder.typicode.com/todos/1')
console.log('result', result)
},
}
}
</script>
Axios interceptors as mentionned by Thatkookooguy are another solution!
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"
}
I'm a beginner with Node and React, and web programming in general. I want to import user credentials from LinkedIn's API and for that I need to authenticate using OAuth2.
My approach so far is to make an API-call from the client side to the LinkedIn oauth API with the relevant parameters, including a redirect URI which leads to an API endpoint on my node server. When the user has been redirected and approved LinkedIn's authentication dialog box, they will be redirected to the node server with an access token.
My question is as follows: I now want to update the user in my database with their corresponding access token, but how do I know which user to update when I can't get any information about the client in my function that handles the last redirect and fetches the access token?
Here's my node function that handles the redirect from LinkedIn:
router.get('/redirect', (req, res) => {
// Handle cancel by user
if(req.query.error){
console.log(req.query.error_description)
return
}
// Extract variables
const code = req.query.code
const state = req.query.state
// Check that state matches
if (state !== testState) {
console.log("State doesnt match")
return
}
// Exchange Authorization Code for an Access Token
var options = {
method: 'POST',
url: 'https://www.linkedin.com/oauth/v2/accessToken',
form: {
client_id: 'theClientID',
client_secret: 'theClienSecret',
grant_type: 'authorization_code',
code: code,
redirect_uri: 'http://localhost:3000/api/linkedin/redirect'
},
headers:
{ 'cache-control': 'no-cache',
"content-type": "application/json",
'user-agent': 'node.js' },
json: true };
// make the actual request
request(options, (error, response, body) => {
if (error) {
res.status(500).json({
message: error
})
return
}
// Extract access token
const token = body.access_token;
// Here I want to save access token to DB with the corresponding
// user, but I don't know which user to update
})
// Redirect user to profile
res.writeHead(301, {
Location: 'http://localhost:3000/profile'
})
res.end()
})
I had a really hard time formulating this question but I hope that my message gets through.