I'm trying to verify that the Webhook received is coming from Shopify. They have this doc, but it doesn't work (getting type errors).
Here's what I have so far. It produces no errors, but the verify_webhook function always returns false.
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = '...'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
genHmac = base64.b64encode(digest)
return hmac.compare_digest(genHmac, hmac_header.encode('utf-8'))
#app.route('/', methods=['POST'])
def hello_world(request):
print('Received Webhook...')
data = request.get_data()
hmac_header = request.headers.get('X-Shopify-Hmac-SHA256')
verified = verify_webhook(data, hmac_header)
if not verified:
return 'Integrity of request compromised...', 401
print('Verified request...')
if __name__ == '__main__':
app.run()
What am I doing wrong?
Answer:
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = '...'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
genHmac = base64.b64encode(digest)
return hmac.compare_digest(genHmac, hmac_header.encode('utf-8'))
#app.route('/', methods=['POST'])
def hello_world(request):
print('Received Webhook...')
data = request.data # NOT request.get_data() !!!!!
hmac_header = request.headers.get('X-Shopify-Hmac-SHA256')
verified = verify_webhook(data, hmac_header)
if not verified:
return 'Integrity of request compromised...', 401
print('Verified request...')
if __name__ == '__main__':
app.run()
Issue was in the data = request.get_data() line.
Related
The bounty expires in 12 hours. Answers to this question are eligible for a +50 reputation bounty.
Gurkenkönig wants to draw more attention to this question.
I can't manage to validate the X-Hub-Signature-256 for my meta / whatsapp webhook in flask successfully.
Can anyone tell me where the error is or provide me with a working example?
import base64
import hashlib
import hmac
import os
from dotenv import load_dotenv
from flask import Flask, jsonify, request
from werkzeug.middleware.proxy_fix import ProxyFix
load_dotenv()
API_SECRET = os.environ.get('API_SECRET')
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1)
def verify_webhook(data, hmac_header):
hmac_recieved = str(hmac_header).removeprefix('sha256=')
digest = hmac.new(API_SECRET.encode('utf-8'), data,
digestmod=hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_recieved.encode('utf-8'))
#app.route("/whatsapp", methods=["GET", "POST"])
def whatsapp_webhook():
if request.method == "POST":
try:
data = request.get_data()
if not verify_webhook(data, request.headers.get('X-Hub-Signature-256')):
return "", 401
except Exception as e:
print(e)
return "", 500
return jsonify({"status": "success"}, 200)
This seems to work:
def verify_webhook(data, hmac_header):
hmac_recieved = str(hmac_header).removeprefix('sha256=')
digest = hmac.new(API_SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
return hmac.compare_digest(hmac_recieved, digest)
If you run this test:
import base64
import hashlib
import hmac
import os
API_SECRET = 'very_secret_key'
def verify_webhook(data, hmac_header):
hmac_recieved = str(hmac_header).removeprefix('sha256=')
digest = hmac.new(API_SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
return hmac.compare_digest(hmac_recieved, digest)
print(verify_webhook('Good morning', 'sha256=63e447ebe2bb46cb621972656087950c9d3a437caa61ece01f18094cc99f5a16'))
It returns True as expected:
$ python3 xhub_new.py
True
I am trying to verify the webhook received from Shopify but the Hmac verification fails.
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
#app.route('/productCreation', methods=['POST'])
def productCreation():
data = request.data
verified = verify_webhook(
data, request.headers.get('X-Shopify-Hmac-SHA256'))
if(verified):
return ("Success", 200)
return("Integrity error", 401)
Getting error as
hash = hmac.new(SECRET.encode('utf-8'), body.encode('utf-8'), hashlib.sha256)
AttributeError: 'bytes' object has no attribute 'encode'
Can anyone help with this? I am developing a Flask app for this.
You´re getting a attribute error, means your body object is not a string (encode() is working for string like objects) as mentioned in the error message it is a 'bytes' like object. Remove the
.encode('utf-8') from body.
Update March 2022
It seems like Shopify has updated their Flask example with the same changes I suggested below.
Original answer
I got the same error when using Shopify's Flask example
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = 'hush'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET, data.encode('utf-8'), hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
#app.route('/webhook', methods=['POST'])
def handle_webhook():
data = request.get_data()
verified = verify_webhook(data, request.headers.get('X-Shopify-Hmac-SHA256'))
if not verified:
abort(401)
# process webhook payload
# ...
return ('', 200)
To get it to work I had to modify verify_webhook by:
Encoding SECRET since hmac.new() expects the key in bytes and not as a string.
Not encoding data since Flask's response.get_data already returns an encoded bytestring.
The final code
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = 'hush'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
#app.route('/webhook', methods=['POST'])
def handle_webhook():
data = request.get_data()
verified = verify_webhook(data, request.headers.get('X-Shopify-Hmac-SHA256'))
if not verified:
abort(401)
# process webhook payload
# ...
return ('', 200)
I have the following script written in python
from flask import Flask, request, jsonify
from flask_cors import CORS, cross_origin
app = Flask(__name__)
CORS(app, resources=r'/chat', headers='Content-Type')
#app.route("/chat")
def chat():
print(request)
request.get_data()
data = json.loads(request.data)
response = chatbot.get_response(str(data['message']))
response_data = response.serialize()
response = jsonify({'data': response_data})
return response
app.run(host="0.0.0.0", port=8900, debug=True)
I am calling this API from a JavaScript frontend running on http://localhost:8080
I am using Google Chrome and get the following error
Access to XMLHttpRequest at 'http://localhost:8900/chat/' from origin 'http://localhost:8080' 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.
I also get the following the log message in the Python Console for each request
127.0.0.1 - - [19/Mar/2020 15:12:00] "?[33mOPTIONS /chat/ HTTP/1.1?[0m" 404 -
I am getting really frustrated because even if I change my code to
#app.route("/chat")
def chat():
print(request)
request.get_data()
data = json.loads(request.data)
response = chatbot.get_response(str(data['message']))
response_data = response.serialize()
response = jsonify({'data': response_data})
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'append,delete,entries,foreach,get,has,keys,set,values,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
return response
I still get the same error.
Can you try setting the headers like this instead?
from flask import Flask, request, jsonify
from flask_cors import CORS, cross_origin
app = Flask(__name__)
CORS(app, resources=r'/chat', headers='Content-Type')
#app.route("/chat")
def chat():
print(request)
request.get_data()
data = json.loads(request.data)
response = chatbot.get_response(str(data['message']))
response_data = response.serialize()
response = jsonify({'data': response_data})
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Headers'] = 'append,delete,entries,foreach,get,has,keys,set,values,Authorization'
response.headers['Access-Control-Allow-Methods'] = 'GET,PUT,POST,DELETE,OPTIONS'
return response
app.run(host="0.0.0.0", port=8900, debug=True)
This question already has answers here:
Read file data without saving it in Flask
(8 answers)
Opening a file that has been uploaded in Flask
(2 answers)
Closed 4 years ago.
With server.py running:
from flask import Flask, request, Response
app = Flask(__name__)
#app.route('/test', methods=['GET','POST'])
def route():
print('got files: %s' % request.files)
return Response()
if __name__ == '__main__':
app.run('0.0.0.0', 5000)
send a request using client.py:
import json, requests
dictionary_1 = {"file": {"url": "https://bootstrap.pypa.io/get-pip.py"}}
files = [('dictionary_1', ('get-pip.py', json.dumps(dictionary_1), 'application/json'))]
response = requests.post('http://127.0.0.1:5000/test', files=files)
Server logs that it received a request:
got files: ImmutableMultiDict([('dictionary_1', <FileStorage: u'get-pip.py' ('application/json')>)])
Apparently, the dictionary_1 was received as FileStorage object.
How to turn the received FileStorage into the Python dictionary?
edited later
The possible duplicate post does not clarify how to send and unpack the Python dictionary object sent via requests(files=list())
This is happening because you're posting files instead of data. This should work:
import flask
app = flask.Flask(__name__)
#app.route('/test', methods=['GET','POST'])
def route():
print('got data: {}'.format(flask.request.json))
return Response()
if __name__ == '__main__':
app.run('0.0.0.0', 5000)
and then send data to your app by
import requests
dictionary_1 = {"file": {"url": "https://bootstrap.pypa.io/get-pip.py"}}
response = requests.post('http://127.0.0.1:5000/test', json=dictionary_1)
In your example there's no need to post the file unless I'm misunderstanding something
Solution # 1:
from flask import Flask, request, Response
import StringIO, json
app = Flask(__name__)
#app.route('/test', methods=['GET','POST'])
def route():
print('got files: %s' % request.files)
for key, file_storage in request.files.items():
string_io = StringIO.StringIO()
file_storage.save(string_io)
data = json.loads(string_io.getvalue())
print('data: %s type: %s' % (data, type(data)) )
return Response()
if __name__ == '__main__':
app.run('0.0.0.0', 5000)
Solution # 2:
from flask import Flask, request, Response
import tempfile, json, os, time
app = Flask(__name__)
#app.route('/test', methods=['GET','POST'])
def route():
print('got files: %s' % request.files)
for key, _file in request.files.items():
tmp_filepath = os.path.join(tempfile.mktemp(), str(time.time()))
if not os.path.exists(os.path.dirname(tmp_filepath)):
os.makedirs(os.path.dirname(tmp_filepath))
_file.save(tmp_filepath)
with open(tmp_filepath) as f:
json_data = json.loads(f.read())
print type(json_data), json_data
return Response(json_data)
if __name__ == '__main__':
app.run('0.0.0.0', 5000)
I am trying to build a Microsoft Teams chat bot using Flask, following the instructions on how to build custom bots. However I am unable to verify the HMAC auth which I really want for security.
Based on guides and documentation I've found I am using the following minimial testing app trying to calculate a HMAC for the incoming request. (Bot name and description DevBot and the key/security_token below for testing).
#!/usr/bin/python
# coding=utf-8
from flask import Flask, request, jsonify
import hmac, hashlib, base64, json
app = Flask(__name__)
#app.route('/', methods=['GET', 'POST'])
def webhook():
if request.method == 'POST':
# Authenticate
security_token = b"O5XHU8OSzwx8w9YiM0URkR/Ij4TZZiZUwz7Swc+1hZE="
request_data = request.get_data()
digest = hmac.new(security_token, msg=request_data, digestmod=hashlib.sha256).digest()
signature = base64.b64encode(digest).decode()
# TODO: Verify signature = Authorization header HMAC here
return jsonify({
'type' : 'message',
'text' : "Auth header: {0} <br>Calculated HMAC: {1}".format(request.headers.get('Authorization'), signature),
})
elif request.method == 'GET':
return "Hello World"
if __name__ == '__main__':
app.run(debug=True)
Upon sending the message #DevBot test I get the following hashes back in the reply from the bot, but they aren't matching as expected:
Auth header: HMAC LuDmz97y/Z2KWLIZ1WZASz3HlOEtDCwk5/lL/fK8GqM=
Calculated HMAC: eaxTdJSLuU3Z4l94bxFiWvsBhjNG9SPxwq/UHeR7KcA=
Any ideas or pointers? I've been trying all sorts of stuff with encoding but I have a feeling that Flask might be doing something that modifies the request body or something?
edit 1: small clarification
edit 2: full Flask app example
edit 3: sample bot details, input and output examples
After lots of trial and error and trying to reproduce the C# code example from MS I managed to solve it myself. Here's the solution:
#!/usr/bin/python
# coding=utf-8
from flask import Flask, request, jsonify
import hmac, hashlib, base64, json
app = Flask(__name__)
#app.route('/', methods=['GET', 'POST'])
def webhook():
if request.method == 'POST':
# Reply
data = request.get_json()
channel = data['channelId']
message_type = data['type']
sender = data['from']['name']
message_format = data['textFormat']
message = data['text']
# Authenticate
security_token = b"O5XHU8OSzwx8w9YiM0URkR/Ij4TZZiZUwz7Swc+1hZE="
request_data = request.get_data()
digest = hmac.new(base64.b64decode(security_token), msg=request_data, digestmod=hashlib.sha256).digest()
signature = base64.b64encode(digest).decode()
# TODO: verify that HMAC header == signature
return jsonify({
'type' : 'message',
'text' : "auth header: {0} <br>hmac: {1}".format(request.headers.get('Authorization').split(' ')[1], signature),
})
elif request.method == 'GET':
return "Hello World"
if __name__ == '__main__':
app.run(debug=True)
Another option rather than interfacing directly with Microsoft Teams may be to use the Microsoft Bot Connector API.
https://docs.botframework.com/en-us/restapi/connector/
I have a bot working with Microsoft Teams using https://github.com/Grungnie/microsoftbotframework which is validating the JWT that is sent from Microsoft.