I'm using Github-Flask to authenitcate users on my app. I use github.authorize(scope='user:email'). How can I get the logged in user's email?
github = GitHub(app)
user = None
#app.route('/login')
def login():
if user.username:
return redirect(url_for('index'))
return github.authorize(scope='user:email')
#github.access_token_getter
def token_getter():
if user is not None:
return user.github_access_token
#app.route('/github-callback')
#github.authorized_handler
def authorized(oauth_token):
if oauth_token is None:
flask.flash("Authorization failed.")
return redirect(url_for('index'))
global user
user.username = oauth_token
return redirect(url_for('index'))
The login route is only redirecting to GitHub's authorization page, it has no way to access the user data yet because the user isn't logged in. Once you get to the authorized callback, you can make API calls to GitHub.
In the authorized route, use github.get to call the user API endpoint.
data = github.get('user')
email = data['email']
Also, do not use global user to store the logged in user. See Are global variables thread safe in flask? Instead, store the user id in session, and load the user into g, as shown in GitHub-Flask's full example.
Related
i have a problem with a functionality of flask-login.
I need to create a system, that prevents multiple users from logging in on one account in the same time. The goal i am trying to achieve, is a way to check if the user is currently logged in, and deny access to people that are trying to log in if the account is already logged in. I was trying to use:
user.is_active
user.is_authenticated
But they always are displaying "True", here is the rest of the code: is there a property that changes after the users logs in/out?
def login():
if request.method == 'GET':
return render_template('login.html')
else:
login = request.form['login']
password = request.form['password'].encode('utf-8')
user = User.query.filter_by(username=f"{login}").first()
if user == None:
return "Non-Existing User"
else:
if bcrypt.checkpw(password,user.password.encode('utf-8')):
login_user(user)
return "Success"
else:
return "Bad creds."
#app.route('/logout')
#login_required
def logout():
logout_user()
return 'You are logged out'```
I don't know about any property but we can do it another way. You can store a token like thing in database with the user details.
class user(db.Model):
[...]
token = db.Column(db.String(20))
when the user login, you can generate a random hex or string and store it in database.
When user logout set token to False
while logging in the user , you can check if token is not False.
user = User.query.filter_by(username=user_input).first()
if user.token != False:
// allow user to login
else:
// don't allow him and show warning message.
The below login solution is not successfully authenticating the user.
After the login_user(user) function is called, the current_user is supposed to be authenticated.
When trying: print(flask_login.current_user.is_authenticated) immediately after login this returns TRUE, but as soon as the next page loads it returns FALSE.
In summary: The user authentification state is not persistent.
Consequently, pages protected with the #flask_login.login_required decorator (below the #app.route line) are inaccessible and I am redirected to the #login_manager.unauthorized_handler page.
As I understand it, the Flask framework is supposed to set cookies or send some kind of information in the headers that contain some kind of authentication. I also tried using the flask 'sessions' module and also tried using the 'g' module, but these produced entirely different errors.
The below version of the code I plagiarised from https://github.com/maxcountryman/flask-login, which uses only the things I have below. Yet for some reason, my version isn't working.
For the purposes of this testing phase, I made a list containing python dicts of a user so I don't have to make multiple unnecessary calls to the Firebase db I'm using to store the info.
This is the setup section for the login manager including my copy of the UserMixin class:
app.secret_key = 'dummysecretkey'
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
class User(flask_login.UserMixin):
pass
This is a sample of the structure of the users:
{
'email': 'email#domain.com',
'password': 'password',
'username': 'user01'
...*other fields*...
}
Here are the routes for the login manager:
#login_manager.user_loader
def user_loader(username):
for entries in users:
if entries['username'] not in users:
return
for entries in users:
if entries['username'] == username:
user = User()
user.id = entries['username']
return user
#login_manager.request_loader
def request_loader(request):
username = request.form.get('username')
try:
for entries in users:
if entries['username'] == username:
user = User()
user.id = entries['username']
user.is_authenticated = request.form['password'] == entries['password']
return user
except:
return None
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
uname = request.form['uname']
psw = request.form['psw']
for entries in users:
if uname == entries['username'] and psw == entries['password']:
user = User()
user.id = uname
flask_login.login_user(user)
print(flask_login.current_user.is_authenticated)
return redirect(url_for('addnew'))
return 'Bad login'
Example route that requires authentication:
#app.route('/addnew')
#flask_login.login_required
def addnew():
return render_template("addnew.html")
Notwithstanding the flaws in the password comparison (I'm aware they're insecure), could someone please point out the flaw in the above code or present an alternative to achieve the login authentication?
I am bit confused by your code.
From the Flask Login documentation:
Sometimes you want to login users without using cookies, such as using header values or an api key passed as a query argument. In these cases, you should use the request_loader callback.
But I understand you want to use cookie based login?
So you should not use the request_loader.
Also, why do you instantiate a new User in the login route? The login should compare the login data with already existing users.
I suggest you "manually" instantiate the users and put them in a list like this:
users = []
a = User()
a.name = "name_a"
a.id = "id_a"
users.append(a)
...
And then adapt your user_loader to compare the login data from the form with the existing data.
There is an excellent Flask tutorial out there by Miguel Grinberg, which also explains the user login:
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world
I have several files that can be downloaded on my website.
The HTML content refers to this method:
from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
...
#app.route('/customer_section/download/<product_id>')
#login_required
def customer_download_file(product_id):
# get the company the currently logged in user belongs to
user_company = current_user.company
# get the product entity
product = Product.query.filter_by(id=product_id).first_or_404()
# now get the company, the product belongs to
product_company = product.project.owner
if current_user.is_admin or (not current_user.is_admin and product_company.id == user_company.id):
print("Splitting Path %s to folder and filename..." % product.path)
# Split the path to folder and filename
folder, filename = os.path.split(product.path)
return send_from_directory(folder, filename, mimetype="application/octet-stream", as_attachment=True)
else:
# Unauthorized
abort(401)
This works great when the user logs in via flask_login in my login view.
So the user logs in via my login view, then somehow navigates through my website, and the clicks on a download link to download a file.
But what if a user tries to download a file directly?
I'd then like to show the user a popup so the user can enter his credentials and may download the file.
Is this possible?
Sorry, I guess my description was not clear enough: I‘m not trying to show a custom popup view of mine when a user calls my download method. I‘d like the default (browser specific) Basic Authentication popup to appear if a non logged in user tries to call that specific url. As if I had a restrictive .htaccess file that ensures that only authorized users continue. If the user reaches the same url via my webpage (after logging in, this method simply returns the file stream).
Ok, after reading this link I solved it like this:
def get_db_user(username):
user = User.query.filter_by(username=username).first()
return user
def check_auth(username, password):
"""This function is called to check if a username /
password combination is valid.
"""
if current_user.is_authenticated:
return True
user = get_db_user(username)
if not user or not user.check_password(password):
return False
else:
return True
#login.unauthorized_handler
def handle_unauthorized_callback():
page_to_load = request.path
print("Tried to access page: %s" % page_to_load)
# this regex match checks whether a specific url was called
if is_file_download_url_pattern.match(page_to_load):
basic_auth = request.authorization
if not basic_auth or not check_auth(basic_auth.username, basic_auth.password):
return authenticate()
else:
user = get_db_user(basic_auth.username)
login_user(user, False)
# extract product id
product_id = re.search(product_id_pattern, page_to_load).group()
return customer_download_file(int(product_id))
else:
return redirect(url_for('login'))
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'}
)
I don't know whether this is an elegant solution, but at least it solves my problem for now.
Thanks.
I am using the OAUTH API to verify access to the users google calendar. They are not logging into my system using OAUTH, just accepting my site accessing their data.
The problem is, if the user is logged out of their google account and after they hit verify, it force logs them out of their session and I have no way of linking them back up.
This DOES work if they are already logged into the google account in their browser session and hit accept, they will be redirected to the right page.
I replicate the error when the cache, cookies are clear and they need to relogin into their google account to verify.
I've tried storing the session ID, etc, but the request parameter is not containing the same request data as the initial view, so there is a conflict in the data I am trying to retrieve.
The user is logged in using the standard Django libraries for the credentials model.
CODE
FLOW = flow_from_clientsecrets(
CLIENT_SECRETS,
scope='https://www.googleapis.com/auth/calendar.readonly',
redirect_uri='http://127.0.0.1:8000/oauth2callback')
'''''''''''''''''''''''''''''''''''''''''''''''''''
Main function dealing with auth verification
'''''''''''''''''''''''''''''''''''''''''''''''''''
def index(request):
current_user = User.objects.get(username=request.user.username)
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
credential = storage.get()
if credential is None or credential.invalid == True:
FLOW.params['state'] = xsrfutil.generate_token(settings.SECRET_KEY,
request.user.id)
authorize_url = FLOW.step1_get_authorize_url()
return redirect(authorize_url)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
User then calls the data function once authenticated
''''''''''''''''''''''''''''''''''''''
def auth_return(request):
print("THE CURRENTLY REQUESTED USER IN THIS SESSION REQUEST IS %s"%(request.user.username))
credential = FLOW.step2_exchange(request.REQUEST)
try:
current_user = User.objects.get(id=request.user.id)
except:
return HttpResponseRedirect("/login")
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
storage.put(credential)
return HttpResponseRedirect("/get_cal")
Ok, so this was a bit more involved than I thought.
I fixed this by adding a state parameter of the currently logged in username.
logged_in_username = request.user.username
user_data = {'var_test' : logged_in_username}
pass_param = urllib.urlencode(user_data)
FLOW.params['state']=pass_param
authorize_url = FLOW.step1_get_authorize_url()
This gave me the ability to query the user from the DB via the User model in Django contrib. I parsed out the state var from the URL:
#Get the currently logged in username
user_variable_data = str(FLOW.params['state'])
#get rid of the var_test= preprended text data for parsing reasons
user_variable_data = user_variable_data[9:]
#Get that user from the database in the form of a user object
current_user = User.objects.get(username=user_variable_data)
and then built a custom backend authentication file to auth the user without a password to maintain the request like nothing weird ever even happened.
user = authenticate(username=user_variable_data)
login(request, user)
print("AUTHENTICATED")
Appended this to settings file
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'procrastinate.auth_backend.PasswordlessAuthBackend',
)
Custom Backend File
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
class PasswordlessAuthBackend(ModelBackend):
"""Log in to Django without providing a password.
"""
def authenticate(self, username=None):
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
In short:
By only using the Flask micro-framework (and its dependencies) can we perform an internal redirect from one route to another?
For example:
User submits the registration form (both username and password) to #app.route('/register', methods=['POST'])
If the registration is successful, Flask internally does an HTTP POST to #app.route('/login', methods['POST']) passing the username and password
Process and log in the user
Details:
I am building a REST API using Flask and the Flask-JWT extension. More specifically I'm implementing the login and registration.
Login works perfectly and returns a JSON object with a token.
Following is my (login) authentication handler (i.e. /auth (POST request) - Default Flask-JWT authentication URL rule):
#jwt.authentication_handler
def authenticate(username, password):
user = User.query.filter_by(username=username).first()
if user and user.verify_password(password):
return user
return None
A successful login returns:
{
"token": "<jwt-token>"
}
Following is my registration route:
#app.route('/register', methods=['PUT'])
def register():
username = request.form.get('username')
password = request.form.get('password')
if username is None or password is None:
abort(400) # missing parameters
user = User.query.filter_by(username=username).first()
if user:
abort(400) # user exists
else:
user = User(user=user)
user.hash_password(password)
db.session.add(user)
db.session.commit()
# How do we generate a token?
# Perform an internal redirect to the login route?
return jsonify({'token': <jwt-token>}), 201
You should use the Post-Redirect-Get pattern.
from flask import Flask, redirect, request, render_template
app = Flask("the_flask_module")
#app.route('/', methods=["GET", "POST"])
def post_redirect_get():
if request.method == "GET":
return render_template("post_redirect_get.html")
else:
# Use said data.
return redirect("target", code=303)
#app.route("/target")
def target():
return "I'm the redirected function"
app.run(host="0.0.0.0", port=5001)
And if you want to pass data to the target function (like that token) you can use the session object to store it
So that would break down something like
#app.route('/register', methods=['PUT'])
def register():
username = request.form.get('username')
password = request.form.get('password')
if username is None or password is None:
abort(400) # missing parameters
user = User.query.filter_by(username=username).first()
if user:
abort(400) # user exists
else:
user = User(user=user)
user.hash_password(password)
db.session.add(user)
db.session.commit()
# How do we generate a token?
redirect("login_success", code=307)
#app.route("login_success", methods=["GET", "POST"])
#jwt_required()
def login_success():
return "Redirected Success!"
Edit:
I haven't used Flask-JWT before and didn't know about the post requirement. But you can tell Flask to redirect with the current method used (rather than a get request) by passing the redirect function code=307.. Hopefully that solves your extended problem.