I am trying to log my data into the console from my fetch function. I am currently able to log the response to the flask server but not on the react side. I have tried to include the headers in the fetch function however that does not seem to work. As I am still getting an error:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
App.py
from flask import Flask, request, jsonify
from flask_cors import CORS, cross_origin
app = Flask(__name__)
CORS(app)
#app.route('/api', methods=['GET','POST'])
def api():
if request.method == 'POST':
username = request.form['username']
email = request.form['email']
occupation = request.form['occupation']
print('Username: ' + username + ' Email: ' + email + ' occupation: ' + occupation)
print('Json ', (jsonify(username)))
return {"username": username}
else:
return {"username": username}
App.js
import React, {useState, useEffect} from 'react';
import Form from './components/Form'
import './App.css';
function App() {
const [initialData, setInitialData] = useState ([{}])
useEffect(()=> {
fetch('/api',{
headers: {
method: 'GET',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}).then(
response => response.json()
).then(data => console.log(data))
});
return (
<div className="App">
<Form/>
</div>
);
}
export default App;
The problem is that your fetch is yielding a 500 error. Thus, the response is something like:
<!DOCTYPE HTML.....
the reason is you are sending a GET request, so it fails the if request.method == 'POST': test. So, it tries to respond with return {"username": username}, but username is not defined in the else branch of the conditional.
Also, you might want to use:
return jsonify( {"username": username} )
to make sure you are sending an application/json mimetype response.
Related
I am working on a simple user authentication feature for a website using Angular front end and Flask back end. Currently, API requests made through Postman are successful, but when trying to log in through the browser, I receive the console error that login:1 Access to XMLHttpRequest at 'http://localhost:5000/api/auth/login' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I am able to see that the inject_csrf_token() works in generating a csrf token, but when I print the response, the response.cookies['csrf_token'] returns blank. I have tried adding response headers for Access-Control-Allow-Origin with *, but am still receiving the KeyError: 'csrf_token' error.
Any assistance would be greatly appreciated, this has been a major blocker for a few days now.
Here are what I believe are the relevant files:
app.init.py
from flask import Flask, render_template, request, session, redirect
from flask_cors import CORS
from flask_migrate import Migrate
from flask_wtf.csrf import CSRFProtect, generate_csrf
from flask_login import LoginManager
from .models import db, User
from .api.user_routes import user_routes
from .api.auth_routes import auth_routes
from .seeds import seed_commands
from .config import Config
app = Flask(__name__)
# Setup login manager
login = LoginManager(app)
login.login_view = 'auth.unauthorized'
# Tell flask about our seed commands
app.cli.add_command(seed_commands)
app.config.from_object(Config)
app.register_blueprint(user_routes, url_prefix='/api/users')
app.register_blueprint(auth_routes, url_prefix='/api/auth')
db.init_app(app)
Migrate(app, db)
# Application Security
CORS(app)
#app.before_request
def https_redirect():
if os.environ.get('FLASK_ENV') == 'production':
if request.headers.get('X-Forwarded-Proto') == 'http':
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)
#app.after_request
def inject_csrf_token(response):
print("INJECT: ", generate_csrf())
response.set_cookie(
'csrf_token',
generate_csrf(),
secure=True if os.environ.get('FLASK_ENV') == 'production' else False,
samesite='Strict' if os.environ.get(
'FLASK_ENV') == 'production' else None,
httponly=True)
response.headers.add("Access-Control-Allow-Origin", "*")
return response
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def react_root(path):
if path == 'favicon.ico':
return app.send_static_file('favicon.ico')
return app.send_static_file('index.html')
auth_routes.py
from flask import Blueprint, jsonify, session, request
from app.models import User, db
from app.forms import LoginForm
from app.forms import SignUpForm
from flask_login import current_user, login_user, logout_user, login_required
auth_routes = Blueprint('auth', __name__)
def validation_errors_to_error_messages(validation_errors):
"""
Simple function that turns the WTForms validation errors into a simple list
"""
errorMessages = []
for field in validation_errors:
for error in validation_errors[field]:
errorMessages.append(f'{field} : {error}')
return errorMessages
#auth_routes.route('/')
def authenticate():
"""
Authenticates a user.
"""
if current_user.is_authenticated:
return current_user.to_dict()
return {'errors': ['Unauthorized']}
#auth_routes.route('/login', methods=['POST'])
def login():
"""
Logs a user in
"""
form = LoginForm()
# Get the csrf_token from the request cookie and put it into the
# form manually to validate_on_submit can be used
print("##########REQUEST: ", request.cookies.to_dict())
form['csrf_token'].data = request.cookies['csrf_token']
if form.validate_on_submit():
# Add the user to the session, we are logged in!
user = User.query.filter(User.email == form.data['email']).first()
login_user(user)
return user.to_dict()
return {'errors': validation_errors_to_error_messages(form.errors)}, 401
#auth_routes.route('/logout')
def logout():
"""
Logs a user out
"""
logout_user()
return {'message': 'User logged out'}
#auth_routes.route('/signup', methods=['POST'])
def sign_up():
"""
Creates a new user and logs them in
"""
form = SignUpForm()
form['csrf_token'].data = request.cookies['csrf_token']
if form.validate_on_submit():
user = User(
username=form.data['username'],
email=form.data['email'],
password=form.data['password']
)
db.session.add(user)
db.session.commit()
login_user(user)
return user.to_dict()
return {'errors': validation_errors_to_error_messages(form.errors)}, 401
#auth_routes.route('/unauthorized')
def unauthorized():
"""
Returns unauthorized JSON when flask-login authentication fails
"""
return {'errors': ['Unauthorized']}, 401
login_form.py
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired, Email, ValidationError
from app.models import User
def user_exists(form, field):
# Checking if user exists
email = field.data
user = User.query.filter(User.email == email).first()
if not user:
raise ValidationError('Email provided not found.')
def password_matches(form, field):
# Checking if password matches
password = field.data
email = form.data['email']
user = User.query.filter(User.email == email).first()
if not user:
raise ValidationError('No such user exists.')
if not user.check_password(password):
raise ValidationError('Password was incorrect.')
class LoginForm(FlaskForm):
email = StringField('email', validators=[DataRequired(), user_exists])
password = StringField('password', validators=[
DataRequired(), password_matches])
users.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { environment } from 'src/environments/environment';
#Injectable({
providedIn: 'root'
})
export class UsersService {
constructor(
private httpClient: HttpClient
) { }
loginUser(credentials: any) {
console.log("log in clicked")
return this.httpClient.post(`${environment.apiUrl}/auth/login`, {
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials),
});
};
getUser(username: string) {
return this.httpClient.get(`${environment.apiUrl}/users/${username}`, { observe: 'response' })
};
}
I am having an issue with the SetCookie which I can't get it to work. I've seen too many SO topics but no success so far. Here is my situation:
I have an Ajax POST call which sends a token from my frontend (JavaScript) to my backend (Flask). This Ajax call send the token to the route /sessionLogin:
#app.route('/sessionLogin', methods=['POST'])
def session_login():
id_token = request.form['idToken']
csrfToken = request.form['csrfToken']
expires_in = datetime.timedelta(minutes=120)
expires = datetime.datetime.now() + expires_in
session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in)
response = make_response(redirect('/home'))
response.set_cookie(
'session', session_cookie, expires=expires, httponly=True, secure=True)
return response
Then, on any 'protected route' I am basically getting the cookie 'session':
#app.route('/home', methods=['GET', 'POST'])
def home():
session_cookie = request.cookies.get('session')
# session_cookie always returning None
return render_template("index.html")
The problem is that when I sign in, get the token (from JS) and make the POST request, the request to /sessionLogin works great and I see the cookie 'session' in the response but any subsequent requests and by the way the redirect to /home does not include the cookie.
What I am missing here?
UPDATE (adding Ajax code):
firebase.auth().signInWithEmailAndPassword(email, pass)
.then(({user}) => {
return user.getIdToken().then((idToken) => {
console.log(user)
if (idToken) {
const csrfToken = getCookie('csrfToken')
return postIdTokenToSessionLogin('/sessionLogin', idToken, csrfToken)
.then(function() {
console.log('logged in.')
}, function(error) {
window.location.assign('/login');
})
}
})
})
const postIdTokenToSessionLogin = function(url, idToken, csrfToken) {
// POST to session login endpoint.
return $.ajax({
type:'POST',
url: url,
data: {idToken: idToken, csrfToken: csrfToken},
contentType: 'application/x-www-form-urlencoded'
});
};
I have an API endpoint that updates a database. What I want is to update the database, then redirect to /requests with a flash message. Here is the PUT endpoint that updates the database:
#requests.route('/<request_id>', methods=['DELETE', 'GET', 'PUT'])
def one_req(request_id):
if (request.method == 'PUT'):
req = ChangeRequest.query.get(request.form['request_id'])
req.state = request.form['state']
try:
db.session.commit()
except:
flash('There was an error updating the request.', 'error')
else:
flash('The request has been ' + request.form['state'].lower() + '.', 'success')
return redirect(url_for('requests.index'))
Here's the endpoint I'd like to redirect to:
#requests.route('/', methods=['GET', 'POST', 'PUT'])
def index():
if(request.method == 'GET'):
requests = db.session.query(ChangeRequest, Project).filter(...).all()
return render_template('requests.html.jinja', requests=requests)
For completeness, here's the Javascript that makes the PUT request:
$.ajax({
url: `/requests/${data.request_id}`,
data,
type: 'PUT'
})
The redirect isn't working. The request is a PUT, so that's what get's redirected to requests.index. I have some questions to help me understand what's going on:
How can I change this to a GET request instead?
Even if I capture a PUT in requests.index (e.g., by changing the first line of requests.index to if(request.method == 'GET' or request.method == 'PUT):), I see the following in the Javascript console: PUT http://txslibvpda1v.nss.vzwnet.com:5001/requests/ net::ERR_CONNECTION_RESET 200 (OK). The template does not re-render. How can I achieve the behavior I am looking for? Thanks!
I was able to fix this by returning a success indicator to the Javascript function:
if (request.method == 'PUT'):
req = ChangeRequest.query.get(request.form['request_id'])
req.state = request.form['state']
try:
db.session.commit()
except:
flash('There was an error updating the request.', 'error')
else:
flash('The request has been ' + request.form['state'].lower() + '.', 'success')
data = { c.name: str(getattr(req, c.name)) for c in req.__table__.columns }
return jsonify(data)
In the success handler of the put request, I closed the modal & reloaded the page. The flash messages persist through the reload:
$.ajax({
url: `/requests/${data.request_id}`,
data,
type: 'PUT',
success: res => {
$('#requestModal').modal('hide')
location.reload()
}
})
I'm using Django with Allauth + REST-Auth for SPA social Login and successfully set up Facebook, VK and Google authorization but faced a problem while adding Twitter. It ends up with {"code":89,"message":"Invalid or expired token."}
Looks like i'm missing something 'cos standard login with Twitter works as it should
Here are my tries:
First of all, i've set Twitter login endpoint as described in doc:
class TwitterLogin(SocialLoginView):
serializer_class = TwitterLoginSerializer
adapter_class = CustomTwitterOAuthAdapter
It features post method, expecting access_token and token_secret
So redirect view was created to receive redirect from twitter, complete login and set inner django token to browser localStorage via template render (with couple of JS lines):
class TwitterReceiveView(APIView):
def get(self, request, *args, **kwargs):
access_token = request.query_params.get('oauth_token')
token_secret = request.query_params.get('oauth_verifier')
params = {'access_token': access_token,
'token_secret': token_secret}
try:
result = requests.post(settings.DOMAIN + reverse('tw_login'), data=params).text
result = json.loads(result)
except (requests.HTTPError, json.decoder.JSONDecodeError):
result = {}
access_token = result.get('access_token')
context = {'access_token': access_token}
return render(request, 'account/local_storage_setter.html',
context, content_type='text/html')
Have to mention that I tried two methods to start process(get initial token)
1. Used standard allauth url http://0.0.0.0:8080/accounts/twitter/login
2. Created another view (using lib python oauth2) which could be used from SPA:
class TwitterGetToken(APIView):
def get(self, request, *args, **kwargs):
request_token_url = 'https://api.twitter.com/oauth/request_token'
authorize_url = 'https://api.twitter.com/oauth/authorize'
app = SocialApp.objects.filter(name='Twitter').first()
if app and app.client_id and app.secret:
consumer = oauth.Consumer(app.client_id, app.secret)
client = oauth.Client(consumer)
resp, content = client.request(request_token_url, "GET")
if resp['status'] != '200':
raise Exception("Invalid response {}".format(resp['status']))
request_token = dict(urllib.parse.parse_qsl(content.decode("utf-8")))
twitter_authorize_url = "{0}?oauth_token={1}"\
.format(authorize_url, request_token['oauth_token'])
return redirect(twitter_authorize_url)
raise Exception("Twitter app is not set up")
I even tried to write get method for FacebookLoginView and pass twitter callback to it directly
class TwitterLogin(SocialLoginView):
serializer_class = TwitterLoginSerializer
adapter_class = TwitterOAuthAdapter
def get(self, request, *args, **kwargs):
data = {
'access_token': request.query_params.get('oauth_token'),
'token_secret': request.query_params.get('oauth_verifier')
}
self.request = request
self.serializer = self.get_serializer(data=data,
context={'request': request})
self.serializer.is_valid(raise_exception=True)
self.login()
return self.get_response()
All methods led me to mentioned error. Could you, please, advise something in my case. Thank you in advance!
UPDATE:
Here is how it's worked for me:
import json
import requests
import urllib.parse
import oauth2 as oauth
from requests_oauthlib import OAuth1Session
from django.urls import reverse
from django.conf import settings
from django.shortcuts import redirect, render
from rest_framework.views import APIView
from allauth.socialaccount.models import SocialApp
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter, TwitterAPI
from rest_auth.social_serializers import TwitterLoginSerializer
from rest_auth.registration.views import SocialLoginView
class TwitterGetToken(APIView):
'''
Initiates Twitter login process
Requests initial token and redirects user to Twitter
'''
def get(self, request, *args, **kwargs):
request_token_url = 'https://api.twitter.com/oauth/request_token'
authorize_url = 'https://api.twitter.com/oauth/authorize'
app = SocialApp.objects.filter(name='Twitter').first()
if app and app.client_id and app.secret:
consumer = oauth.Consumer(app.client_id, app.secret)
client = oauth.Client(consumer)
resp, content = client.request(request_token_url, "GET")
if resp['status'] != '200':
raise Exception("Invalid response {}".format(resp['status']))
request_token = dict(urllib.parse.parse_qsl(content.decode("utf-8")))
twitter_authorize_url = "{0}?oauth_token={1}"\
.format(authorize_url, request_token['oauth_token'])
return redirect(twitter_authorize_url)
raise Exception("Twitter app is not set up")
class TwitterLogin(SocialLoginView):
'''
Takes the final twitter access token, secret
Returns inner django Token
'''
serializer_class = TwitterLoginSerializer
adapter_class = TwitterOAuthAdapter
class TwitterReceiveView(APIView):
'''
Receives Twitter redirect,
Requests access token
Uses TwitterLogin to logn and get django Token
Renders template with JS code which sets django Token to localStorage and redirects to SPA login page
'''
def get(self, request, *args, **kwargs):
access_token_url = 'https://api.twitter.com/oauth/access_token'
callback_uri = settings.DOMAIN + '/accounts/twitter/login/callback/'
app = SocialApp.objects.filter(name='Twitter').first()
client_key = app.client_id
client_secret = app.secret
oauth_session = OAuth1Session(client_key,
client_secret=client_secret,
callback_uri=callback_uri)
redirect_response = request.get_full_path()
oauth_session.parse_authorization_response(redirect_response)
token = oauth_session.fetch_access_token(access_token_url)
params = {'access_token': token['oauth_token'],
'token_secret': token['oauth_token_secret']}
try:
result = requests.post(settings.DOMAIN + reverse('tw_login'),
data=params).text
result = json.loads(result)
except (requests.HTTPError, json.decoder.JSONDecodeError):
result = {}
access_token = result.get('access_token')
context = {'access_token': access_token,
'domain': settings.DOMAIN}
return render(request, 'account/local_storage_setter.html',
context, content_type='text/html')
Great code, Thank you for posting!
I'd like to add however that user authentication can be done directly from the front end, and given you're writing an SPA it seems logical to do so, instead of redirect your in your back-end (which kind of breaks the notion of a RESTful) to auth and then send a response.
I used the following JS helper class based of vue-authenticate. To make a popup and get info from the callback url
export default class OAuthPopup {
constructor(url, name, redirectURI) {
this.popup = null
this.url = url
this.name = name
this.redirectURI = redirectURI
}
open() {
try {
this.popup = window.open(this.url, this.name)
if (this.popup && this.popup.focus) {
this.popup.focus()
}
return this.pooling()
} catch(e) {
console.log(e)
}
}
pooling() {
return new Promise((resolve, reject) => {
let poolingInterval = setInterval(() => {
if (!this.popup || this.popup.closed || this.popup.closed === undefined) {
clearInterval(poolingInterval)
poolingInterval = null
reject(new Error('Auth popup window closed'))
}
try {
var popupLocation = this.popup.location.origin + this.popup.location.pathname
if (popupLocation == this.redirectURI) {
if (this.popup.location.search || this.popup.location.hash ) {
const urlParams = new URLSearchParams(this.popup.location.search);
var params = {
oauth_token: urlParams.get('oauth_token'),
oauth_verifier: urlParams.get('oauth_verifier'),
url: this.popup.location.href
}
if (params.error) {
reject(new Error(params.error));
} else {
resolve(params);
}
} else {
reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.'))
}
clearInterval(poolingInterval)
poolingInterval = null
this.popup.close()
}
} catch(e) {
// Ignore DOMException: Blocked a frame with origin from accessing a cross-origin frame.
}
}, 250)
})
}
}
The methodology I followed is similar to yours however:
I make a GET request to TwitterGetToken and get the Twitter auth url back as a response
I use the url in the response from my front end to open a popup which the allows the user to auth
I make a POST request to TwitterReceiveView and attach the response url after twitter auth
everything else the just falls into place and the user is returned a access key.
In any case,
Thanks I messed around with loads of libraries in js and python but this was just the best way to do things
I have the below Python Flask router:
#app.route('/create', methods=['GET', 'POST'])
#crossdomain(origin='*')
def create():
if request.method == 'POST':
print(request.form)
title = request.form['title']
url = request.form['url']
new_mark = Mark(
title=title,
url=url
)
new_mark.save()
return new_mark
When I do an ajax call (below) it responds with a 400 error.
$.ajax({
type: 'POST',
url: 'http://localhost:5000/create',
data: {
'title': sender.title,
'url': sender.url
},
xhrFields: {
withCredentials: true
},
dataType: 'json'
});
When I try printing out request it prints an empty immutableMultiDict. Any idea why it is giving this 400 and why there is no data?
Your ajax call is sending json-encoded data. I guess you should decode.
import json
data = json.loads(request.data)
print data.get("title")
I am stupid. I was not actually sending any data because sender.url and sender.title did not contain any values -_-.