Facebook webhook verification response structure - facebook

This is my first time posting the question so please feel to provide feedback to improve the question.
Facebook webhook mentions that the endpoint should be first verified before the webhook endpoint can receive any event notifications.
The docs for Verification Request does not provide a response structure for the API. It simply tells us to send back the hub.challenge parameter.
As I am using NodeJS, I am trying with the code below. However, it does not verify the webhook from facebook dashboard.
How should we send back the response to the verify the webhook?
app.get('/webhook', (req, res) => {
const challenge = req.query['hub.challenge'];
const verify_token = req.query['hub.verify_token'];
if (verify_token === process.env.FACEBOOK_VERIFICATION_TOKEN) {
return res.status(200).send({message: "Success", challenge: challenge});
}
return res.status(400).send({message: "Bad request!"});
})

The verification endpoint of Facebook requires the response Content-Type to be text/html. This is not mentioned on the docs;they should have provided a structure. You can set the header to use text/html explicitly.
However, when you are using express, you can directly return just the challenge value.
app.get('/webhook', (req, res) => {
const challenge = req.query['hub.challenge'];
const verify_token = req.query['hub.verify_token'];
if (verify_token === process.env.FACEBOOK_VERIFICATION_TOKEN) {
return res.status(200).send(challenge); // Just the challenge
}
return res.status(400).send({message: "Bad request!"});
})
If you are using fastify set:
res.header('Content-Type', 'text/html; charset=utf-8');
return res.send('' + challenge);

Related

How to tell auth0 im authenticated in req.rest file

So I want to make a post request to my nextJS backend and the route i am making the req to a protected route so in my Rest client file (req.rest) I need to tell auth0 im authenticated but i do not know how to do that.
req.rest
POST http://localhost:3000/api/video
Content-Type: application/json
Authorization: Bearer cookie
{
"title": "Video",
"description": "Video description"
}
api/video.js
import { withApiAuthRequired, getSession } from "#auth0/nextjs-auth0";
import Video from "../../database/models/Video";
export default withApiAuthRequired(async function handler(req, res) {
if (req.method === "POST") {
try {
const { user } = getSession(req, res);
const newVideo = new Video({
title: req.body.title,
description: req.body.description,
ownerId: user.sub,
});
await newVideo.save();
res.json(newVideo);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
});
I'm not sure I understand your question. Your API should determine if the user is authenticated by validating the bearer token value you are passing through the Authorization request header, you shouldn't need to pass additional data as separate parameters to authorize the API. If you do need additional data to determine if the user is authorized to consume the API, that should be included inside of the bearer token as a claim.
So I haven't really found a solution but I do have a workaround which is to just make new page on the frontend for requests and send the requests from there.

How to specify redirectUrl after logout for Ambassador OAuth2 Filter with Keycloak?

I'm using the Ambassador OAuth2 Filter to perform OAuth2 authorization against Keycloak.
For the logout I use the the RP-initiated logout as described in the Docs of Ambassador
The logout works fine. However I could not figure out how to provide the redirect url needed for Keycloak to redirect to the Login page after successfully logged out. As a result the user stays on the blank logout page of keycloak.
The RP-initiated logout looks as follows
const form = document.createElement('form');
form.method = 'post';
form.action = '/.ambassador/oauth2/logout?realm='+realm;
const xsrfInput = document.createElement('input');
xsrfInput.type = 'hidden';
xsrfInput.name = '_xsrf';
xsrfInput.value = getCookie("ambassador_xsrf."+realm);
form.appendChild(xsrfInput);
document.body.appendChild(form);
form.submit();
I expected that Ambassador provides a way to add the redirect url as a query param or something, but I couldn't find a solution.
Are there any suggestions or workarounds?
I found this in the Ambassador documentation that could be overlooked as I did several times:
Ambassador OAuth2 Settings
protectedOrigins: (You determine these, and must register them with your identity provider) Identifies hostnames that can appropriately set cookies for the application. Only the scheme (https://) and authority (example.com:1234) parts are used; the path part of the URL is ignored.
You will need to register each origin in protectedOrigins as an authorized callback endpoint with your identity provider. The URL will look like {{ORIGIN}}/.ambassador/oauth2/redirection-endpoint.
So it looks like ambassador hard codes the redirection-endpoint (redirect_uri) that you need add to your OAuth2 client in Keycloak.
I found a solution for that, is not the best solution but you will logout using a button.
async function logout() {
const data = new URLSearchParams("realm=keycloak-oauth2-filter.ambassador")
data.append('_xsrf', getCookie("ambassador_xsrf.keycloak-oauth2-filter.ambassador"));
fetch('/.ambassador/oauth2/logout', {
method: 'POST',
body: data
})
.then(function (response) {
if (response.ok) {
return response.text()
} else {
throw "err";
}
})
.then(function (text) {
console.log(text);
})
.catch(function (err) {
console.log(err);
});
}

How to manage contacts using Contacts API in our website correctly?

I was trying to integrate Google Contacts API to manage the contacts in my website.
I've done the following things:
I've created an application in google developer console and added http://localhost:4200 as URIs & Authorized redirect URIs.
Enabled 'Contacts API'.
I've added the following in my index.html (I've replaced {clientID} with my original client ID (of course):
<script>
function loadAuthClient() {
gapi.load('auth2', function() {
gapi.auth2.init({
client_id: '{clientID}'
}).then(() => {
console.log("success");
}).catch(err => {
console.log(err);
});
});
}
</script>
<script src="https://apis.google.com/js/platform.js?onload=loadAuthClient" async defer></script>
<meta name="google-signin-client_id" content="{clientID}">
Signed in successfully using:
gapi.auth2.getAuthInstance().signIn().then(() => {
console.log("Logged in")
}).catch(err => {
console.log(err);
});
Tried fetching the contacts using the following:
var user = gapi.auth2.getAuthInstance().currentUser.get();
var idToken = user.getAuthResponse().id_token;
var endpoint = `https://www.google.com/m8/feeds/contacts/`;
var xhr = new XMLHttpRequest();
xhr.open('GET', endpoint + '?access_token=' + encodeURIComponent(idToken));
xhr.setRequestHeader("Gdata-Version", "3.0");
xhr.onreadystatechange = function () {
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
window.alert(xhr.responseText);
}
};
xhr.send();
But I'm getting the error:
Access to XMLHttpRequest at 'https://www.google.com/m8/feeds/contacts/?access_token={I removed the access token}' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Can someone please guide me where I'm going wrong?
My original response was off the mark. The actual answer is much simpler.
In step 4, try changing your endpoint:
var endpoint = `https://www.google.com/m8/feeds/contacts/default/full`;
In my local tests, this resulted in the expected response.
Another suggestion is to add alt=json to your query, so that you get easy to parse JSON payload. Otherwise you'll get a nasty XML payload in the response.
Here's the updated step 4 with these changes:
var endpoint = `https://www.google.com/m8/feeds/contacts/default/full`;
var xhr = new XMLHttpRequest();
xhr.open('GET', endpoint + '?access_token=' + encodeURIComponent(idToken) + '&alt=json');
xhr.setRequestHeader("Gdata-Version", "3.0");
xhr.onreadystatechange = function () {
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
window.alert(xhr.responseText);
}
};
xhr.send();
Here's my original response, just in case it helps someone else.
I suspect that you'll need to add http://localhost:4200 to your list of "Authorized JavaScript origins" for the OAuth Client that you are using.
Edit your OAuth 2.0 Client ID and add the URI to the Javascript origins as below:
The other section on that page, Authorized Redirect URIs, only permits the OAuth flow to be redirected back to your web app. Often your web app server will actually consume the APIs so Google doesn't automatically permit CORS access to these APIs to keep things secure.

unable to verify webhook

I have a webhook which delivers Push event payloads to a Google Cloud Function. My nodejs code looks like this:
function validateRequest (req) {
return Promise.resolve()
.then(() => {
const digest = crypto
.createHmac('sha1', '12345')
.update(JSON.stringify(req.body))
.digest('hex');
if (req.headers['x-hub-signature'] !== `sha1=${digest}`) {
const error = new Error('Unauthorized');
error.statusCode = 403;
throw error;
} else {
console.log('Request validated.');
}
});
}
I have double and triple checked that the secret token (
12345') in the code matches the secret in the webhook. Yet, the sha computed by this code does not equal the sha sent by GitHub. This code was taken verbatim from https://cloud.google.com/community/tutorials/github-auto-assign-reviewers-cloud-functions. Has the hashing method GitHub uses changed?
Issue was that webhook needs to send content type application/json not application/x-www-form-urlencoded.

Disable redirect in fetch request using React native

I'm trying to crawl a web using React Native which has no API. It's written in PHP.
To log an user, a POST request must be sent. The response returns a cookie with a PHPSessid cookie which I must capture to use in subsequent requests.
I would like to capture the cookie value, buy the POST response is a 302 and the redirection is followed automatically, so I can't see the cookie. In node I was able to do it with redirect:manual, but it does not work in react native.
The cookie is sent automatically in subsequent requests, buy I'm trying to manage cookies by hand with react-native-cookie and I'd like to know if it's possible.
Do you know a way to stop the redirection?
I've been checking the code and what I did was the following:
Clear all cookies
Launch an empty login request
Capture the PHPSessID coookie
Launch a login request with that PHPSessID
After that, the subsequent fetch requests would have automatically a PHPSessID cookie with a valid logged in user, so we can use the site with simple fetchs
Here is some code, but the important thing is that you do a first empty login request, capture the PHPSessid and launch the real login request with that PHPSessid.
This would be the main function:
import Cookie from 'react-native-cookie';
// I think this is used only to clear the cookies
function login(user, pass){
// clear all cookies for all domains
// We need to start withouth authorization token
Cookie.clear();
const makeLoginRequest = (sessid) =>
makeLoginRequestForUserAndPass(user,pass,sessid);
return makeInitialRequest()
.then(getSessionIDFromResponse)
.then(makeLoginRequest)
.then(checkIfLoggedAndGetSessionID);
}
The initial request is a request to the login script. Note that I used GET because it worked with my site, perhaps an empty post would be necessary:
function makeInitialRequest() {
const INIT_PATH = '/index.php?r=site/login';
const INIT_URL = site + INIT_PATH;
const request = new Request(INIT_URL, options....);
return fetch(request);
}
We have the session ID in the response. I used a simple regex to extract it. Note that we are not logged in; PHP has created a session and that's what we have here:
function getSessionIDFromResponse(response) {
return getPHPSessIdFromCookie(response.headers.get('set-cookie'));
}
function getPHPSessIdFromCookie(header) {
const regex = /PHPSESSID=(\w*)/;
const match = regex.exec(header);
return match ? match[1] : '';
}
Now the login request. Note that I can't stop redirection here, but I't have to do it because we can have PHPSessid later. Redirection must be set to manual in POST request:
function makeLoginRequestForUserAndPass(user, pass, sessid) {
const request = buildLoginRequest(user, pass, sessid);
return fetch(request);
}
// This is where we build the real login request
function buildLoginRequest(user, pass, sessid) {
const LOGIN_PATH = '/index.php?r=site/login';
const LOGIN_URL = site + LOGIN_PATH;
const fields = [
{name: 'LoginForm[username]', value: user},
{name: 'LoginForm[password]', value: pass},
etc...
];
const data = translateFieldsToURLEncodedData(fields);
const headers = {
'Content-type': 'application/x-www-form-urlencoded',
Cookie: `PHPSESSID=${sessid}`, // HERE is where you put the data
};
const options = { method: 'POST',
headers: headers,
mode: 'cors',
cache: 'default',
agent: proxy,
body: data,
redirect: 'manual' // VERY IMPORTANT: if you don't do it, the cookie is lost
};
return new Request(LOGIN_URL, options);
}
// Simple utility function
function translateFieldsToURLEncodedData(fields){
let pairs = fields.map( (field) => {
return encodeURIComponent(field.name) + '=' + encodeURIComponent(field.value);
});
return pairs.join('&');
}
This is the last part. To see if I was logged in I checked if the response had text belonging to login error's page. I also got the PHPSessid (I think it changed after login, not sure, it was a year ago) but I don't know if I used it, I believe it was included automatically in subsequent requests. I think this part could be simplified an improved:
function checkIfLoggedAndGetSessionID(response) {
return (
checkIfLoggedOK(response)
.then(() => getSessionIDFromResponse(response))
);
}
function checkIfLoggedOK(response){
return getTextFromResponse(response)
.then(throwErrorIfNotLogedOk);
}
function getTextFromResponse(response) {
return response.text();
}
function throwErrorIfNotLogedOk(page) {
if(isErrorPage(page)) throw new Error("Login failed");
}
function isErrorPage(text) {
const ERROR_MESSAGE = 'Something that appears in login failed page of your site';
let n = text.search(ERROR_MESSAGE);
return n !== -1;
}
Hope this can be useful.