Day 68 - Authentication & Flask Login & Werkzeug


Posted by pei_______ on 2022-06-23

learning from 100 Days of Code: The Complete Python Pro Bootcamp for 2022


Flask Login - Documentation
Werkzeug - Documentation


Learning Point - Flask & Flask login


Point 1. Flask - return / download files

from flask import send_from_directory

@app.route('/download')
def download():
    filename = request.args.get('filename')
    return send_from_directory('static/files/',
                               filename, as_attachment=True)

Point 2. Multiple inheritance and mixin classes in Python

Point 3. Login required

Point 4. Flashing Message

Point 5. Template Inheritance

notes in project


Learning Point - Werkzeug


Point 1. Hash & Salt the password

from werkzeug.security import generate_password_hash

hash_and_salted_psw = generate_password_hash(
    request.form['password'], 
    method='pbkdf2:sha256', 
    salt_length=8
    )

> method$salt$hash

Point 2. Check the password

from werkzeug.security import check_password_hash

password = request.form['password']
check_password_hash(% HASH PASSWORD %, password)
> True / False

Authentication Project


main.py

from flask import Flask, render_template, request, url_for, redirect, flash, send_from_directory
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin, login_user, LoginManager, login_required, current_user, logout_user

app = Flask(__name__)

app.config['SECRET_KEY'] = 'any-secret-key-you-choose'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

# ---------- 01. set the login manager in whole app---------- #

login_manager = LoginManager()
login_manager.init_app(app)


# ---------- 02. set the user class with UserMixin ---------- #

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(100), unique=True, nullable=False)
    password = db.Column(db.String(100), nullable=False)
    name = db.Column(db.String(1000), nullable=False)


# Line below only required once, when creating DB.
# db.create_all()


# ---------- 03. load user ,AUTO check for current user ---------- #

@login_manager.user_loader
def load_user(user_id):
    # if user not valid, return None
    return User.query.get(user_id)


# ---------- 04. check current user's status ---------- #

@app.route('/')
def home():
    # if not logged in, current user = <User 3>
    # instead, current user = <flask_login.mixins.AnonymousUserMixin object at % Space %>
    return render_template("index.html", logged_in=current_user.is_authenticated)


@app.route('/register', methods=["GET", "POST"])
def register():
    if request.method == "POST":
        email = request.form['email']

        if User.query.filter_by(email=email).first() is not None:

            # ---------- 05. Flag: show a msg at once ---------- #

            flash("You've already sign up with the Email. Please log in instead.")
            return render_template("register.html")

        # ---------- 06. Werkzeug.security: hashing & salting ---------- #

        hash_and_salted_psw = generate_password_hash(
            request.form['password'],
            method='pbkdf2:sha256',
            salt_length=8
        )

        new_user = User(
            email=email,
            password=hash_and_salted_psw,
            name=request.form['name']

        )
        db.session.add(new_user)
        db.session.commit()

        # ---------- 07. Log in ---------- #

        login_user(new_user)

        return render_template("secrets.html", name=request.form['name'])
    return render_template("register.html")


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        email = request.form['email']
        password = request.form['password']
        user = User.query.filter_by(email=email).first()

        if user is not None:

            # ---------- 08. Werkzeug.security: check password ---------- #

            if check_password_hash(user.password, password):
                login_user(user, remember=True)

                # ---------- 09. Different from redirect & render_template ---------- #
                # redirect: switch to other pages (function)
                # render_template: show the 'html' with in the same url

                return redirect(url_for('secrets'))
            else:
                flash('Password incorrect. Please try again.')
                return render_template("login.html")
        else:
            flash('User is not exist. Please try again.')
            return render_template("login.html")

    return render_template("login.html")


# ---------- 10. Login required ---------- #

@app.route('/secrets')
@login_required
def secrets():
    return render_template("secrets.html", name=current_user.name,
                           logged_in=current_user.is_authenticated)


@app.route('/logout')
@login_required
def logout():
    # ---------- 11. Log out ---------- #

    logout_user()
    return redirect(url_for('home'))


@app.route('/download')
@login_required
def download():
    filename = request.args.get('filename')
    return send_from_directory('static/files/', filename, as_attachment=True)


if __name__ == "__main__":
    app.run(debug=True)

index.html

{% extends "base.html" %}
{{ super() }}
{% block content %}

<div class="box">
    <h1>Flask Authentication</h1>

    <!-- 04. check current user's status -->

    {% if not logged_in: %}

    <a href="{{ url_for('login') }}" class="btn btn-primary btn-block btn-large">Login</a>
    <a href="{{ url_for('register') }}" class="btn btn-secondary btn-block btn-large">Register</a>

    {% endif %}

</div>

{% endblock %}

login.html

{% extends "base.html" %}
{% block content %}

<div class="box">
    <h1>Login</h1>

    <form action="{{ url_for('login') }}" method="post">

<!-- 05. Flag: show a msg at once -->

        {% with messages = get_flashed_messages() %}
        {% if messages %}

            {% for message in messages %}
            <p style="color:red;">{{ message }}</p>
            {% endfor %}

        {% endif %}
        {% endwith %}

        <input type="text" name="email" placeholder="Email" required="required"/>
        <input type="password" name="password" placeholder="Password" required="required"/>
        <button type="submit" class="btn btn-primary btn-block btn-large">Let me in.</button>
    </form>
</div>

{% endblock %}

完整語法詳見GitHub


#Python #課堂筆記 #100 Days of Code







Related Posts

Web Component 實戰

Web Component 實戰

Inheritance

Inheritance

用 Nest.js 開發 API 吧 (六) - TypeORM

用 Nest.js 開發 API 吧 (六) - TypeORM


Comments