Figure out how to register, login and logout users with email and password. So they can access their own private profile pages.
The send_from_directory function is the recommended secure way to allow a user to download a file from our application.
@app.route("/get-image/<image_name>")
def get_image(image_name):
try:
return send_from_directory(app.config["CLIENT_IMAGES"],
filename=image_name, as_attachment=True)
except FileNotFoundError:
abort(404)
-app.config["CLIENT_IMAGES"] - The path to the directory containing the images we're allowing our users to download
-filename=image_name - The image_name variable passed in from the URL
-as_attachment=True - Allows the client to download the file as an attachment
-send_from_directory is then returned
@app.route('/download')
def download():
return send_from_directory('static',
filename='files/cheat_sheet.pdf')
Why Authenticate?: to associate data with individual users.
* Level 1 Encryption: store password into DB.
* Level 2 Authentication: Use of encryption
Scrambling it to be unable to decode data without anigma machine/ key
Caesar Cipher: early form of encryption
A way of scrambling message
Key to unscramble the msg
* Level 3 Hashing:
Password + Key -> Ciphertext
Method
Not using Key
We don’t decrypt the password
Password turned into Hash
Hash doesn’t go backward into original password.
377/1 = 13*29
13*29
Calculated very fast, but doesn’t go backward
Store hash: When try to login again, send Hash to the DB, DB compare it to stored Hash.
If those two matches, they are same.
In that way, only users know their password.
'Have been pwned?'
Same password -> Same Hash
Generating hashes with GPU
Pre-built hash table: Search by the hash value of common password
Longer the password, harder to crack: Time to crack exponentially increase.
That's why include all Uppercase, Lowercase, Numbers, Symbols over 10 characters.
don’t use dictionary words
* Level 4 : Hashing & Salting
In addition to the password + random set of character (SALT) increases number of character.
Salt it stored in the DB along with Hash
Combine with PW+Salt
We only store Salt and Hash
MD5 is less secure
Industry standard password-hashing function
As computers get faster, almost doubles the computing power.
You can increse the rounds exponentially. It gets securer.
Round 1
PW + SALT => Hash1
(Random set of characters)
Round 2
Hash1 + SALT => Hash2
Round 3
Hash2 + SALT => Hash3
...
DB
User/ Salt/ Hash x 10(rounds)
*level 5: Cookies and Sessions
Cookie
-Amazon stores the cookies, session contains id number to be used to fetch all data. If we delete the cookie, we won't be able to see the item in the cart.
-At Amazon, Adding an item in a shopping cart means "Browser POST request to server; I added item to my cart." In response, server creates the cookies containing the data and send it to browser, telling save the coockie in. When browser(user)comeback to the website, browser make GET request, sending along the cookie to server. Server responses by sending html, css, js and cookie.
There are lots of different type of cookies.
Session: a period of time when a browser interacts with a server. When logged in, cookie is created and expires after a certain period of time.
generate_password_hash()
hash_and_salted_password = generate_password_hash(
request.form.get('password'),
method='pbkdf2:sha256',
salt_length=8
)
new_user = User(
email=request.form.get('email'),
name=request.form.get('name'),
password=hash_and_salted_password,
)
Security Helpers
Docs
Flask-Login package provides user session management for Flask. It handles the common tasks of logging in, logging out, and remembering your users’ sessions over extended periods of time.
Youtube tutorial by pretty print
login_manager = LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
"""to associate the coockie with user object"""
return User.query.get(int(user_id))
-user_loader: How Flask_Login creates the connection btw cookie and data in DB.
-flask login uses user_loader to find whose session is currently active. Flask login will create a cookie when the used is logged in and inside the cookie, will be the 'user id'.
Anytime user performs a request on your app it sends the cookie along with that. Flask looks for the user id and usees user_loader to actually find the user in DB.
from flask_login import UserMixin
class User(UserMixin, db.Model):
-Without UserMinxin, Flask_Login cannot read or modify User class.
-UserMixin adds some attritubes to User class to help Flask_login actually use your User Class.
Check stored password hash against entered password hashed.
#(pw from DB, user typed pw-plain text)
#1 check only password
password = request.form.get('password')
if check_password_hash(user.password, password):
login_user(user)
#2 check both email in DB and password
email = request.form.get('email')
password = request.form.get('password')
user = User.query.filter_by(email=email).first()
if not user and not check_password_hash(user.password, password):
return redirect(url_for("login"))
login_user(user)
When try to register but user email already on DB, redirect to login
When try to login, find the user by email
email = request.form.get('email')
user = User.query.filter_by(email=email).first()
function creates the cookcie . session tells flask that user is logged in
-login_required secure the page, only logged in user can view the page.
-current_user object represents user object.
e.g. Secure the /secrets and /download route
@app.route('/secrets')
@login_required
def secrets():
return render_template("secrets.html", name=current_user.name)
secrets.html
<h1 class="title">Welcome, {{ name }} </h1>
Users aren't logged in shouldn't do logout, so login_required
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index.html'))
Give user a feedback
e.g. Was there an issue with login in? Are they typing in the wrong password or does their email not exist?
-They are messages that get sent to the template to be rendered just once. And they disappear when the page is reloaded.
When tried to register with existing email at register page, redirects to login and show flash saying Email already esits.(so try loggingin)
main.py
if user:
flash("Email address already exists. ")
return redirect(url_for('login'))
login.html
<h1>Login</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
{% endif %}
{% endwith %}
nav bar -> base.html
{% if not current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('home') }}">Home</a>
</li>
{% if not current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('login') }}">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('register') }}">Register</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">Log Out</a>
</li>
base.html changes after logging in