I am trying to use the twitter api, but need to get authentication. There are 2 types , and I only need Application-only authentication aka app only. This is the type of authentication where an application makes API requests on its own behalf.
The docs explain to use this method, you need to use a bearer token. You can generate a bearer token by passing your consumer key and secret through the POST oauth2 / token endpoint.
Here is the link to docs explaining this endpoint. There is even an example request but still it isn't very clear to me what needs to be done.
I have an API key and API secret key, but am getting the following error:
body: ‘{“errors”:[{“code”:170,“message”:“Missing required parameter:
grant_type”,“label”:“forbidden_missing_parameter”}]}’ }
My server side code looks like this
var request = require('request');
var btoa = require('btoa');
const KEY = encodeURIComponent('1234');
const SECRET = encodeURIComponent('5678');
request({
headers: {
'Authorization': 'Basic ' + btoa(`${KEY}:${SECRET}`),
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
},
uri: 'https://api.twitter.com/oauth2/token',
method: 'POST',
body: JSON.stringify({
'grant_type': 'client_credentials' // I am passing the grant_type here
})
}, function (err, res, body) {
console.log('res', res)
});
The CURL request in the docs looks like the following:
POST /oauth2/token HTTP/1.1
Host: api.twitter.com
User-Agent: My Twitter App v1.0.23
Authorization: Basic eHZ6MWV2R ... o4OERSZHlPZw==
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 29
Accept-Encoding: gzip
grant_type=client_credentials
To to this there were a couple of things. First the request needed to be made server side. You need to install btoa from npm to provide the encoding of the key and secret key. The KEY and SECRET need to be separated by a colon. The body of the request needs to be a string of
'grant_type=client_credentials'
See full code example below.
const btoa = require('btoa');
request({
headers: {
'Authorization': 'Basic ' + btoa(`${KEY}:${SECRET}`),
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
},
uri: 'https://api.twitter.com/oauth2/token',
method: 'POST',
body: 'grant_type=client_credentials'
}, (error, response, body) => {
const token = JSON.parse(body).access_token;
});
For Swift 5
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
let info = Bundle.main.infoDictionary
let twitterConsumerKey : String = (info?["TwitterConsumerKey"] as? String)!
let twitterConsumerSecret : String = (info?["TwitterConsumerSecret"] as? String)!
let loginString = String(format: "%#:%#", twitterConsumerKey, twitterConsumerSecret)
let loginData = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString()
let urlString = NSString(format: "https://api.twitter.com/oauth2/token");
print("url string is \(urlString)")
let request : NSMutableURLRequest = NSMutableURLRequest()
request.url = NSURL(string: NSString(format: "%#", urlString)as String) as URL?
request.httpMethod = "POST"
request.timeoutInterval = 30
request.httpBody = "grant_type=client_credentials".data(using: String.Encoding.utf8)!
request.addValue("application/x-www-form-urlencoded;charset=UTF-8", forHTTPHeaderField: "Content-Type")
request.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
let dataTask = session.dataTask(with: request as URLRequest) {data, response, error -> Void in
guard let httpResponse = response as? HTTPURLResponse,
let receivedData = data else {
print("error: not a valid http response")
return
}
switch (httpResponse.statusCode)
{
case 200:
let response = NSString (data: receivedData, encoding: String.Encoding.utf8.rawValue)
if response == "SUCCESS"
{
}
default:
print("save profile POST request got response \(httpResponse.statusCode)")
let str = String(decoding:data!, as: UTF8.self)
print(str)
}
}
dataTask.resume()
The problem is with the format of http body of request. I was wrongly using dictionary of grant_type : client_credentials instead of string grant_type= client_credentials.
request.httpBody = try! JSONSerialization.data(withJSONObject:
["grant_type" : "client_credentials"], options: [])
Related
I have a Flask-based site and Flask-RestX-based Endpoint API. API is protected by Flask-JWT-Extended. API will be on 'api.' subdomain. API should receive the image in base-64 encoding. For API requests to be successful, an access token for jwt is required. As API requests will be CORS-mode, some CORS headers are added to the response:
{
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin" : "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
}
Suppose we have an invalid access token. I have added two handlers: one for restx and another one for jwt:
class Error(Exception):
"""Base class for other exceptions"""
def __init__(self, msg:str):
args = (msg,)
super().__init__(args)
class UnAuthorizedError(Error):
pass
#api_.errorhandler(UnAuthorizedError)
def unautherr_h(e):
return {
'status':"FAILED",
'error':{
'status_code': 401,
'comment': e.args[0][0]
}
}, 401,{
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
}
#jwt.invalid_token_loader
def invalid_token_loader(s):
raise UnAuthorizedError('JWT Token is invalid. Please, check your token and try again.')
The Client-side is written on JS.
For client-side requests, I tried to use ajax, fetch, and Axios. Problem is that when I try to send base64 encoded data(~3 MB) using those libraries and if the server will return an error code with JSON data as a response, they will be classified just as Network Error instead of factual error:
apiRequest = async function(frames)
{
return new Promise(async(resolve, reject)=>{
len = frames.length;
frms = [];
j = 0;
const response = await
fetch('http://api.xxx.xx/v1/access_token',{method:'GET'});
const json = await response.json();
acc_tok = json.result.token;
for(let i = 0; i < len; i++)
{
fm = new FormData();
fm.append('image', frames[i]);
const req = axios({
method: "post",
url: "api.xxx.xx/v1/compute",
data: fm,
headers: { "Content-Type": "multipart/form-data", 'Authorization': 'Bearer ' + acc_tok},
});
}
resolve(frms);
});
}
Some explanation to code: frames are images in base64, acc_tok is access_token that is taken from /v1/access_token. axios do POST request to /v1/compute
But when I send relatively small data and if the server does the same thing, a factual error gets caught. What can cause this problem?
I've successfully been able to query the Twitter Ads API with GET requests, but I'm trying to POST (to generate reports) and those keep failing with an UNAUTHROIZED_ACCESS "This request is not properly authenticated" error from Twitter.
I have been able to successfully run the query from Postman, so my credentials are OK. I have tried a couple of oAuth libraries (the oauth and oauth-1.0a libraries from npm) and I believe the root cause may be in the way they're generating the oAuth signature in the header.
Here's some test code which shows the problem:
let key = TWITTER_CONSUMER_API_KEY;
let secret = TWITTER_CONSUMER_API_SECRET_KEY;
let urlPost = 'https://ads-api.twitter.com/11/stats/jobs/accounts/<<accountid>>?entity=CAMPAIGN&entity_ids=i8be1&granularity=DAY&metric_groups=BILLING,ENGAGEMENT,MEDIA,WEB_CONVERSION&start_time=2022-06-12T00:00:00+10:00&end_time=2022-07-27T00:00:00+10:00&placement=ALL_ON_TWITTER';
let urlGet = 'https://ads-api.twitter.com/11/stats/jobs/accounts/<<accountid>>';
let oauth_token = <<OAUTH TOKEN>>;
let oauth_token_secret = <<OAUTH TOKEN SECRET>>;
// METHOD 1
const OAuth = require('oauth-1.0a')
const oauthConsumer = OAuth({
consumer: {
key: key,
secret: secret
},
signature_method: 'HMAC-SHA1',
hash_function(base_string, key) {
return crypto
.createHmac('sha1', key)
.update(base_string)
.digest('base64')
}
});
const authHeaderPost = oauthConsumer.toHeader(oauthConsumer.authorize({
url: urlPost,
method: 'POST',
}, {key: oauth_token, secret: oauth_token_secret}));
const authHeaderGet = oauthConsumer.toHeader(oauthConsumer.authorize({
url: urlGet,
method: 'GET',
}, {key: oauth_token, secret: oauth_token_secret}));
let initialResponse = await fetch(urlGet, {
method: 'GET',
headers: new Headers({
Authorization: authHeaderGet["Authorization"]
})
});
console.log(`[TEST] Method 1 GET Received Successful?`, initialResponse.status >= 200 && initialResponse.status <= 299);
initialResponse = await fetch(urlPost, {
method: 'POST',
headers: new Headers({
Authorization: authHeaderPost["Authorization"]
})
});
console.log(`[TEST] Method 1 POST Received Successful?`, initialResponse.status >= 200 && initialResponse.status <= 299);
// METHOD 2
import oauth from 'oauth'; // This is the npm library "oauth"
let extra_params = {};
const oAuthConsumer2 = new oauth.OAuth(
'https://api.twitter.com/oauth/request_token', 'https://api.twitter.com/oauth/access_token',
key,
secret,
'1.0', ``, 'HMAC-SHA1'
);
let orderedParametersPost= oAuthConsumer2._prepareParameters(oauth_token, oauth_token_secret, "POST", urlPost, extra_params);
let headerPost = oAuthConsumer2._buildAuthorizationHeaders(orderedParametersPost);
let orderedParametersGet= oAuthConsumer2._prepareParameters(oauth_token, oauth_token_secret, "GET", urlGet, extra_params);
let headerGet = oAuthConsumer2._buildAuthorizationHeaders(orderedParametersGet);
initialResponse = await fetch(urlGet, {
method: 'GET',
headers: new Headers({
Authorization: headerGet
})
});
console.log(`[TEST] Method 2 GET Received Successful?`, initialResponse.status >= 200 && initialResponse.status <= 299);
initialResponse = await fetch(urlPost, {
method: 'POST',
headers: new Headers({
Authorization: headerPost
})
});
console.log(`[TEST] Method 2 POST Received Successful?`, initialResponse.status >= 200 && initialResponse.status <= 299);
console.log(`[TEST ************************************************************************************************************************************************************]`);
}
The results are that both GET requests work successfully, but both POST requests fail.
Is there something I'm doing incorrectly?
I believe this appears to be a problem of user identity verification.
You are manipulating identities to deceive your system to produce the desired outcome on your system but the interface verifies identity against its own databases.
This is similar to a nonjudicial hearing officer trying to enter a judgment that requires verification by individual user profile of a judicial hearing officer.
Your workaround does not circumvent the verification standards for the POST function. You may be able to pull data from databases because they may have lower or even no encryption requirements for that function, but you cannot post if your ID is not compliant with the requirements of that function. This prevents unauthorized users from posting to verified accounts.
https://www.okta.com/identity-101/what-is-federated-identity/
Using Spotify Documentation for Client Credential Flow here:
I was able to create a API request in google app script (Javascript).
function callAPI () {
SPOTIFY_CLIENT_SECRET = secret
SPOTIFY_CLIENT_ID = id
const HEADERS = {
"Content-Type": "application/json",
'Authorization': `Basic ${Utilities.base64Encode(SPOTIFY_CLIENT_ID + ':' + SPOTIFY_CLIENT_SECRET)})`
}
const BODY = {
'grant_type': 'client_credentials'
}
var url = `https://api.spotify.com/api/token`
var requestOptions = {
'method': 'POST',
'headers': HEADERS,
'payload': JSON.stringify(BODY),
'muteHttpExceptions': true,
'redirect': 'follow'
};
var response = UrlFetchApp.fetch(url, requestOptions);
var data = JSON.parse(response.getContentText());
I am confused about two things, please be able to answer both.
1). The Spotify documentation states to enter "Basic" before the client credentials in the authorization header.
Yet, when I run this code, I get this error
{ error:
{ status: 400,
message: 'Only valid bearer authentication supported' } }
If, I'm am using client credential flow, why does it think I am using a bearer token? (Also if I change authentication to Bearer I get a 401 error "Invalid access token")
2). Could you provide an example of a working version of this code and why it was able to run opposed to mine?
I believe your goal is as follows.
You want to convert the following curl command to Google Apps Script.
curl -X "POST" -H "Authorization: Basic ZjM4ZjAw...WY0MzE=" -d grant_type=client_credentials https://accounts.spotify.com/api/token
In this case, grant_type=client_credentials is sent as the form data. When I saw your script, it is sent as the data. And you use the URL of https://api.spotify.com/api/token. But the curl command uses https://accounts.spotify.com/api/token. `I thought that these might be the reason for your issue. So when your script is modified, it becomes as follows.
Modified script:
function callAPI() {
SPOTIFY_CLIENT_SECRET = secret; // Please set your value.
SPOTIFY_CLIENT_ID = id; // Please set your value.
const HEADERS = {
'Authorization': `Basic ${Utilities.base64Encode(SPOTIFY_CLIENT_ID + ':' + SPOTIFY_CLIENT_SECRET)}` // Modified
}
const BODY = {
'grant_type': 'client_credentials'
}
var url = "https://accounts.spotify.com/api/token";
var requestOptions = {
'method': 'POST',
'headers': HEADERS,
'payload': BODY,
'muteHttpExceptions': true,
};
var response = UrlFetchApp.fetch(url, requestOptions);
var data = response.getContentText();
console.log(data)
}
Note:
When I saw your script again, I noticed that Basic ${Utilities.base64Encode(SPOTIFY_CLIENT_ID + ':' + SPOTIFY_CLIENT_SECRET)}) is required to be modified. Because in this case, it's Basic ###). Please remove ).
References:
Client Credentials Flow
fetch(url, params)
I figured it out! For some reason you need to add the client id and client secret in the form data. The Spotify docs says to put them in the headers base64 encoded but that is not the case in this instance. (You don't even need to encode them)
Also you don't even need to include the content-type parameter like the doc says.
working code looks like this
function callAPI () {
let SPOTIFY_CLIENT_SECRET = secret
let SPOTIFY_CLIENT_ID = id
const BODY = {
'Content-Type': 'application/x-www-form-urlencoded',
'grant_type': 'client_credentials',
"client_id": SPOTIFY_CLIENT_ID,
"client_secret": SPOTIFY_CLIENT_SECRET,
}
var url = "https://accounts.spotify.com/api/token";
var requestOptions = {
'method': 'POST',
'payload': BODY,
'muteHttpExceptions': true,
};
var response = UrlFetchApp.fetch(url, requestOptions);
var data = response.getContentText();
console.log(data)
}
I found my answer from reading about a similar problem here
I'm working on making a SOAP request in Javascript. I've never done a SOAP request before, and the service provider only has sample code in Java.
Here's their Java sample code:
String applicationPath = "c:\\e-Notify\\";
String inputDirectory = "inputs\\";
String outputDirectory = "outputs\\";
String url = "https://example.com/ENotifyService.svc";
String xml = "";
String resp = "";
String action = "";
String inputFilePath = "";
String outputFilePath = "";
try {
//Encode the URL
URL urlx = new URL(url);
//Instance of connection object
HTTPRequestPoster poster = new HTTPRequestPoster();
//Character stream
Reader data = new StringReader("");
//Get the XML from the input file
inputFilePath = applicationPath + inputDirectory + "manage-consultant-list-input.xml";
xml = FileReader(inputFilePath);
data = new StringReader(xml);
//Set operation
action = "ManageConsultantListRequest";
//Send request to server and get the response.
poster = new HTTPRequestPoster();
resp = poster.postData(data, urlx, action); <==NOTE `ACTION` VARIABLE
//Write the response to the output file
outputFilePath = applicationPath + outputDirectory + "manage-consultant-list-output.xml";
FileWriter(outputFilePath, resp);
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}
Per sample code provided by the SOAP API owner, I need to send the following values:
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<ManageConsultantListRequest xmlns="http://com.example.services.ServiceModel/2012/eNotifyService">
<Credentials xmlns:a="http://com.example.services.ServiceModel/2012/Credentials" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Username>MyAPIUsername</a:Username>
<a:Password>MyAPIPassword#1</a:Password>
</Credentials>
<Consultants xmlns:a="http://com.example.services.ServiceModel/2012/eNotify" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Consultant>
<a:SubmissionActionCode>A</a:SubmissionActionCode>
<a:Jurisdiction>IL</a:Jurisdiction>
<a:LicenseNumber>00000001</a:LicenseNumber>
</a:Consultant>
</Consultants>
<TransactionID>12345</TransactionID>
</ManageConsultantListRequest>
</s:Body>
</s:Envelope>
I'm looking at the npm Soap package.
import soap from 'soap'; //https://www.npmjs.com/package/soap
let url = 'https://example.com/ENotifyService.svc';
let args = {name: 'value'};
soap.createClient(url, function(err, client) {
client.MyFunction(args, function(err, result) { <==WHERE TO PUT DATA FROM `ACTION` VARIABLE??
console.log(result);
});
});
I think I can probably get the XML data I need into JSON format using something like the technique described at https://davidwalsh.name/convert-xml-json.
I haven't yet figured out:
How to get the data contained in the Java action variable, into the npm soap package call. It doesn't seem to have a place for it. ???
Thanks very much in advance for any thoughts/advice/info!
UPDATE: If anyone would like to show how to do it using an alternative soap package, that would be an accepted answer as well!
I actually paid somebody on UpWork to help me figure this out! Neither of us could figure out how to get the npm soap package to work on this API. However, he provided this alternative approach, which did work:
var request = require("request");
var rawXML = ''; //your xml goes here
var options = { method: 'POST',
url: 'https://example.com/eNotifyService.svc',
qs: { asmx: '' },
headers:
{ 'cache-control': 'no-cache',
Connection: 'keep-alive',
'content-length': '1960',
'Cache-Control': 'no-cache',
Accept: '*/*',
'User-Agent': 'PostmanRuntime/7.15.0',
Host: 'example.com',
SOAPAction: '"requestedAction"',
'Accept-Encoding': 'gzip,deflate',
'Content-Type': 'text/xml;charset=UTF-8' },
body: rawXML };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
I'm trying to send data with http post following differents threads, but I can't do it.
I need to send this data, tested in postman.
Headers.
Content-Type: application/x-www-form-urlencoded
Authorization: Basic user:pass
Body.
grant_type: password
scope: profile
This is my code.
login() {
let url = URL_LOGIN;
let headers = new Headers(
{
'Content-Type': 'application/json',
'Authorization': 'Basic user:pass'
});
let body = {
'grant_type': 'password',
'scope': 'profile'
}
return this.http.post(url, body, { headers: headers })
.map((response: Response) => {
var result = response.json();
return result;
})
}
Thanks in advance!!
There are two things you need to modify:
Your headers passed into the http post method missed one step. It should contain the following:
let options = new RequestOptions({ headers: headers });
Ensure you import RequestOptions from #angular/http
Then pass options into your post method as follows:
return this.http.post(url, body, options)...
The http post method body can only be a string. Therefore, it should be as follows:
let body = 'grant_type=password' + '&scope=profile';