I am trying to upload an image to Google+ using Javascript/JQuery; I have an access token which I can use to authenticate POST requests successfully but I get the following response when I attempt to use G+ Media Insert (https://developers.google.com/+/domains/api/media/insert):
{
"error": {
"errors": [
{
"domain": "global",
"reason": "forbidden",
"message": "Forbidden"
}
],
"code": 403,
"message": "Forbidden"
}
}
I haven't configured Domain-wide delegation because I expect the user to sign in to generate the access token (is this perhaps the issue?). I have enabled the Google+ Domains API in the Developer console and the relevant scopes are in place but I can't figure out why I receive a 403 error. The following AJAX request is being used:
var postForm = new FormData();
postForm.append("source",[code which generated a blob]);
postForm.append("displayName", "TestUpload");
$.ajax({
url: "https://www.googleapis.com/upload/plusDomains/v1/people/me/media/cloud",
headers: {"Authorization": "Bearer " + acToken},
type:"POST",
uploadType: "multipart/related",
processData:false,
contentType:false,
cache:false,
data: postForm,
});
Any help would be appreciated, I can provide more information if needed.
PS: I am actually using a userID instead of me in the URL for the moment
First, make sure that the userID belongs to the user that you have authenticated (perhaps through this OAuth flow). Unlike methods such as people.get, this method requires authentication, and cannot be called solely with an ID, unless that ID is the currently authenticated user. This is why we recommend using the special value me to avoid confusion.
Second, you need to be certain that the user that is authenticated is a Google Apps user. For example, if the user is a GMail user, the request will get a 403, since the Google+ Domains API is not allowed for non-Domains accounts.
Related
I have a Django web app that is using the Django REST framework to generate various API endpoints.
I can ensure only logged in users can view/read these endpoints, but now I am at the stage of development where I want users to post to the API using tokens. I have successfully done this, however, I have hard-coded the users token into the post request in Javascript... This worked for testing but obviously is not a good final solution.
Is it possible to request the current users token somehow? Could I then include this token in the POST request head automatically?
Thanks for any help/feedback in advance!!
EDIT:
I think I am close, but I am getting a few errors in my chrome console, and still can't retrieve token.
Console Errors:
toggleScript.js:25 Uncaught DOMException: Failed to execute
'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.
at getToken (http://127.0.0.1:8000/static/defaults/toggleScript.js:25:7)
at manageDefaults
(http://127.0.0.1:8000/static/defaults/toggleScript.js:62:5)
at HTMLInputElement.onclick (http://127.0.0.1:8000/defaults/:1:1)
getToken # toggleScript.js:25
manageDefaults # toggleScript.js:62
onclick # (index):1
toggleScript.js:24 POST http://127.0.0.1:8000/api-token-auth/ 415
(Unsupported Media Type)
I have a button when pressed, will trigger the function to retrieve the token, and this is what is causing the error stack above.
toggleScript.js
function getToken(){
var xhr = new XMLHttpRequest();
var url = 'http://127.0.0.1:8000/api-token-auth/';
xhr.open("POST", url, true);
var data = JSON.stringify({"username": "myusername", "password": "mypassword"});
xhr.send(data);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var json = JSON.parse(xhr.responseText);
console.log(json.token);
}
};
}
Django Rest Framework provides an API endpoint for requesting a user's token, given a username and password. You can wire the view into your urls.py:
from rest_framework.authtoken import views
urlpatterns += [
url(r'^auth-token/', views.obtain_auth_token)
]
Then when you POST a valid username and password to that view it will return the token in a JSON response:
{ 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }
Your app can then store that and send it in subsequent requests.
An example of retrieving the token using JQuery (assuming the view was mapped to the path ^auth-token/ in your urls.py):
$.post('/auth-token/', { username: 'admin', password: 'whatever' }, function(data) {
// Token available as data.token
});
If you try and post to the auth-token view from within an already authenticated session, Django will likely reject the request with a CSRF token missing or incorrect response. You should either ensure that the session is not authenticated when you retrieve the token, or you could potentially include the X-CSRFToken header in the request. You'd need to extract the value from the csrftoken cookie. For example (using JQuery and the JQuery Cookie plugin):
$.ajax({
url: "/auth-token/",
type: "POST",
headers: {
"X-CSRFToken": $.cookie("csrftoken") # Extract the csrftoken from the cookie
},
data:{ username: "admin", password: "whatever" },
dataType:"json"
}).done(function(data) {
// Token available as data.token
});
More info on obtaining an auth token here
I have the following code for TESTING PURPOSES:
$.ajax({
url: 'https://fcm.googleapis.com/v1/projects/[PROJECT]/messages:send',
type: 'POST',
headers:{
"Authorization":"Bearer "+[Access Token from FireBase Auth]
},
contentType:"application/json",
data: {
"message":{
"token": [TOKEN from messaging.getToken],
"notification" : {
"body" : "This is an FCM notification message!",
"title" : "FCM Message",
}
}
},
success: function () { },
error: function () { },
});
This always results in the following response with a 401()...
{
"error": {
"code": 401,
"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"
}
}
What am I doing wrong?
In the docs we linked in comments: https://firebase.google.com/docs/cloud-messaging/js/first-message
Under Retrieve Registration Token, you see this code:
messaging.getToken().then(function(currentToken) {
if (currentToken) {
sendTokenToServer(currentToken);
updateUIForPushEnabled(currentToken);
} else {
// Show permission request.
console.log('No Instance ID token available. Request permission to generate one.');
// Show permission UI.
updateUIForPushPermissionRequired();
setTokenSentToServer(false);
}
}).catch(function(err) {
console.log('An error occurred while retrieving token. ', err);
showToken('Error retrieving Instance ID token. ', err);
setTokenSentToServer(false);
});
You'll notice the sendTokenToServer() function, that's not their function, that's supposed to be yours. You call their getToken() and in the promise you take the result and send it up, would look like this:
function sendTokenToServer(currentToken) {
$.post({
url: 'yourServer.com/some_token_receiving_endpoint',
type: 'post',
data: {token: currentToken}
});
}
Then on the server, you'd receive that, and store it, likely in a database, related to their profile information.
Then, either at that moment, or, at a later time, you can query your database for those you want to notify, grab that token, and in conjunction with your access token stored securely on your server, you can then send the notification from there.
Typically, NodeJS, PHP, Python, or Ruby. As events happen, or on a schedule, your server can send notifications like this:
<?php
// Get some http client service for your language
$client = new GuzzleHttp\Client();
// Get your user or users (with their tokens that you've stored)
$user = Db.someQueryReturningUser();
// Your message
$jsonData = '{
"message":{
"token": [TOKEN from messaging.getToken],
"notification" : {
"body" : "This is an FCM notification message!",
"title" : "FCM Message",
}
}
}';
// Send Mesage
$client->post('https://fcm.googleapis.com/v1/projects/[PROJECT]/messages:send',
[
'headers' => [
'Authorization' => 'Bearer ' . [Access Token from FireBase Auth]
],
'json' => $jsonData
]);
In a very broad sense, what you're doing wrong is trying to call the FCM APIs from a web browser client. FCM messages are intended to be sent from a backend server under your total control. The authorization token that you need to send is going to effectively have admin privileges to send messages to any and all of your users, and you don't want to give that up to clients, as it's massive security issue.
From the documentation:
Requests sent to FCM from your app server or trusted environment must
be authorized. The FCM HTTP v1 API uses a short-lived OAuth 2.0 access
token generated for a service account associated with your Firebase
project. The legacy protocols use long-lived API keys retrieved from
the Firebase console. In both cases, you must add the required
credential to each message request sent to FCM.
In other words, you're not supposed to give clients access to send messages with your privileged service account credentials. The rest of that page of documentation describes how to actually do the world of authorizing the send request.
I need to retrieve some data from Google Search Console (Webmaster Tools) using a service account.
So far I've been able to retrieve an access_token for the service account which I need to append to the url of the request. The problem is that I can't find a way to do so, this is the code i'm using:
function retrieveSearchesByQuery(token)
{
gapi.client.webmasters.searchanalytics.query(
{
'access_token': token,
'siteUrl': 'http://www.WEBSITE.com',
'fields': 'responseAggregationType,rows',
'resource': {
'startDate': formatDate(cSDate),
'endDate': formatDate(cEDate),
'dimensions': [
'date'
]
}
})
.then(function(response) {
console.log(response);
})
.then(null, function(err) {
console.log(err);
});
}
This is the url called by the function:
https://content.googleapis.com/webmasters/v3/sites/http%3A%2F%2Fwww.WEBSITE.com/searchAnalytics/query?fields=responseAggregationType%2Crows&alt=json"
Instead it should be something like this:
https://content.googleapis.com/webmasters/v3/sites/http%3A%2F%2Fwww.WEBSITE.com/searchAnalytics/query?fields=responseAggregationType%2Crows&alt=json&access_token=XXX"
The gapi.client.webmasters.searchanalytics.query doesn't recognize 'access_token' as a valid key thus it doesn't append it to the url and that's why I get a 401 Unauthorized as response.
If I use 'key' instead of 'access_token' the parameter gets appended to the url but 'key' is used for OAuth2 authentication so the service account token I pass is not valid.
Does anyone have a solution or a workaround for this?
If your application requests private data, the request must be authorized by an authenticated user who has access to that data. As specified in the documentation of the Search Console API, your application must use OAuth 2.0 to authorize requests. No other authorization protocols are supported.
If you application is correctly configured, when using the Google API, an authenticated request looks exactly like an unauthenticated request. As stated in the documentation, if the application has received an OAuth 2.0 token, the JavaScript client library includes it in the request automatically.
You're mentioning that you have retrieved an access_token, if correctly received, the API client will automatically send this token for you, you don't have to append it yourself.
A very basic workflow to authenticate and once authenticated, send a request would looks like the following code. The Search Console API can use the following scopes: https://www.googleapis.com/auth/webmasters and https://www.googleapis.com/auth/webmasters.readonly.
var clientId = 'YOUR CLIENT ID';
var apiKey = 'YOUR API KEY';
var scopes = 'https://www.googleapis.com/auth/webmasters';
function auth() {
// Set the API key.
gapi.client.setApiKey(apiKey);
// Start the auth process using our client ID & the required scopes.
gapi.auth2.init({
client_id: clientId,
scope: scopes
})
.then(function () {
// We're authenticated, let's go...
// Load the webmasters API, then query the API
gapi.client.load('webmasters', 'v3')
.then(retrieveSearchesByQuery);
});
}
// Load the API client and auth library
gapi.load('client:auth2', auth);
At this point, your retrieveSearchesByQuery function will need to be modified since it doesn't need to get a token by argument anymore in order to pass it in the query. The JavaScript client library should include it in the request automatically.
You can also use the API Explorer to check what parameters are supported for a specific query and check the associated request.
If you need to use an externally generated access token, which should be the case with a Service Account, you need to use the gapi.auth.setToken method to sets the OAuth 2.0 token object yourself for the application:
gapi.auth.setToken(token_Object);
If I try to call the GMail API, I get the following error in return:
{
"error": {
"errors": [{
"domain": "global",
"reason": "failedPrecondition",
"message": "Bad Request"
}],
"code": 400,
"message": "Bad Request"
}
}
First I generate a token
var sHead = JSON.stringify({
"alg": "RS256",
"typ": "JWT"
});
var iat = timeStampf();
var exp = iat + 3600;
var sPayload = JSON.stringify({
"iss": client_email,
"sub": client_email,
"scope": "https://mail.google.com/",
"aud": "https://www.googleapis.com/oauth2/v3/token",
"exp": exp,
"iat": iat
});
var sJWS = KJUR.jws.JWS.sign("RS256", sHead, sPayload, private_key);
var paramstoken = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + sJWS
getToken("POST", "/oauth2/v3/token", paramstoken, jsonData, replier);
/*rest petition
return 200 ok {
"access_token": "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
"token_type": "Bearer",
"expires_in": 3600
}
*/
Next I test the token
function testToken(accessToken, replier) {
var client = vertx.createHttpClient().host(urlbase).port(443).ssl(true).maxPoolSize(10);
var request = client.request("GET", "/gmail/v1/users/me/messages", function(resp) {
console.log('server returned status code: ' + resp.statusCode());
console.log('server returned status message: ' + resp.statusMessage());
resp.bodyHandler(function(body) {
replier(JSON.parse(body.toString()));
});
});
request.headers()
.set("Content-type", contentType)
.set("Authorization", "Bearer " + accessToken);
request.end();
client.close();
}
And I get an error 400 bad request in return. But if I use a different scope, for example Google+, I get a 200 ok.
I think the error is '"sub":client_email'. I tried add more GMails in the Google console, and with 3 mails auto create for the project, only the mail from Json return me failed precondition if try with others mails including auto created for project and my own GMail(the GMail owner of project) error is 401 unautorized_client.
Or do I need other "grant_type Use the following string, URL-encoded as necessary: urn:ietf:params:oauth:grant-type:jwt-bearer"?
de13#appspot.gserviceaccount.com
7686#developer.gserviceaccount.com///this is de mail downloaded in json from credentials
768-compute#developer.gserviceaccount.com
(this is server side js)
The problem seems with your OAauth credentials. On Google Developer Console -> Credentials, when you try to get new credentials, there are 3 options, 1) Web Application (This is the one you should be using) 2) Service Account (Which is I believe you are trying to use right now), 3) Installed Application (For iOS & Android apps)
Also while authenticating, mention that you will be trying to access the service content in 'offline' mode, which will give you a 'refresh Token' to make subsquent requests without user's involvment.
You are able to access Google+ because its public data as compared to Gmail where you are trying to access messages for a certain user (Private data). Service account credentials will not give you access to a user's private data and you must impersonate the user whose emails you are trying to access via authentication using Web Application credentials in your case.
Hope this helps....
"sub": client_email
seems to be incorrect.
You should use your gmail address in the sub field, as you are trying to impersonate this email address.
I am getting an error on the final stage of a Paypal payment, when I execute the it. As far as I can see, its correct, I have no errors, but this is what it returns:
{"body":"","headers":{"Content-Type":"application/json","Date":"Mon, 30 Jun 2014 18:10:56 GMT","Content-Length":"0","PROXY_SERVER_INFO":"host=slcsbjava1.slc.paypal.com;threadId=911","Paypal-Debug-Id":"b190b1adb3748","Server":"Apache-Coyote/1.1"},"status":401}
This is the code I am using:
execute = XHR.send('POST', 'https://api.sandbox.paypal.com/v1/payments/payment/' + ppid[0].paymentid + '/execute', {
"header": {
"Authorization": "Bearer " + auth,
"Content-Type" : "application/json"
},
"parameters": {
"payer_id": pid
}
});
console.log(execute);
I cannot see what the Paypal debug means, and I have looked it up, but mostly I see PHP issues rather than JS and cURL.
I saw on a post confusing REST API with Classic, but I have taken the instructions from the interactive guide by Paypal: https://devtools-paypal.com/guide/pay_paypal/curl?interactive=OFF&env=sandbox
Can anyone help me with this ?
UPDATE
Found out with debugging auth was empty, so I fixed it, and now getting a new error which is this:
Error: {"message":"com.mongodb.BasicDBList cannot be cast to java.util.Map","code":"0"} ( # 8 : 45 ) -> var innerResult = GlobalXHRInner.send(method, url, GlobalJSON.stri
This error changes depending on how I send the final headers and parameters:
var newauth = "Bearer " + ppid[0].auth;
var info = {"headers": [{"Authorization": "'+newauth+'", "Content-Type" : "application/json"}], "parameters": [{"payer_id": "'+pid+'"}]};
console.log(info);
execute = XHR.send('POST', 'https://api.sandbox.paypal.com/v1/payments/payment/' + ppid[0].paymentid + '/execute', info );
console.log(execute);
response.success(execute);
If I change :
var info = {"headers": [{"Authorization": "'+newauth+'", "Content-Type" : "application/json"}], "parameters": [{"payer_id": "'+pid+'"}]};
to this:
var info = '{"headers": [{"Authorization": "'+newauth+'", "Content-Type" : "application/json"}], "parameters": [{"payer_id": "'+pid+'"}]}';
By adding the quote, I get a invalid object error, which I have checked with a json validator and passed. Really confused by these errors as I followed the guide :(
This error message according to the PayPal documentation means this:
Authentication Errors
HTTP Status Code: 401
Authentication errors are often caused by issues related to access tokens:
Ensure the access token is included and correct.
Ensure the access token hasn’t expired.
PayPal Developer API Call Information
Here is additional information on Access tokens from PayPal Developer Access Token:
When you make the API calls, make request by adding the access token in the ‘Authorization’ header using the following syntax (as defined in the OAuth 2.0 protocol):
Authorization: {tokenType} {accessToken}
Example: Authorization: Bearer EEwJ6tF9x5...4599F
Access token validity and expiration
PayPal-issued access tokens can be used to access all the REST API endpoints. These tokens have a finite lifetime and you must write code to detect when an access token expires. You can do this either by keeping track of the ‘expires_in’ value returned in the response from the token request (the value is expressed in seconds), or handle the error response (401 Unauthorized) from the API endpoint when an expired token is detected.