TIL Python Basics Day 68 - Authentication with Flask


Figure out how to register, login and logout users with email and password. So they can access their own private profile pages.

Send from directory


The send_from_directory function is the recommended secure way to allow a user to download a file from our application.
Docs
Helpful Article
@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')

Authentication


Why Authenticate?: to associate data with individual users.
  • Access to private contents like messages
  • Restrict Acess depending on user's status: paid membership
  • * 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
    

    Hash Function


    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.

    How to Hack Password 101


    'Have been pwned?'
    Same password -> Same Hash
    Generating hashes with GPU

    Hash Table Cracks


    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.

    Dictionary Attack


    don’t use dictionary words

    Salting Passwords


    * 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

    Bcrypt Hashes


    Industry standard password-hashing function

    Salt Rounds


    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.

    1. Hashing Passwords using Werkzeug

    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

    2. Authenticating Users with Flask-Login


    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

    1. configure Flask app to use Flask_Login

    login_manager = LoginManager()
    login_manager.init_app(app)

    2. create a user_loader function.

    @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.

    3. Implement the UserMixin in User class.

    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.

    4. check_password_hash function.


    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)      

    5. Find the user by the email


    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()

    6. login_user(user) function to authenticate them.


    function creates the cookcie . session tells flask that user is logged in

    7. Login_required



    -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>

    8. Log out


    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'))

    Flask Flash messages


    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 %}

    Passing Authentication Status to Templates


    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