Flask routing request url based on request body? - python

How can I route request based on the request body in flask? I think I can use redirect but I prefer not to use that and to route request immediately based on the content of the body.
Here is what I tried but failed:
# I am using Flask-restful extension
from flask import Flask, request
from flask_restful import Resource, Api
class RouteA:
def post(self):
return {'route': 'RouteA'}
class RouteB:
def post(self):
return {'route': 'RouteB'}
app = Flask(__name__)
api = Api(app) # This is just flask-restful app wrapper extension
# I am trying to use before_request
#app.before_request
def routing():
request_body = request.get_json(force=True)
type = request_body.get('type', None)
if type and type == 'RouteB':
# somehow direct to routeB
request.path = '/jobs/routeb'
# I can do this too but I prefer direct routing rather than redirecting
# return redirect(location='jobs/routeb', code=307)
# routing here
api.add_resource(RouteA, '/jobs')
api.add_resource(RouteB, '/jobs/routeb')
if __name__ == '__main__':
app.run(debug=True)
How I will make request to that endpoint:
import requests
url = 'http://localhost:5000/jobs'
payload = {
'type': 'RouteB'
}
r = requests.post(url=url, json=payload)
response = r.json()
print(response)
# Expecting:
# {'route': 'RouteB'}

Related

How to use #token_required decorator for PyJWT in Flask App to access route after user gets logged in?

I've used PyJWT for authenticating the user now my main concern is how to use authentication decorator in API endpoints as I've addes SQL query to to fetch user detail using uid in my route but in token_required definition for current user do I've to add that query again?
Ex. After login I want to access API to display user profile.
#app.route('/users/<uid>', methods=['GET'])
**#token_required** ??
I've used SqlAlchemy core to execute and get data from database in my route.
In token_required definition can we add SqlAlchmey core query for current user & how to implement because I've already used that in my route /users/.
def token_required(f):
#wraps(f)
def decorator(*args, **kwargs):
token = None
if 'x-access-tokens' in request.headers:
token = request.headers['x-access-tokens']
if not token:
return jsonify({'message': 'a valid token is missing'})
try:
data = jwt.decode(token, app.config['SECRET_KEY'])
current_user = User.query.filter_by(uid=data['uid']).first()
except:
return jsonify({'message': 'token is invalid'})
return f(current_user, *args, **kwargs)
return decorator
#app.route('/users/<uid>', methods=['GET'])
def profile_view(uid):
print("user_details")
conn = engine.connect()
str_sql = text(**"""SELECT * FROM user WHERE uid = uid""",{"uid": uid}**)
results = conn.execute(str_sql).fetchall()
print(results)
return users_scehma.dump(results)
First of all import flask and JWTManager
from flask import Flask, jsonify
from flask_jwt_extended import (create_access_token,get_jwt_identity,jwt_required,JWTManager)
Define the Flask app and set a JWT_SECRET_KEY
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "supersecret" # Change this!
Initialize the jwt with the app
jwt = JWTManager(app)
Make a #app.route to define the method and path.
def login() is to get the Authentifikation key
#app.route("/token/login", methods=["GET"])
def login():
additional_claims = {"aud": "some_audience", "foo": "bar"}
access_token = create_access_token(identity="username", additional_claims=additional_claims) #you can add additional parameters
return jsonify(access_token=access_token),200#, encoded=False), 200
Our second route is protected to with #jwt_required()
#app.route("/protected", methods=["GET"])
#jwt_required()
def protected():
# Access the identity of the current user with get_jwt_identity
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
if __name__ == '__main__':
app.run()
the code looks like this at the end:
from flask import Flask, jsonify
from flask_jwt_extended import (create_access_token,get_jwt_identity,jwt_required,JWTManager)
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "supersecret" # Change this!
jwt = JWTManager(app)
#app.route("/token/login", methods=["Get"])
def login():
additional_claims = {"aud": "some_audience", "foo": "bar"}
access_token = create_access_token(identity="username", additional_claims=additional_claims) #you can add additional parameters
return jsonify(access_token=access_token),200#, encoded=False), 200
#app.route("/protected", methods=["GET"])
#jwt_required()
def protected():
# Access the identity of the current user with get_jwt_identity
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
if __name__ == '__main__':
app.run()
Output:
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2Njk2MTA3MCwianRpIjoiNGViY2MwYzAtMjAxYy00ODAwLThjMTUtNmQzNDQ1MmVhYmQxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InVzZXJuYW1lIiwibmJmIjoxNjY2OTYxMDcwLCJleHAiOjE2NjY5NjE5NzAsImF1ZCI6InNvbWVfYXVkaWVuY2UiLCJmb28iOiJiYXIifQ.qAn8rhsxyF_00Ayu9L7ddd6USkbYIHKvsUneDMzzjHs"}
To use the access_token we need an module that can call the Webserver with header.
I will use requests
import requests
key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2Njk2MTA3MCwianRpIjoiNGViY2MwYzAtMjAxYy00ODAwLThjMTUtNmQzNDQ1MmVhYmQxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InVzZXJuYW1lIiwibmJmIjoxNjY2OTYxMDcwLCJleHAiOjE2NjY5NjE5NzAsImF1ZCI6InNvbWVfYXVkaWVuY2UiLCJmb28iOiJiYXIifQ.qAn8rhsxyF_00Ayu9L7ddd6USkbYIHKvsUneDMzzjHs"
requests.get("http://127.0.0.1:5000/protected", headers={'Authorization': 'Bearer ' + key}
If you want to set an expire time set this:
app.config["JWT_SECRET_KEY"] = "supersecret" # Change this!
from datetime import timedelta
pp.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(minutes=1000)
jwt = JWTManager(app)
if you need to know more you can follow the link:
https://flask-jwt-extended.readthedocs.io/en/stable/token_locations/
On the right side is an Navigation to go throw the sides.
This will also show you how to use Cookies with the #jwt_required()

How can the following issue be resolved in flask? "Method Not Allowed The method is not allowed for the requested URL"

Here is the code
import os
import redis
import flask
import json
import urllib.parse
from flask import Flask, Response, request, render_template, abort
from flask_cors import CORS, cross_origin
#from flask.ext.cors import CORS, cross_origin
app = Flask(__name__)
app.config['CORS_HEADERS'] = 'Content-Type'
redis_handle = redis.Redis('localhost')
requiredFields = ("id", "title", "name") # fields required for user object
#app.route('/')
#cross_origin()
def hello():
return 'Hello World!'
#app.route('/users/<user_id>', methods=['GET'])
#cross_origin()
def get_user(user_id):
response = {}
# user_id = request.args.get("id")
user = redis_handle.get(user_id)
if not user:
response["msg"] = "no user found"
return Response(json.dumps(response), status=404, mimetype="application/json")
return user
#app.route('/users', methods=['POST'])
#cross_origin()
def save_user():
data = request.get_json(force=True)
response = {}
if all(field in data for field in requiredFields):
redis_handle.set(data["id"], json.dumps(data))
return Response(status=201)
else:
missing_key = str([val for val in requiredFields if val not in dict(data).keys()])
response["msg"] = "required key " + missing_key + " not found"
return Response(json.dumps(response), status=400)
#app.route('/users/<user_id>', methods=['DELETE'])
#cross_origin()
def delete_user(user_id):
response = {}
resp = redis_handle.delete(user_id)
if resp == 0:
response["msg"] = "no such entity found"
status = 404
else:
response["msg"] = "Delete op is successful"
status = 200
return Response(json.dumps(response), status=status)
#app.route('/clear', methods=['GET'])
#cross_origin()
def clear_data():
redis_handle.flushall()
return "ok!"
if __name__ == "__main__":
app.run(debug=True)
As of my knowledge, I have even included the method = "POST" as well but still don't know what is going wrong.
I tried to create a small crud application using redis, python, flask but couldn't encountering this issue. Can someone tell me where and what am I doing wrong?
Browsers don't run POST methods outside of a <form> entry or AJAX function. Therefore, you're running a GET, which "isn't allowed".
Unclear what you expected, but to see all users, you'll need to edit your route to first add the GET method, then if so, return a response that returns/renders all users rather than checking the request json body, which won't exist for GET requests
If you only wanted to get one user, edit the url to include the user ID
The browser will use the GET method for URLs that you input in URL/search bar but you don't have any function decorated with #app.route('/users', methods=['GET']).
If you want to create a user with POST /users then it would be easier to use some HTTP client like https://www.postman.com, https://insomnia.rest, etc. or even fetch in the browser's console.

Microsoft Authentication - Python Flask msal Example App Ported to FastAPI

I don't do much web work but I recently began using FastAPI and am building an MVC app with jinja2 templating that uses PowerBI embedded capacity to serve multiple embedded analytics in app owns data arrangement. All of this works beautifully. However, I'm wanting to add further modules and I'd like to use the msal package to do user authentication by routing a user to the Microsoft login page, letting them sign in against a multi-tenant app service I set up in Azure, and then redirecting back to my page via redirect URI, grabbing the token, and progressing with authorization. Microsoft saved a great example our here for doing this in Flask. However, I am having fits porting the example to FastAPI.
I can get the user to the login screen and log in but I am having no luck capturing the token at my call back URI - it's appropriately routing but I am unable to capture the token from the response.
Has anyone (or can anyone) taken that super simple Flask example and ported it to FastAPI? Everything I find online for FAPI is back-end token-bearer headers for APIs - not meant for MVC apps.
Here's my current code. Messy because I have "tests" built in.
import msal
import requests
from fastapi import APIRouter, Request, Response
from fastapi.responses import RedirectResponse
from starlette.templating import Jinja2Templates
from config import get_settings
settings = get_settings()
router = APIRouter()
templates = Jinja2Templates('templates')
# Works
#router.get('/login', include_in_schema=False)
async def login(request: Request):
request.session['flow'] = _build_auth_code_flow(scopes=settings.AUTH_SCOPE)
login_url = request.session['flow']['auth_uri']
return templates.TemplateResponse('error.html', {'request': request, 'message': login_url})
# DOES NOT WORK - Pretty sure error is in here --------------------
#router.get('/getAToken', response_class=Response, include_in_schema=False)
async def authorize(request: Request):
try:
cache = _load_cache(request)
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
request.session.get('flow'), request.session
)
if 'error' in result:
return templates.TemplateResponse('error.html', {'request': request, 'message': result})
request.session['user'] = result.get('id_token_claims')
_save_cache(cache)
except Exception as error:
return templates.TemplateResponse('error.html', {'request': request, 'message': f'{error}: {str(request.query_params)}'})
return templates.TemplateResponse('error.html', {'request': request, 'message': result})
# -----------------------------------------------------
def _load_cache(request: Request):
cache = msal.SerializableTokenCache()
if request.session.get("token_cache"):
cache.deserialize(request.session["token_cache"])
return cache
def _save_cache(request: Request, cache):
if cache.has_state_changed:
request.session["token_cache"] = cache.serialize()
def _build_msal_app(cache=None, authority=None):
return msal.ConfidentialClientApplication(
settings.CLIENT_ID,
authority=authority or settings.AUTH_AUTHORITY,
client_credential=settings.CLIENT_SECRET,
token_cache=cache
)
def _build_auth_code_flow(authority=None, scopes=None):
return _build_msal_app(authority=authority).initiate_auth_code_flow(
scopes or [],
redirect_uri=settings.AUTH_REDIRECT)
def _get_token_from_cache(scope=None):
cache = _load_cache() # This web app maintains one cache per session
cca = _build_msal_app(cache=cache)
accounts = cca.get_accounts()
if accounts: # So all account(s) belong to the current signed-in user
result = cca.acquire_token_silent(scope, account=accounts[0])
_save_cache(cache)
return result
Any help is GREATLY appreciated. Happy to answer any questions. Thank you.
This is because FastAPI session variables are stored client-side as a cookie, which has a limit of 4096 bytes of data. The data being stored from the redirect url is pushes the cookie size over this limit and results in the data not being stored. Starlette-session is an alternative SessionMiddleware that stores variables server-side, eliminating cookie limit. Below is a basic (but messy) implementation:
from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from starlette.requests import Request
from starlette.responses import RedirectResponse
from starlette_session import SessionMiddleware
from starlette_session.backends import BackendType
from redis import Redis
import uvicorn
import functools
import msal
app_client_id = "sample_msal_client_id"
app_client_secret = "sample_msal_client_secret"
tenant_id = "sample_msal_tenant_id"
app = FastAPI()
redis_client = Redis(host="localhost", port=6379)
app.add_middleware(
SessionMiddleware,
secret_key="SECURE_SECRET_KEY",
cookie_name="auth_cookie",
backend_type=BackendType.redis,
backend_client=redis_client,
)
templates = Jinja2Templates(directory="templates")
default_scope = ["https://graph.microsoft.com/.default"]
token_cache_key = "token_cache"
# Private Functions - Start
def _load_cache(session):
cache = msal.SerializableTokenCache()
if session.get(token_cache_key):
cache.deserialize(session[token_cache_key])
return cache
def _save_cache(cache,session):
if cache.has_state_changed:
session[token_cache_key] = cache.serialize()
def _build_msal_app(cache=None):
return msal.ConfidentialClientApplication(
app_client_id,
client_credential=app_client_secret,
authority=f"https://login.microsoftonline.com/{tenant_id}",
token_cache=cache
)
def _build_auth_code_flow(request):
return _build_msal_app().initiate_auth_code_flow(
default_scope, #Scopes
redirect_uri=request.url_for("callback") #Redirect URI
)
def _get_token_from_cache(session):
cache = _load_cache(session) # This web app maintains one cache per session
cca = _build_msal_app(cache=cache)
accounts = cca.get_accounts()
if accounts: # So all account(s) belong to the current signed-in user
result = cca.acquire_token_silent(default_scope, account=accounts[0])
_save_cache(cache,session)
return result
# Private Functions - End
# Custom Decorators - Start
def authenticated_endpoint(func):
#functools.wraps(func)
def is_authenticated(*args,**kwargs):
try:
request = kwargs["request"]
token = _get_token_from_cache(request.session)
if not token:
return RedirectResponse(request.url_for("login"))
return func(*args,**kwargs)
except:
return RedirectResponse(request.url_for("login"))
return is_authenticated
# Custom Decorators - End
# Endpoints - Start
#app.get("/")
#authenticated_endpoint
def index(request:Request):
return {
"result": "good"
}
#app.get("/login")
def login(request:Request):
return templates.TemplateResponse("login.html",{
"version": msal.__version__,
'request': request,
"config": {
"B2C_RESET_PASSWORD_AUTHORITY": False
}
})
#app.get("/oauth/redirect")
def get_redirect_url(request:Request):
request.session["flow"] = _build_auth_code_flow(request)
return RedirectResponse(request.session["flow"]["auth_uri"])
#app.get("/callback")
async def callback(request:Request):
cache = _load_cache(request.session)
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(request.session.get("flow", {}), dict(request.query_params))
if "error" in result:
return templates.TemplateResponse("auth_error.html",{
"result": result,
'request': request
})
request.session["user"] = result.get("id_token_claims")
request.session[token_cache_key] = cache.serialize()
return RedirectResponse(request.url_for("index"))
# Endpoints - End
if __name__ == "__main__":
uvicorn.run("main:app",host='0.0.0.0', port=4557,reload=True)`

Sending a Post request to Flask API, but not receiving data

So I am trying to send certain values to my Flask API streamlit application, but it appears that it is not executing a post request. Right now, the post request code I have for my main.py does not work because I am getting a TypeError: 'NoneType'.
app.py :
import requests
import json
import streamlit as st
...
api_url = "http://127.0.0.1:5000/" # Flask url
create_row_data = {'name': name, 'type': get_type(name), 'token_value': token_value,
'external': external, 'start': start, 'end': end, 'step': step}
print(create_row_data)
# The terminal cannot print out the value of r and it appears that it cannot send the post request as well
r = requests.post(url=api_url, json = create_row_data)
print(r)
Output of print(create_row_data) in app.py:
{'name': 'session', 'type': 'area_chart', 'token_value': 'G0as7vjk1ksuxn94',
'external': False, 'start': datetime.datetime(2021,7,1,14,9,7,322438), 'end': datetime.datetime(2021,7,8,14,9,7,322441), 'step': '1d'}
main.py:
from flask import Flask
from flask import jsonify
from flask import request
...
import requests, json
#app.route('/', methods=['GET', 'POST'])
def get_data():
if request.method == 'GET':
return "ok", 200
if request.method =='POST':
p_name = request.json['name']
p_type = request.json['type']
...
p_end = request.json['end']
p_step = request.json['step']
create_row_data = {'p_name': str(p_name), 'p_type': str(p_type), ... , 'p_end': str(p_end), 'p_step': str(p_step)}
print(create_row_data)
response = requests.post(url, data=json.dumps(create_row_data), headers= {'Content-type': 'application/json'}
return response.content
From my understanding, you need to post data to 127.0.0.1 and process it to create a table from app.py using flask_restful to create an API endpoint so that you can post data. The code for main.py would be:
from flask import Flask
from flask import Flask, request
from flask_restful import Resource, Api
from flask import jsonify
from flask import request
import requests, json
app = Flask(__name__)
api = Api(app)
#app.route('/', methods=['GET', 'POST'])
def get_data():
if request.method == 'GET':
return "ok", 200
class create_row_data(Resource):
def post(self):
response = request.get_json()
p_name = response['name']
p_type = response['type']
...
# code to process your data and to create a table
# return something for your client application
return True
api.add_resource(create_row_data, '/create_row_data/')
No changes are needed for your client app.py except the url which now changes to http://127.0.0.1:5000/create_row_data/

Python Flask: Error "The method is not allowed for the requested URL"

I am very new to working with Python Flask and i wanted to try a simple API-example:
from flask import Flask, jsonify, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
some_json = request.get_json()
return {'you sent': some_json}, 201
class Multi(Resource):
def get(self,num):
return {'result': num*10}
api.add_resource(HelloWorld, '/')
api.add_resource(Multi,'/multi/<int:num>')
if __name__ == '__main__':
app.run(debug=True)
and if I type in the terminal
-H "Content-Type: application/json" -X POST -d '{"name":"xyz","address":"myaddress"}' http://127.0.0.1:5000/
I get the following message:
{
"message": "The method is not allowed for the requested URL."
}
I hope someone can help me with this...
Since your are calling the POST HTTP method. You should rename 'get' function in class HelloWorld to 'post'. 'HelloWorld' class can also have both 'get' and a 'post' functions if '/' endpoint should serves both.
from flask import Flask, jsonify, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def post(self):
some_json = request.get_json()
return {'you sent': some_json}, 201
class Multi(Resource):
def get(self,num):
return {'result': num*10}
api.add_resource(HelloWorld, '/')
api.add_resource(Multi,'/multi/<int:num>')
if __name__ == '__main__':
app.run(debug=True)

Categories