I'm using flask-oauthlib module to develop both oauth 2 client and provider
When using resource owner password flow, the provider won't redirect to client's redirect url.
Here is my client code for sending post to provider:
#app.route('/signin', methods=['POST', 'GET'])
def signin():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
f = {'client_id': 'jCce40zAaHXLxP0prU*************',
'client_secret': 'vzf7U219hrAjIYN70NcFo3VBQzott******',
'grant_type': 'password', 'scope': 'email',
'redirect_uri': 'http://localhost:8000/authorized', 'response_type': 'token'}
data = {'username': username, 'password': password}
encoded_url = 'http://127.0.0.1:5000/oauth/authorize?' + parse.urlencode(f)
headers = {"Content-Type": "application/json"}
requests.post(encoded_url, data=json.dumps(data), headers=headers)
return render_template('signin.html')
And here is provider authorize_handler
#app.route('/oauth/authorize', methods=['GET', 'POST'])
#oauth.authorize_handler
def authorize(*args, **kwargs):
if request.method == 'POST':
details = json.loads(request.data)
username = details['username']
password = details['password']
user = User.query.filter_by(user_name=username).first()
if user:
if user.check_password(password):
session['id'] = user.id
return True
return False
return False
if request.method == 'GET':
user = current_user()
if not user:
session['redirect_after_login'] = request.url
return redirect('/home')
client_id = kwargs.get('client_id')
client = Client.query.filter_by(client_id=client_id).first()
kwargs['client'] = client
kwargs['user'] = user
return render_template('authorize.html', **kwargs)
confirm = request.form.get('confirm', 'no')
return confirm == 'yes'
Also Flask-oauthlib oauth 2 provider logging
Fetched credentials from request {'response_type': 'token', 'state': None, 'client_id': 'jCce40zAaHXLxP0prU************', 'redirect_uri': 'http://localhost:8000/authorized'}.
Found redirect_uri http://localhost:8000/authorized.
Validate client 'jCce40zAaHXLxP0prU***********'
Save bearer token {'scope': 'email', 'access_token': 'y08hkm594YbLe2*****', 'expires_in': 180, 'token_type': 'Bearer'}
Authorization successful.
127.0.0.1 - - [20/Sep/2015 17:40:53] "POST /oauth/authorize?client_id=jCce40zAaHXLxP0prU*********&client_secret=vzf7U219hrAjIYN70NcFo3VBQzot**********&response_type=token&grant_type=password&scope=email&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fauthorized HTTP/1.1" 302 -
The way I see it, the token is being saved but:-
Redirection does not occur
It cause the client to load like forever until I restart it (Even if I tried to access other routes, the client does not respond)
What am I missing ?
NB:
I've implemented server side flow and client side flow and they worked fine
I'm still new to flask
I think you are mixing different grant types of OAuth2. With the Resource Owner Password Credentials grant, the authorization server does not do a redirect, instead it provides a token response to the client.
https://www.rfc-editor.org/rfc/rfc6749#section-4.3
redirect_uris are associated with the Authorization Code grant.
Related
I am trying to add Google OAuth login/register to my app, first testing it locally then on the web.
Google OAuth has been set up. Redirect URLs as below:
A 'sign in' button on the login page loads the login route.
The code in the routes.py file is:
# Create a LoginManager and Flask-OAuthlib object
login_manager = LoginManager()
oauth = OAuth()
# Configure Flask-OAuthlib to use the Google OAuth API
google = oauth.remote_app(
'google',
consumer_key='377916639662-b3hlrf0tqbr4ib13bg8jgu1dsltfin8s.apps.googleusercontent.com',
consumer_secret='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',
request_token_params={
'scope': 'email'
},
base_url='https://www.googleapis.com/oauth2/v1/',
request_token_url=None,
access_token_method='POST',
access_token_url='https://accounts.google.com/o/oauth2/token',
authorize_url='https://accounts.google.com/o/oauth2/auth',
)
#login_manager.user_loader
def load_user(google_id):
return User.query.get(google_id)
# Login
#accounts_bp.route('/login')
def login():
return render_template('login.html')
#accounts_bp.route('/google-login')
def google_login():
callback = url_for(
'accounts_bp.authorized',
_external=True,
next=request.args.get('next') or request.referrer or None
)
return google.authorize(callback=callback)
#accounts_bp.route('/authorized')
def authorized():
resp = google.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error_reason'],
request.args['error_description']
)
session['google_token'] = (resp['access_token'], '')
me = google.get('userinfo')
user = User.query.filter_by(google_id=me.data['id']).first()
if not user:
user = User(google_id=me.data['id'], name=me.data['name'], email=me.data['email'])
db.session.add(user)
db.session.commit()
login_user(user)
return redirect(url_for('dashboard_bp.app_home'))
The error during Google sign in is "Request Invalid: redirect_uri_mismatch":
Question: What is causing the redirect uri mismatch and how to resolve it?
Setup Https on your local machine, following these instructions:
https://link.medium.com/ZEhdGL9T6wb
Then add the origin and redirect to Google Credentials, ensuring they match and do not have trailing slashes, etc.
I integrated Google's code generator recently. It's nicely simple.
https://developers.google.com/identity/gsi/web/tools/configurator
I am using AWS Coginto to sign in a user and retrieve the authorization and refresh token response. I am able to successfully authenticate, retrieve the tokens, and decode the tokens. I verify the tokens are decoded on https://jwt.io/.
However, when I use the flask_jwt_extended.set_access_cookies() with the access_token returned from Cognito I get an error saying
jwt.exceptions.InvalidSignatureError: Signature verification failed
The login and code setting the access token is below.
import os
import boto3
from flask import Flask, request, make_response, redirect, render_template
from flask_jwt_extended import set_access_cookies
app = Flask(__name__)
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
auth_response = boto3.client('cognito-idp').admin_initiate_auth(
UserPoolId=os.environ['AWS_COGNITO_USER_POOL_ID'],
ClientId=os.environ['APP_CLIENT_ID'],
AuthFlow='ADMIN_NO_SRP_AUTH',
AuthParameters={
'USERNAME': username,
'PASSWORD': password
}
)
response = make_response(redirect('login_success', 302))
set_access_cookies(response, auth_response['AccessToken'], max_age=15)
return response
return render_template('login.html')
The issue was the public key being set was from a previously deleted cognito pool and needed to be updated to the current one.
I have been trying for some time now to use a login form, create a JWT with Flask-JWT-Extended and then pass it along with a redirect if it passes the checks.
Very simple example below, but I'm getting "No Authorization Header" in return, which makes me think it won't pass the header which is set? I want to protect a view which will render a template if the auth is ok.
#app.route("/")
def index():
return render_template("index.html")
#app.route('/login', methods=['POST'])
def login():
username = request.form["username"]
password = request.form["password"]
if not username:
return jsonify({"msg": "Missing username parameter"}), 400
if not password:
return jsonify({"msg": "Missing password parameter"}), 400
if username != 'test' or password != 'test!':
return jsonify({"msg": "Bad username or password"}), 401
access_token = create_access_token(identity=username)
response = redirect('http://127.0.0.1:5000/protected')
response.headers["Authorization"] = f'Bearer {access_token}'
return response
#app.route('/protected', methods=['GET'])
#jwt_required
def protected():
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
I have a basic Django server with an python command line client for posting to this service. I have a login function and a post function. Even though the cookie for CSRF is being set from the login function the server is saying forbidden when I try to access the post_product endpoint after logging in.
I've been troubleshooting this for days and haven't had any luck.
/api/login/ function:
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse
#csrf_exempt
def handle_login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
if user.is_authenticated:
return HttpResponse(author.name + ' is logged in, Welcome!', status=201, content_type='text/plain')
return HttpResponse(data)
else:
return HttpResponse('disabled account', status=400, content_type='text/plain')
else:
return HttpResponse('invalid login', status=400, content_type='text/plain')
else:
return HttpResponse('request method invalid ' + request.method, status=400, content_type='text/plain')
/api/postproduct/ function:
def post_story(request):
if request.method == 'POST' and request.user.is_authenticated:
# Pull product details from request.
# Validate product details.
# Create model and save.
Python terminal client
FAILURE_MESSAGE = "The server responded with an unsuccessful code: "
def run():
url ='http://127.0.0.1:8000' # For debugging
logged_in = false
with requests.session() as session:
while True:
command = input("""Enter one of the following commands:
login
post \n""")
# login case.
if command == "login":
url = user_inputs[1]
logged_in = login(session, url)
continue
# post case.
elif command == "post" and logged_in:
post(session, url)
continue
else:
print('incorrect command')
continue
def login(session, url):
username = input("Enter your username: \n")
password = input("Enter your password: \n")
response = session.post(url + "/api/login/", data={'username': username, 'password': password})
# If any response but success notify user.
if response.status_code != 201:
print(FAILURE_MESSAGE + str(response.status_code))
return False
else:
print("Successfully logged in!")
return True
def post(session, url):
# Check session is authenticated
if 'csrftoken' not in session.cookies:
print("Not authenticated, have you logged in to a service?")
return
# Omitted : prompt user for productname, category, price and details.
data = {'productname': productname, 'category': category, 'price': price, 'details': details}
data_json = json.dumps(data)
payload = {'json_payload': data_json}
if not session_is_active():
print("You aren't logged into any services")
return
else:
response = session.post(url + "/api/postproduct/", data=payload)
print(6)
if response.status_code != 201:
print(FAILURE_MESSAGE + str(response.status_code))
return
print("Post was successful")
When I run the client, login works fine and on inspection does set the csrf cookie. However when I then try and post the server responds with 403 forbidden. From the server's output:
[15/Aug/2019 15:45:23] "POST /api/login/ HTTP/1.1" 201 42
Forbidden (CSRF token missing or incorrect.): /api/postproduct/
Django's CSRF protection requires that you post the CSRF cookie and a token hidden in a form field. For AJAX requests, you can set a header instead of the form field.
Try something like the following (untested):
headers = {'X-CSRFToken': session.cookies['csrftoken']}
response = session.post(url + "/api/postproduct/", data=payload, headers=headers)
Error = csrf Forbidden (CSRF token missing or incorrect.) when submitting a request to a Django backend:
in a form, include {% csrf_token %}, which generates an input tag with the csrf token value,
and in the request, have the headers include ‘X-CSRFTOKEN
headers: {
content_type: 'application/json',
'X-CSRFToken': "{{ csrf_token }}"
},
I'm having problems to do test on Django. I've been reading the documentation of the responses and I can't do the same as they explain on the documentation.
When I get the response, I only have access to response.status_code and can't access to context or redirect_chain when I write response.(and now PyCharm shows all available options).
I've checked on settings.py and I've 'BACKEND': 'django.template.backends.django.DjangoTemplates' to be sure that I'm using Django templates so I don't know why don't work the test. I need configure something?
The code of the test I'm trying to do it's:
from django.test import TestCase
from django.test.client import Client
class Test(TestCase):
def testLogin(self):
client = Client()
headers = {'X-OpenAM-Username': 'user', 'X-OpenAM-Password': 'password', 'Content-Type': 'application/json'}
data = {}
response = self.client.post('/login/', headers=headers, data=data, secure=True, follow=True)
assert (response.status_code == 200)
# self.assertRedirects(response, '/menu/', status_code=301, target_status_code=200)
I'm not using Django authentication, the login form sends the data to an IDP and if the IDP sends with a correct answer, the "login" it's successful:
def login(request):
logout(request)
message = None
if request.method == "POST":
form = LoginForm(request.POST)
if form.is_valid():
username = request.POST['username']
password = request.POST['password']
headers = {'X-OpenAM-Username': username, 'X-OpenAM-Password': password, 'Content-Type': 'application/json'}
data = {}
req = requests.post('http://openam.idp.com:8090/openamIDP/json/authenticate', headers=headers, params=data)
if req.status_code == 200:
respJson = json.loads(req.content)
tokenIdJson = respJson['tokenId']
request.session['tokenId'] = tokenIdJson
return render_to_response('menu/menu.html', request)
elif req.status_code == 401:
message = "Invalid username and/or password. Please, try again"
else:
form = LoginForm()
return render_to_response('registration/login.html', {'message': message, 'form': form},
context_instance=RequestContext(request))
The redirect assert it's commented because now it fails, when I do the debug I see an empty redirect_chain. I don't understand why happens this because running the web everything works, all views redirect as expected.
Why I only can check status_code? I'm doing something wrong when I redirect after a successful login that on a normal use it works but on the test not?
Thanks.
The remote authentication url expects the credentials as headers, but your local login view expects them as POST data. Your test passes the credentials as headers to your local view.
As a result, the form is passed an empty dictionary (request.POST contains no actual data), and the form is invalid. You get an empty form as a response, without any redirects.
You have to simply pass the credentials as post data to your local view:
def testLogin(self):
client = Client()
data = {'username': 'user', 'password': 'password'}
response = self.client.post('/login/', data=data, secure=True, follow=True)
assert (response.status_code == 200)
self.assertRedirects(response, '/menu/', status_code=301, target_status_code=200)