Python3 PyTest Flask JWT throws HTTP 422 Unprocessable Entity - python-3.x

I am using Python 3.9 and the Flask-JWT-Extended PyPi package in my application. I am writing some test cases and when I POST to the endpoint I am testing with a proper-looking Bearer token, I get an HTTP 422 'Unprocessable Entity'. Google is not turning up an answer. How can I fix this?
# Do the Bearer login
data = {
'username': app.username,
'password': app.password,
}
tokenResponse = client.post("/login", json=data)
assert tokenResponse.content_type == 'application/json'
assert tokenResponse.json['access_token']
And shortly after, in the same test method, I try to POST to the actual endpoint:
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print({"Authorization": f"JWT {tokenResponse.json['access_token']}"})
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
response = client.post(endpoint, buffered=False,
content_type='multipart/form-data',
data=data,headers={"Authorization": f"JWT {tokenResponse.json['access_token']}"})
Here is the token printout:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{'Authorization': 'JWT eyJ0eXAiOiJKV1QilCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTI0Nzk5NzAsIm5iZiI6MTYxMjQ3OTk3MCwianRpIjoiYTQyZjU1NmUtYjQ2MS00NTNiLThkM2ItYjk1MmIzYzAwZjc0IiwiZXhwIjoxNjeyNDgwMDMwLCJpZGVudGl0eSI6IlNlbnNvbml4QXBpVXNlciIsImZyZXNoIjpmYWxzZSwidHlwZSI6ImFjY2VzcyJ9.IYrgg2e9VxhLFH0_kwQbmoHKI1wKsKfm3cpK3XZmqyY'}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is the traceback.
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print({"Authorization": f"JWT {tokenResponse.json['access_token']}"})
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
response = client.post(endpoint, buffered=False,
content_type='multipart/form-data',
data=data,headers={"Authorization": f"JWT {tokenResponse.json['access_token']}"})
> assert response.status_code == 200
E assert 422 == 200
E + where 422 = <Response streamed [422 UNPROCESSABLE ENTITY]>.status_code
../tests/test_endpoints.py:153: AssertionError

First suggestion, if you look at the response.get_json() it should give you a helpful error message for why the 422 was thrown (invalid audience, jwt verification failed, etc). That might help point you in the right direction.
Here is an example of a working spec that creates and passes a JWT in via headers if it helps:
import pytest
from flask import Flask
from flask import jsonify
from flask_jwt_extended import JWTManager
from flask_jwt_extended import jwt_required
from flask_jwt_extended import create_access_token
#pytest.fixture(scope="function")
def app():
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "foobarbaz"
JWTManager(app)
#app.route("/login", methods=["POST"])
def login():
return jsonify(access_token=create_access_token("test_user"))
#app.route("/protected", methods=["GET"])
#jwt_required
def access_protected():
return jsonify(foo="bar")
return app
def test_default_headers(app):
test_client = app.test_client()
response = test_client.post("/login")
access_token = response.get_json()["access_token"]
access_headers = {"Authorization": "Bearer {}".format(access_token)}
response = test_client.get("/protected", headers=access_headers)
assert response.status_code == 200
assert response.get_json() == {"foo": "bar"}

Related

How to return 401 in flask despite authorization

I am new to flask and I want to return 401 error, I am not using authentication, I am simply using a parameter api_key to check whether the user is authenticated.
I've tried abort(401) and return false, but it returns error 400 'Bad Request', is this the default way it returns a 401. Or is there a different way to do this?
My Code:
import flask
from flask import jsonify, request, Response
from flask_cors import CORS, cross_origin
from GoogleNews import GoogleNews
googlenews = GoogleNews()
googlenews.set_lang('en')
googlenews.set_period('1d')
googlenews.set_encode('utf-8')
app=flask.Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'
#cross_origin()
#app.route('/', methods=["GET"])
def return_news():
API_KEY = request.args['api_key']
if API_KEY == "xyz":
googlenews.get_news('TECHNOLOGY')
return jsonify(googlenews.result(True)[0:4])
else:
return Response(response="Unauthorized", status=401)
Thankyou
instead, try flask.Response object.
from flask import Flask, Response
app = Flask(__name__)
#app.route("/abort")
def abort():
return Response(response="Unauthorized", status=401)
app.run()
without the actual code it is hard to tell, but I think it is not using the flask abort() but the python internal abort() function.
Check the code below:
import flask
app = flask.Flask("aborter")
#app.route("/abort")
def abort():
return flask.abort(401)
if __name__ == '__main__':
app.run()
Happy Coding

How can I add Authorization header to a request so that a flask route with #jwt_required can be access if access token is present on the headers

I'm trying to implement flask_jwt_extended to my flask app.
My Use Case is, I want to set Authorization headers to every request. So that when a #jwt_required decorator is decorated to a flask route it can be access if an access token is present on the Authorization headers.
I've tried some solutions like the one below:
I use #app.after_request to attach headers to every request but still it gives me this response
{
"msg": "Missing Authorization Header"
}
Here is my code:
#app.after_request
def add_headers(resp):
access_token = session.get("access_token", None)
if access_token is not None:
resp.headers["Authorization"] = f"Bearer {access_token}"
return resp
return resp
my login route:
#app.route('/', methods=["GET", "POST"])
def login():
if request.method == "POST":
form = request.form
_username = form["username"]
_password = form["password"]
for username in Users:
if username.get(_username):
if safe_str_cmp(username[_username]["password"],_password):
access_token = create_access_token(identity=username[_username]["user_id"], fresh = True)
session["access_token"] = access_token
res = make_response(redirect(url_for("home")))
res.headers["Authorization"] = f"Bearer {access_token}"
return res
else:
return "Incorrect password"
return f"Hello{_username} doesn't exist"
return render_template("login.html")
Here is my protected route:
#app.route('/home')
#jwt_required
def home():
res = Response(render_template("base.html"))
return res
I've also tried adding my headers in the route,But it still the headers I specify are not recognize and still give me the same response message. Here's how do it
#app.route('/home')
#jwt_required
def home():
access_token = session.get("access_token", None)
print(access_token)
if access_token is not None:
res = Response(render_template("base.html"), headers={"Authorization" : f"Bearer {access_token}"})
return res
res = Response(render_template("base.html"))
return res
Writing this for those who will come across this and are trying to implement an OAuth flow
Don't use decorators use middleware instead
I believe you should handle this in a middleware. In the middleware, you can set the authorization property of the 2nd parameter of the call function which contains the current wsgi app environment variables you have passed in your init function. Look at code below:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
cookie = Request(environ).cookies.get('access_token')
if cookie is not None:
environ['HTTP_AUTHORIZATION']='Bearer '+cookie
return self.app(environ, start_response)

How can I validate payloads from GitHub webhooks in Python 3?

I've realized auto-deploying of my Flask app on the server by Github webhooks, but I cannot adapt Ruby script specified in the manual to Python 3 to validate the POST-request. I've tried this:
from flask import Flask, request
from hmac import HMAC, compare_digest
from hashlib import sha1
app = Flask(__name__)
def verify_signature(req):
received_sign = req.headers.get('X-Hub-Signature').split('sha1=')[-1].strip()
secret = 'my_secret_string'.encode()
expected_sign = HMAC(key=secret, msg=req.data, digestmod=sha1).hexdigest()
return compare_digest(received_sign, expected_sign)
#app.route('/webhook', methods=['POST', 'GET'])
def webhook():
if request.method == 'POST':
if verify_signature(request):
do_smth()
return 'Successfully', 200
return 'Forbidden', 403
return 'Not allowed', 405
Also I tried other variants with sha1(...).hexdigest() and compare_digest() from secrets package, but received signature always differs.
What do I do wrong?
Use SHA-256 instead of SHA-1
Instead of SHA-1 GitHub recommends using the more secure SHA-256.
Changed your code respectively for convenience.
from flask import Flask, request
from hmac import HMAC, compare_digest
from hashlib import sha256
app = Flask(__name__)
def verify_signature(req):
received_sign = req.headers.get('X-Hub-Signature-256').split('sha256=')[-1].strip()
secret = 'my_secret_string'.encode()
expected_sign = HMAC(key=secret, msg=req.data, digestmod=sha256).hexdigest()
return compare_digest(received_sign, expected_sign)
#app.route('/webhook', methods=['POST', 'GET'])
def webhook():
if request.method == 'POST':
if verify_signature(request):
do_smth()
return 'Successfully', 200
return 'Forbidden', 403
return 'Not allowed', 405
I've updated the code. Now it works well.

Flask JWT Extended cookie name overide Flask Session Cookie Name

I am using the Flask JWT Extended extension for flask and have built a login application successfully using JWT. I have gone through the tutorial on JWT in Cookies on the JWT extended documentation site using CSRF protection and everything.
What I can't seem to figure out is when using the set_access_cookies() and set_refresh_cookies() methods the JWTs are not saved in a httponly cookie named using the JWT Extended default configuration setting.
app.config.setdefault('JWT_ACCESS_COOKIE_NAME', 'access_token_cookie')
app.config.setdefault('JWT_REFRESH_COOKIE_NAME', 'refresh_token_cookie')
Instead when I debug the return back from the auth call the cookies are saved in the base Flask default configuration instead.
'SESSION_COOKIE_NAME': 'session',
Shouldn't the set_access_cookies() and set_refresh_cookies() methods override the base Flask default configurations as long as make sure to register my app in the JWTManager()?
uscc_login_app = Flask(__name__)
jwt = JWTManager(uscc_login_app)
Or is there something else I missed in the base Flask JWT Extended documentation to ensure that its configuration defaults are used when appropriate?
Updated code via request.
The code is pretty spread out but here is my best shot to include what I think will help.
In init.py:
from flask import Flask, url_for
from flask_restful import Api
from flask_jwt_extended import JWTManager
from resources.auth import Authenticate
from resources.refresh import Refresh
from temp_app.temp import TempApp
from uscc_login.uscc_app_login import *
uscc_login_app = Flask(__name__)
uscc_login_app.config.from_object(os.environ.get('FLASK_ENV'))
jwt = JWTManager(uscc_login_app)
api = Api(uscc_login_app, prefix='/v1')
# Add resources via the add_resource method
api.add_resource(Authenticate, '/login')
api.add_resource(Refresh, '/refresh_token')
login_view = Login.as_view(name='uscc_login')
uscc_login_app.add_url_rule('/login', view_func=login_view, methods=['POST', 'GET'])
In my app.py:
from uscc_login import uscc_login_app
if __name__ == '__main__':
uscc_login_app.run(debug=uscc_login_app.config.get('DEBUG'), threaded=uscc_login_app.config.get('THREADED'),
port=uscc_login_app.config.get('PORT'), host=uscc_login_app.config.get('HOST'))
In my config.py since I am using the Flask config.from_objects
import os
import datetime
uscc_login_app_dir = os.path.abspath(os.path.dirname(__file__))
class BaseConfig:
SECRET_KEY = os.environ.get('USCC_SECRET_KEY') or 'you-will-never-guess'
JWT_SECRET_KEY = os.environ.get('USCC_JWT_KEY') or 'super-secret'
JWT_TOKEN_LOCATION = ['cookies']
JWT_COOKIE_CSRF_PROTECT = True
JWT_HEADER_TYPE = 'JWT'
PROPAGATE_EXCEPTIONS = True
THREADED = True
class DevelopmentConfig(BaseConfig):
DEBUG = True
PORT = 5000 if os.environ.get("PORT") is None else int(os.environ.get("PORT"))
HOST = os.environ.get('HOST') or 'localhost'
if os.environ.get('access_token_expiration') is not None:
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(seconds=int(os.environ.get('access_token_expiration')))
if os.environ.get('refresh_token_expiration') is not None:
JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(seconds=int(os.environ.get('refresh_token_expiration')))
So then in my Flask MethodView that contains my login authorization POST I have the following:
auth.py
import sys
import os
from flask import jsonify, request
from flask_restful import Resource
from flask_jwt_extended import create_access_token, create_refresh_token, jwt_refresh_token_required, get_jwt_identity, \
set_access_cookies, set_refresh_cookies
from utilities import Common
class Authenticate(Resource):
#staticmethod
def post():
"""
:return:
"""
api_cred_path = os.environ.get('api_cred_path')
if api_cred_path is None:
response = jsonify({"msg": "Environment Variable 'api_cred_path' is not set."})
response.status_code = 500
return response
if not request.is_json:
response = jsonify({'msg': 'Missing JSON in request'})
response.status_code = 400
return response
params = request.get_json()
user_name = params.get('username')
user_password = params.get('password')
if not user_name:
response = jsonify({'msg': 'Missing username parameter'})
response.status_code = 400
return response
if not user_password:
response = jsonify({'msg': 'Missing password parameter'})
response.status_code = 400
return response
if Common.check_path_exists(api_cred_path):
with open(api_cred_path) as afh:
for line in afh:
file_userid, file_password = line.split('=')
if file_userid == user_name and file_password.strip('\n') == user_password:
access_token = create_access_token(identity=user_name)
refresh_token = create_refresh_token(identity=user_name)
response = jsonify({'login': True})
set_access_cookies(response, access_token)
set_refresh_cookies(response, refresh_token)
# # Identity can be any data that is json serializable
# art = {
# 'access_token': create_access_token(identity=user_name),
# 'refresh_token': create_refresh_token(identity=user_name)}
# response = jsonify(art)
response.status_code = 200
return response
else:
response = jsonify({"msg": "api_cred_path invalid."})
response.status_code = 500
return response
response = jsonify({'msg': 'Bad username or password'})
response.status_code = 401
return response
Could you provide some code to duplicate what you are seeing? When I try running the example token in jwt code (https://github.com/vimalloc/flask-jwt-extended/blob/master/examples/jwt_in_cookie.py) I see the expected cookie values when I login:
$ http :5000/token/auth username=test password=test
...
Set-Cookie: access_token_cookie=<jwt>; HttpOnly; Path=/api/
Set-Cookie: refresh_token_cookie=<jwt>; HttpOnly; Path=/token/refresh
...
So I realized my mistake in this. I was trying to get the access_token_cookie variable to be set from my auth.py which serves as my RESTFUL based microservice of which my login app calls to do the authorization. Realized it won't be available after redirecting back to the caller from the login apps POST method since the cookie was related to the login app UI frontend. So I just base the access and refresh tokens back from the auth.py POST method to the login POST method and let it set the cookies so they are available to the end client.
This was more of design problem than a code problem.

How to capture API failure while using oauthlib.oauth2 fetch_token

The Python3 fetch_token method in this library does not check the response status before consuming the response. If the API call it makes fails, then the response will be invalid and the script crashes. Is there something I can set so that an exception will be raised on a non-success response before the library can read the response?
import requests
from requests.auth import HTTPBasicAuth
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
from oauthlib.oauth2 import OAuth2Error
AUTH_TOKEN_URL = "https://httpstat.us/500" # For testing
AUTH = HTTPBasicAuth("anID", "aSecret")
CLIENT = BackendApplicationClient(client_id="anID")
SCOPES = "retailer.orders.write"
MAX_API_RETRIES = 4
class MyApp:
def __init__(self):
"""Initialize ... and obtain initial auth token for request"""
self.client = OAuth2Session(client=CLIENT)
self.client.headers.update(
{
"Content-Type": "application/json"
}
)
self.__authenticate()
def __authenticate(self):
"""Obtain auth token."""
server_errors = 0
# This needs more work. fetch_token is not raising errors but failing
# instead.
while True:
try:
self.token = self.client.fetch_token(
token_url=AUTH_TOKEN_URL, auth=AUTH, scope=SCOPES
)
break
except (OAuth2Error, requests.exceptions.RequestException) as e:
server_errors = MyApp.__process_retry(
server_errors, e, None, MAX_API_RETRIES
)
#staticmethod
def __process_retry(errors, exception, resp, max_retries):
# Log and process retries
# ...
return errors + 1
MyApp() # Try it out
You can add a "compliance hook" that will be passed the Response object from requests before the library attempts to parse it, like so:
def raise_on_error(response):
response.raise_for_status()
return response
self.client.register_compliance_hook('access_token_response', raise_on_error)
Depending on exactly when you may get errors, you might want to do this with 'refresh_token_response' and/or 'protected_request' as well. See the docstring for the register_compliance_hook method for more info.

Resources