POST requests not working with token validation checks - python

With flask_jwt_extended, whenever I'm trying to send a POST request with the following decorators:
#jwt_refresh_token_required
#jwt_required
I am having this 401 error:
{
"msg": "Missing CSRF token"
}
When I use a GET instead, it's working fine.
I have read the documentation that talk about double submit protection, but that does not solve my problem. Any ideas how I could fix my issue?
The code to reproduce the problem is below.
Below is the structure of my code:
- src/__init.py__ # where I put all configs
- src/auth.py # where are the endpoints
init.py
login_serializer = URLSafeTimedSerializer(SERIALIZER_SECRET_KEY)
jwt = JWTManager()
def create_app():
app = Flask(__name__)
app.config["SECRET_KEY"] = SERIALIZER_SECRET_KEY
app.config['JWT_SECRET_KEY'] = JWT_SECRET_KEY
app.config['JWT_TOKEN_LOCATION'] = ['cookies']
app.config['JWT_COOKIE_CSRF_PROTECT'] = True
db.init_app(app)
jwt.init_app(app)
# blueprint for auth routes in our app
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
# blueprint for non-auth parts of app
from .routes import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
auth.py
import logging
from flask import Blueprint, request, current_app as app, jsonify
from werkzeug.security import generate_password_hash, check_password_hash
from . import login_serializer, jwt
from flask_jwt_extended import (jwt_required, jwt_refresh_token_required,
get_jwt_identity, get_raw_jwt, unset_jwt_cookies,
current_user, create_access_token, create_refresh_token, set_access_cookies, set_refresh_cookies)
auth = Blueprint('auth', __name__)
def set_response_cookies(token_identity, resp=None, token_types=["access", "refresh"]):
"""
Helper function to set cookies in response
"""
logging.warning("Setting cookies")
resp = jsonify(resp)
token_types.sort()
if token_types == ["access", "refresh"]:
access_token = create_access_token(identity = token_identity)
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token, "refresh_token": refresh_token})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp
elif token_types == ["access"]:
access_token = create_access_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token})
set_access_cookies(resp, access_token)
return resp
elif token_types == ["refresh"]:
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"refresh_token": refresh_token})
set_refresh_cookies(resp, refresh_token)
return resp
else:
raise ValueError("Wrong Call to this function")
#jwt.user_claims_loader
def add_claims_to_access_token(identity):
"""
"""
return {
'email': identity
}
#jwt.user_loader_callback_loader
def user_loader_callback(identity):
"""
Ignore Here, but I use it to get a User object (not mentionned here) from a Token.
"""
return User.objects(
email=identity,
).first()
#auth.route('/token', methods=['POST'])
def token_post():
""" obj =
{"email": "email", "password": "password"} => Tokens
"""
obj = request.get_json()
resp = set_response_cookies(obj["email"], {"token": True}, ["access", "refresh"])
return resp, 200
#auth.route('/token/access', methods=['POST'])
#jwt_refresh_token_required
def refresh_access_cookies():
if current_user:
resp = set_response_cookies(current_user.email, {"token_refreshed": True}, ["access"])
return resp, 200
So, here, all I have to do to reproduce the error is:
Make a POST request to /token => In postman, my response will get all cookies and headers.
Make a POST request to /token/access => Give the error mentioned above.

On your configuration, you enabled JWT_COOKIE_CSRF_PROTECT to true.
For devep purpose, the error will be gone if you can set it to False which may not safe.
On production, You need to pass csrf_token on your request header.
I think this links can help you.
https://flask-jwt-extended.readthedocs.io/en/stable/tokens_in_cookies/ (see the last section)
https://flask-wtf.readthedocs.io/en/stable/csrf.html

Related

401 UNAUTHORIZED From Flask RESTAPI

I am attempting to log a user in and retrieve an auth token for the user; however, I am getting a 401 UNAUTHORIZED. Researching this issue I keep coming across the "Authorization Header" is incorrect or invalid; however, I am logging the user in and I do not have a valid auth code yet. I cannot understand why CORS will not allow this request through:
Using AXIOS:
export const sendServer = (url, token, method, data, callback) => {
let newUrl = `http://localhost:5000/${url}`
axios({
method: method.toUpperCase(),
url: newUrl,
data: data,
headers: { 'Authorization': token }
})
.then(response => callback({ success: true, data: response.data }))
.catch(error => callback({ success: false, error: error.toJSON() }))
}
On The Flask side:
from flask import Flask
from flask_restful import Api
from flask_cors import CORS
from config import ProdConfig
from auth import Authenticate, check_token
app = Flask(__name__)
app.config.from_object(ProdConfig)
CORS(app)
api = Api(app)
api.add_resource(Authenticate, '/login', endpoint="login")
The '/login' endpoint does not require authentication.
class Authenticate(Resource):
def __init__(self):
self.pwd_context = CryptContext(schemes=["pbkdf2_sha256"],default="pbkdf2_sha256",pbkdf2_sha256__default_rounds=30000)
def post(self):
if request.endpoint == 'login':
.......
Any help would greatly be appreciated.

Spotify API create playlist - error parsing JSON

I have a Flask app where I want to create playlists using Spotify API. My issue is similar to this Stackoverflow question.
The difference is that I am using OAuthlib instead of requests and the solution posted there didn't work in my case.
The problem
In the http request, when I set data={'name': 'playlist_name', 'description': 'something'},
I am getting a response: "error": {"status": 400,"message": "Error parsing JSON."}
But when I follow the answer mentioned above and try this: data=json.dumps({'name': 'playlist_name', 'description': 'something'}),
I am getting following error in the console: "ValueError: not enough values to unpack (expected 2, got 1)".
How I can fix this? Here is a simplified version of my app:
app.py
from flask import Flask, url_for, session
from flask_oauthlib.client import OAuth
import json
app = Flask(__name__)
app.secret_key = 'development'
oauth = OAuth(app)
spotify = oauth.remote_app(
'spotify',
consumer_key=CLIENT,
consumer_secret=SECRET,
request_token_params={'scope': 'playlist-modify-public playlist-modify-private'},
base_url='https://accounts.spotify.com',
request_token_url=None,
access_token_url='/api/token',
authorize_url='https://accounts.spotify.com/authorize'
)
#app.route('/', methods=['GET', 'POST'])
def index():
callback = url_for(
'create_playlist',
_external=True
)
return spotify.authorize(callback=callback)
#app.route('/playlist', methods=['GET', 'POST'])
def create_playlist():
resp = spotify.authorized_response()
session['oauth_token'] = (resp['access_token'], '')
username = USER
return spotify.post('https://api.spotify.com/v1/users/' + username + '/playlists',
data=json.dumps({'name': 'playlist_name', 'description': 'something'}))
#spotify.tokengetter
def get_spotify_oauth_token():
return session.get('oauth_token')
if __name__ == '__main__':
app.run()
You are using the data parameter, which takes a dict object, but you are dumping it to a string, which is not necessary. Also, you have to set the format to json, as follow:
#app.route('/playlist', methods=['GET', 'POST'])
def create_playlist():
resp = spotify.authorized_response()
session['oauth_token'] = (resp['access_token'], '')
username = USER
return spotify.post('https://api.spotify.com/v1/users/' + username + '/playlists',
data={'name': 'playlist_name', 'description': 'something'}, format='json')

My flask app login does not work properly

I have a flask app that works great on laptops/desktops but on mobile phone and small screens the login route doesn't work properly.
When I want to login to my app on a mobile phone the login page redirects to itself and nothing happen. I think this problem is somehow related to session or login decorated function. Sometimes it works suddenly but it doesn't response often.
What can I do now?
Here is my app heads and login route:
from cs50 import SQL
from math import ceil
from flask import Flask, flash, redirect, render_template, request, session
from flask_session import Session
from tempfile import mkdtemp
from werkzeug.security import check_password_hash, generate_password_hash
from helpers import intro_alert, main_alert, login_required, usd, ex_separator, separator, \
cmc_logo, exchange_rates, cmc_quote, cmc_listing, cmc_info, exchanges, exchange_pairs, cmc_projects
coinjito = Flask(__name__)
coinjito.config["TEMPLATES_AUTO_RELOAD"] = True
#coinjito.after_request
def after_request(response):
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
return response
coinjito.config["SESSION_FILE_DIR"] = mkdtemp()
coinjito.config["SESSION_PERMANENT"] = False
coinjito.config["SESSION_TYPE"] = "filesystem"
Session(coinjito)
#coinjito.route("/login", methods=["GET", "POST"])
def login():
session.clear()
if request.method == "GET":
return render_template("login.html")
else:
rows = db.execute("SELECT * FROM users WHERE username=:username",
username=request.form.get("username"))
if len(rows) != 1 or not check_password_hash(rows[0]["password"],
request.form.get("password")):
return intro_alert("Apology", "alert-danger", "Login Failed",
"Invalid username and/or password.", "/login", "Go Back")
session["user_id"] = rows[0]["id"]
session["username"] = rows[0]["username"]
return redirect("/tracking")
#coinjito.route("/tracking")
#login_required
And here is my login helper function:
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if session.get("user_id") is None:
return redirect("/login")
return f(*args, **kwargs)
return decorated_function

Store tokens in browser cookies with Flask jwt extended

I know how to create tokens with this library, and also how to put tokens in reponse body:
access_token = create_access_token(identity = token_identity)
refresh_token = create_refresh_token(identity = token_identity)
set_access_cookies({"login": True}, access_token)
set_refresh_cookies({"login": True}, refresh_token)
However, when using it with my flask application, nothing is stored in my browser cookies.
Do I need to do something more than use the set_access_cookies or set_refresh_cookies in order to store tokens in cookies?
One example of code:
import logging
from flask import Blueprint, render_template, redirect, url_for, request, current_app as app, jsonify
from flask_login import login_user, logout_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash
from .models import User
from . import db, login_manager, login_serializer, jwt
from flask_jwt_extended import (create_access_token,
create_refresh_token, jwt_required, jwt_refresh_token_required, get_jwt_identity, get_raw_jwt, set_access_cookies,
set_refresh_cookies, unset_jwt_cookies)
def set_response_cookies(token_identity, resp=None, token_types=["access", "refresh"]):
"""
Helper function to set cookies
"""
logging.warning("Setting cookies")
token_types.sort()
if token_types == ["access", "refresh"]:
access_token = create_access_token(identity = token_identity)
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token, "refresh_token": refresh_token})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp
elif token_types == ["access"]:
access_token = create_access_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token})
set_access_cookies(resp, access_token)
return resp
elif token_types == ["refresh"]:
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"refresh_token": refresh_token})
set_refresh_cookies(resp, refresh_token)
return resp
else:
raise ValueError("Wrong Call to this function")
#auth.route('/signup', methods=['POST'])
def signup_post():
email = request.form.get('email')
name = request.form.get('name')
password = request.form.get('password')
user = User.objects(email=email)
if user: # Email already exist.
return redirect(url_for('auth.signup')), 409
logging.warning("User not existing")
new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'))
new_user.save()
set_response_cookies(email, ["access", "refresh"])
return redirect(url_for('auth.login')), 200
You can make use of set_cookie()
Store token in cookie:
from flask import make_response
#app.route('/')
def index():
response = make_response(render_template(...))
response.set_cookie('access_token', 'YOUR_ACCESS_TOKEN')
response.set_cookie('refresh_token', 'YOUR_REFRESH_TOKEN')
return response
To Retrieve the token from cookie:
from flask import request
#app.route('/')
def index():
access_token = request.cookies.get('access_token')
In this way you can store the token in a cookie and retrive the token from the cookie.
Check that you have several Set-Cookie in your response header:
The HTTP header Set-Cookie is a response header and used to send cookies from the server to the user agent.
I can also suggest to check your app configurations. Here are my configurations related to jwt-extended:
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", 'local-secret')
JWT_TOKEN_LOCATION = ['cookies']
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(seconds=1800)
JWT_COOKIE_SECURE = False
JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(days=15)
JWT_COOKIE_CSRF_PROTECT = True
JWT_ACCESS_CSRF_HEADER_NAME = "X-CSRF-TOKEN-ACCESS"
JWT_REFRESH_CSRF_HEADER_NAME = "X-CSRF-TOKEN-REFRESH"
The problem may be client-side instead of server-side. My app uses axios and in order to get it to work for me I had to add axios.defaults.withCredentials = true after importing it in the javascript file:
import axios from 'axios'
axios.defaults.withCredentials = true

Handle callback code/retrieve token from Facebook via Flask OAuth2?

I would like to return the token as a string to another python script. However I'm stuck at the callback page. I made the redirect_uri to "/callback" and it returns the authorization code in the URL as
www.my.website.com/callback?code={code}.
The Facebook OAuth 2 example at http://requests-oauthlib.readthedocs.org/en/latest/examples/facebook.html. I keep getting a 404 error by just pasting the redirect URI.
I would also like to request the following parameters: {id, message, created_time} but I'm not sure how.
import os
from requests_oauthlib import OAuth2Session
from flask import Flask, request, redirect, session, render_template, url_for
from requests_oauthlib.compliance_fixes import facebook_compliance_fix
app = Flask(__name__)
app.secret_key = os.urandom(24)
#app.route("/login")
def demo():
client_id = '1624399237811291'
client_secret = 'SECRET'
# OAuth endpoints given in the Facebook API documentation
authorization_base_url = 'https://www.facebook.com/dialog/oauth'
token_url = 'https://graph.facebook.com/oauth/access_token'
redirect_uri = 'http://www.bbg.mybluemix.net/callback' # Should match Site URL
facebook = OAuth2Session(client_id, redirect_uri=redirect_uri)
facebook = facebook_compliance_fix(facebook)
# Redirect user to Facebook for authorization
authorization_url, state = facebook.authorization_url(authorization_base_url)
authurl = '%s' % authorization_url
return render_template('welcome.html', authurl=authurl)
# Get the authorization verifier code from the callback url
#app.route("/callback", methods=["GET"])
def callback():
# Fetch the access token
redirect_response = ' '
token = facebook.fetch_token(token_url, client_secret=client_secret,
authorization_response=redirect_response)
return '%s' % token
# Fetch a protected resource, i.e. user profile
#r = facebook.get('https://graph.facebook.com/me?')
#return '%s' % r.content

Categories

Resources