Firebase Cloud Messaging AJAX POST in JavaScript - javascript

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.

Related

Request current users API token Django REST Framework

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

How do you retrieve notification/data from a request?

I'm trying to send a basic notification using Firebase Cloud Messaging, but I cannot for the life of me figure out how to include any actual content in the notification.
I'm starting with essentially a stripped-down version of what can be found here. I have three files, a manifest.json, which looks like this:
{ "gcm_sender_id": "my sender id" }
an index.html, which looks like this:
<html>
<head><link rel="manifest" href="manifest.json"></head>
<body>
<div id="endpoint-show">There doesn't seem to be an endpoint right now</div>
<script src="https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js"></script>
<script>
var config = {apiKey: "my key", authDomain: "my domain", messagingSenderId: "my id"};
firebase.initializeApp(config);
const messaging = firebase.messaging();
messaging.requestPermission()
.then(function() { console.log('Notification permission granted.'); })
.catch(function(err) { console.log('Unable to get permission to notify. ', err); });
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true})
.then(function(subscription) {
document.getElementById("endpoint-show").innerHTML = "<p>" + subscription.endpoint.split('/\').slice(-1)[0] + "</p>";
})
});
navigator.serviceWorker.register('./service-worker.js')
</script>
</body>
<html>
and a service-worker.js, which looks like:
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
event.waitUntil(self.registration.showNotification("title", { body: "body" }));
});
This seems to be about the minimum amount of code required to register a subscription for push notifications and establish a service worker to handle them.
I then send notifications using a curl command like the one shown here. Ie, POST to https://fcm.googleapis.com/fcm/send with those certain headers set and a body that contains a "too" field which is equal to the one that my idex.html shows. This works, insofar that I get a notification on my computer with the title "title" and the body "body".
But here is where I am stuck: It seems like the only data I can send through this request is the fact that a notification happens, and not any other (actual) data. The first example I linked just hard-codes a notification, as does my own code, but I would like to be able to send a request with arbitrary data. The documentation here seems to indicate that I should be able to set either the data or notification field of the request, and get a notification that has that data, but that doesn't seem to work. I have set both fields in the request I'm making, so the whole thing looks something like this (which I am sending using postman):
POST /fcm/send HTTP/1.1
Host: fcm.googleapis.com
Content-Type: application/json
Authorization: key=my key
Cache-Control: no-cache
Postman-Token: some token
{
"notification": {
"body": "notification body",
"title": "notification title"
},
"data": {
"body": "data body",
"title": "data title"
},
"to" : "my endpoint"
}
But I can't figure out how to actually retrieve any of that data. The event that my service worker captures does not contain any of this data. None of the documentation I have looked at seems to describe how to actually get this data through the web, only on Android or iOS, but obviously it is possible since many website implement dynamic notifications.
I suspect that I have some fundamental misunderstanding of how all of this works, which would not be surprising since I have very little experience with any kind of web development. If what I'm trying to do is impossible, or much be done a completely different way, let me know.
And just to reiterate, here is exactly what I am trying to do:
Send a request (whether that is to a webserver that I write or to firebase).
Have a notifcation pop up on my Chrome with information from that request.
You seem to be mixing the new firebase messaging lib with the old style service worker code.
After you get the permission, you need to call the getToken API.
// service.js
// after request permission is successful
// Get Instance ID token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
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);
});
Its debatable, but you also need to create a service work with filename as firebase-messaging-sw.js. Reference can be found here.
And in this service worker you need to put something like this:
// firebase-messaging-sw.js
'use strict';
console.log('Starting service worker');
if( 'function' === typeof importScripts) {
importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js');
importScripts('core/decoder.js');
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': 'YOUR-SENDER-ID'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function(payload) {
var shinyData = payload || {};
console.log('[firebase-messaging-sw.js] Received background message ', payload, shinyData);
return self.registration.showNotification(shinyData.title, {
body: shinyData.body,
icon: shinyData.icon,
data: {url: shinyData.tag}
})
});
}
You may find this gist I created helpful.

Unable to query Google Search Console API using a Service Account

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

How to access GMail API from my own GMail?

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.

Google Plus Media-Insert - Google Domains API - 403 Forbidden

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.

Categories