session.pop() does not clear cookies - python

I am new to Flask framework and am playing around with it to learn it better. I am following this tutorial along my way.
As per the User Authentication tutorial in the series, I am stuck with the below:
In the tutorial, when the user logs out by hitting the /logout route, the first thing that happens is :
session.pop('logged_in', None)
Now as per the video mentioned above, the moment user hits the /logout route the cookie also gets deleted from the browser.
Now 2 questions here:
In my case, with the exact same setup as the tutorial, although the session might be getting invalidated from the server end, the cookie does NOT get deleted/changed in any way from the browser after the /logout route is hit. Is there something wrong that am doing?
session.pop(...) => how/why exactly will it delete something from the front end, the browser. It can only control things on the server, isn't it ?
For your reference below is my code (taken from the tutorial itself)
# import the Flask class from the flask module
from flask import Flask, render_template, redirect, url_for, request, session, flash
# create the application object
app = Flask(__name__)
app.secret_key = 'my precious'
# use decorators to link the function to a url
#app.route('/')
def home():
return "Hello, World!" # return a string
#return render_template(index.html)
#app.route('/welcome')
def welcome():
return render_template('welcome.html') # render a template
# route for handling the login page logic
#app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != 'admin' or request.form['password'] != 'admin':
error = 'Invalid Credentials. Please try again.'
else:
session['logged_in'] = True
flash('You were just logged in')
return redirect(url_for('home'))
return render_template('login.html', error=error)
#app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were just logged out')
return redirect(url_for('welcome'))
# start the server with the 'run()' method
if __name__ == '__main__':
app.run(debug=True)

First of all session and cookie is not the same. Session is more like unique id posted to you browser and something like a key for the dictionary for you backend. So most of the time, when you change session(not session id), you just modify backend part(add or delete values in backend dictionary by that key). Not browser's cookie.
You understood all correct. When you pop "logged in" from session server will remember that this browser will not logged_in any more.
So cookie used here just to identify the client browser. That is it.

You can set the expiry time of cookie to 0, this will make it invalid
#app.route('/logout')
def logout():
session.pop('token', None)
message = 'You were logged out'
resp = app.make_response(render_template('login.html', message=message))
resp.set_cookie('token', expires=0)
return resp

To remove the cookie you want to remove, you need to set the cookie's max-age to 0, and send it back to the user's browser. You can achieve this by doing this:
#app.route('/logout')
def logout():
resp = make_response(render_template('login.html', message="You are logged out."))
resp.delete_cookie('token')
return resp
the delete_cookie method is a shorthand for calling set_cookie with the cookie's name, an empty string for the value, and expires=0. The method sets the max-age of the cookie to 0, which means it is immediately expired.
In some older browsers you may find that they don't handle the Expires attribute correctly, so using delete_cookie might be a safer choice.
It also looks more clean IMHO.

Related

Why does inserting a function inside a route differ from inserting the code inside the function in Flask?

I am trying to make a web app with a login system. I want to make it so that a user can't access certain pages unless they are logged in.
What I want is that when you click to go to another page while not logged in, you get redirected to the login page and on it you get a message flash.
This is what works:
#app.route("/home", methods=['GET', 'POST'])
def home():
#some form
if not current_user.is_authenticated:
flash('You need to be logged in to access this page.', 'info')
return redirect(url_for('login'))
#rest of the code
But I would need to add all of this to other routes as well. So I created the function and added it to the routes instead:
#app.route("/home", methods=['GET', 'POST'])
def home():
#some form
require_login()
#rest of the code
def require_login():
if not current_user.is_authenticated:
flash('You need to be logged in to access this page.', 'info')
return redirect(url_for('login'))
But this does not work as I want it to. It instead redirects to the home page and then flashes the message. How do I fix this?
The problem is that the redirect(...) doesn't itself do the redirect. It returns a value to Flask telling flask that it needs to do the redirect.
In your first piece of code, you handle this correctly. You take the result of redirect(...) and return it to flask. In your second piece of code, you take the redirection returned by require_login and ignore it in home.
You might try something like:
value = require_login()
if value:
return value
You need to return the function
return require_login()
But be aware, after that u cant have code. You should create an decorator for this. There are examples online just Google "flask authorized decorator"
Your Advantage of this that u can move the auth Logic out of the Views and you can easily decorate your Views and dont have this Stuff in each View/route

Preventing User from accessing login page while signed in using Flask-Login

In Flask, one can use the #login_required decorator in order to protect endpoints. However, I am encountering the opposite issue - how can I prevent my login page from being accessed whilst the user is signed in?
There isn't code that I can really attach, since I have no idea where to even start on this. Any help would be much appreciated! I have followed this tutorial so my code is very similar to this:
https://www.digitalocean.com/community/tutorials/how-to-add-authentication-to-your-app-with-flask-login
Import current_user from flask_login and check if the user (current_user) is authenticated in your view (login view). If the user is authenticated, redirect it to the URL you want.
from flask_login import current_user
#app.route('/login', methods=['GET','POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('home'))
edit the parameters and:
def login_not_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if "logged_in" in session:
return redirect(url_for("index"))
else:
return f(*args, **kwargs)
return decorated_function
how can I prevent my login page from being accessed whilst the user is signed in?
You might use flask-login get_id() and then check if what was returned is not None, which will mean user is logged in.

flask current_user.is_authenticated become "True" before login_user() called

Am I the only one experience this problem? login() mechanism I'm using is the most common flask login using login_manager, with User model.
I'm using python v3.8.3, Flask v1.1.2, Flask-Login v0.5.0
The problem is that "current_user.is_authenticated" is always "True" after "POST" request received on /login route, even before login_user() is called !
Then app redirect user to index page, because index route is under #login_required and because login_user() is not yet called, the user is directed to a error page requesting to login.
def login():
login_form = LoginForm()
if current_user.is_authenticated:
return redirect(url_for('index'))
if login_form.validate_on_submit():
username = login_form.username.data
password = login_form.password.data
# Locate user
user = User.query.filter_by(username=username).first()
# Check the password
if user and verify_pass( password, user.password):
login_user(user, remember=False)
current_user.is_authenticated is False when "GET" request received for /login route to display the form, but as soon as I click submit, it become True.
Any clue is appreciated.
now I'm using a workaround, by setting session['USERNAME'] = username after login_user()
Then replace the line
"if current_user.is_authenticated:" with
"if 'USERNAME' in session:"
So far, it seems to be a stable solution.
Although I still has no clue why current_user.is_authenticated is always True.

Pytest Flask Request Referrer

I'm currently testing my Flask application using Pytest and ran into a problem with a POST request and a redirect. Let me explain a bit more.
A user wants to register for our new site, but must confirm they have an account with a different site. Once they confirm the credentials of the other account, they are taken to the register page. They can only hit the register page if coming from the confirmation page else they are redirected back to the home page.
I want to test this functionality and can successfully make a POST request to the confirmation page. If I don't specify follow_redirects=True and print the response data, I get the following HTML:
Redirecting...
Redirecting...
You should be redirected automatically to target URL: /register?emp_id=1. If not click the link.
Great! Exactly what I'm looking for! I want to be redirected to the registration page.
Now when I do specify follow_redirects=True and print out the response data, I expected the register page HTML to return. The response data instead returns the home page HTML.
I further investigated where the problem was. As I mentioned before, the only way you can hit the registration page is from the confirmation page. I took a look at the request.referrer attribute in the view during the test and it will return None. I attempted setting the Referrer header content in the test's POST request, but to no luck.
Here is the code I'm working with:
views.py
#app.route('/confirm', methods=['GET', 'POST'])
def confirm_bpm():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = BPMLoginForm()
if form.validate_on_submit():
bpm_user = BPMUser.query\
.filter(and_(BPMUser.group_name == form.group_name.data,
BPMUser.user_name == form.username.data,
BPMUser.password == encrypt(form.password.data)))\
.first()
if not bpm_user:
flash('BPM user account does not exist!')
url = url_for('confirm_bpm')
return redirect(url)
if bpm_user.user_level != 3:
flash('Only L3 Users can register through this portal.')
url = url_for('confirm_bpm')
return redirect(url)
url = url_for('register', emp_id=bpm_user.emp_id)
return redirect(url)
return render_template('login/confirm_bpm.html', form=form)
#app.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('index'))
if request.method == 'GET' and\
request.referrer != request.url_root + 'confirm':
return redirect(url_for('index'))
emp_id = request.args.get('emp_id')
emp_id_exists = User.query.filter_by(emp_id=emp_id).first()
if emp_id_exists:
flash('User is already registered!')
return redirect(url_for('login'))
form = RegistrationForm()
if form.validate_on_submit():
new_user = User(login_type=form.login_type.data, login=form.login.data,
emp_id=emp_id)
new_user.set_password(form.password.data)
db.session.add(new_user)
db.session.commit()
flash('Registration successful!')
return redirect(url_for('login'))
return render_template('login/register.html', form=form)
TESTS
base.py
from config import TestConfig
from app import app, db
#pytest.fixture
def client():
"""
Initializes test requests for each individual test. The test request
keeps track of cookies.
"""
app.config.from_object(TestConfig)
client = app.test_client()
ctx = app.app_context()
ctx.push()
yield client
ctx.pop()
def confirm_bpm_login(client, group_name, username, password):
"""
POST to /confirm
"""
return client.post('/confirm', data=dict(
group_name=group_name,
username=username,
password=password,
submit=True
), follow_redirects=True)
test_auth.py
from app import db
from app.models import BPMCompany, BPMEmployee, User, BPMUser
from tests.base import client, db_data, login, confirm_bpm_login
def test_registration_page_from_confirm(client, db_data):
"""
Test registration page by HTTP GET request from "/confirm" url.
Should cause redirect to registration page.
!!! FAILING !!!
Reason: The POST to /confirm will redirect us to /register?emp_id=1,
but it will return the index.html because in the register view,
request.referrer does not recognize the POST is coming from /confirm_bpm
"""
bpm_user = BPMUser.query.filter_by(id=1).first()
rv = confirm_bpm_login(client, bpm_user.group_name,
bpm_user.user_name, 'jsmith01')
assert b'Register' in rv.data
The db_data parameter is a fixture in base.py that just populates the DB with the necessary data for the registration flow to work.
My goal is to test a complete registration flow without having to separate the confirmation and registration into two tests.
The Flask test client does not appear to add the Referer header when it follows redirects.
What you can do is implement your own version of following redirects. Maybe something like this:
def confirm_bpm_login(client, group_name, username, password):
"""
POST to /confirm
"""
response = client.post('/confirm', data=dict(
group_name=group_name,
username=username,
password=password,
submit=True
), follow_redirects=False)
if 300 <= response.status_code < 400:
response = client.get(response.headers['Location'], headers={
"Referer": 'http://localhost/confirm'
})
return response
Please test this, I wrote it from memory and may need some minor adjustments.

Passing POST call values using url_for in Flask

I am new to Flask and have been working on an existing app for its login module. It has to be delivered asap. The login credentials validates by Active Directory authentication, which is working as expected. I am referring this flask link which says
url_for('main', page=2): return the internal URL /?page=2. All optional keyword arguments are treated as GET parameters.
My index page is loaded from here (working perfectly):
#app.route("/", methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
userName = request.form['username']
pwd = request.form['password']
try:
ldapobject = auth.bind(userName,pwd)
return redirect(url_for('scheduler')) ----> NEED TO PASS USER AND PWD TO scheduler function using post here
except ldap.LDAPError, error_message:
error = 'Invalid Credentials. Please try again'
return render_template('login.html', error=error)
My scheduler function:
#app.route("/home", methods=['POST'])
def scheduler():
# some code here
# WANT TO HAVE USER CREDENTIALS IN THIS FUNCTION FROM login page
return render_template("scheduler.html", scheduler=form)
Problem: How to pass user's credentials to scheduler method as a POST call
If you want to persist user data between your application pages - you will need to store some session data on user side, e.g. cookies.
flask-login module (https://flask-login.readthedocs.org/en/) can do all the dirty work for you.
In short you need to create User class with several required fields and methods (https://flask-login.readthedocs.org/en/latest/#your-user-class) and specify several methods with decorators to load and check users that are being authenticated:
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.init_app(app=app)
#login_manager.user_loader
def load_user(username):
return User(username)
#login_manager.request_loader
def load_from_request(request):
return User.auth_is_valid(request.authorization)
After that you can mark any routes that need user credentials with #login_requied decorator and access logged in user via current_user variable:
from flask_login import login_required, current_user
#app.route("/user_needed")
#login_required
def some_function():
print(current_user)

Categories