Auth Flask
Auth Flask
Table of Contents
my_auth_app/
├── venv/ # Python virtual environment
├── app.py # Main Flask application file
├── config.py # Configuration settings
├── models.py # Database models
├── forms.py # Web forms (using Flask-WTF)
├── templates/ # HTML templates
│ ├── base.html
│ ├── register.html
│ └── login.html
├── static/ # Static files (CSS, JS, images)
│ └── css/
│ └── style.css
└── requirements.txt # Python dependencies
my_auth_app/config.py:
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or
'a_very_secret_key_that_should_be_changed_in_production'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or
'sqlite:///site.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY=your_actual_strong_secret_key_here
DATABASE_URL=sqlite:///site.db
my_auth_app/app.py:
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login' # The view Flask-Login should redirect
to for login
@app.route('/')
@app.route('/home')
def home():
return render_template('home.html')
if __name__ == '__main__':
# Create database tables if they don't exist
with app.app_context():
db.create_all()
app.run(debug=True)
my_auth_app/templates/base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask Auth App</title>
<link rel="stylesheet" href="{{ url_for('static',
filename='css/style.css') }}">
</head>
<body>
<header>
<nav>
<a href="{{ url_for('home') }}">Home</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('dashboard') }}">Dashboard</a>
<a href="{{ url_for('logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('login') }}">Login</a>
<a href="{{ url_for('register') }}">Register</a>
{% endif %}
</nav>
</header>
<main>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<li class="{{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2023 Flask Auth App</p>
</footer>
</body>
</html>
my_auth_app/templates/home.html:
{% extends "base.html" %}
{% block content %}
<h2>Welcome to the Flask Authentication App!</h2>
{% if current_user.is_authenticated %}
<p>Hello, {{ current_user.username }}! You are logged in.</p>
<p><a href="{{ url_for('dashboard') }}">Go to your
dashboard.</a></p>
{% else %}
<p>Please <a href="{{ url_for('register') }}">register</a> or <a
href="{{ url_for('login') }}">login</a> to continue.</p>
{% endif %}
{% endblock %}
my_auth_app/static/css/style.css:
body {
font-family: sans-serif;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
header {
background-color: #333;
color: white;
padding: 10px 20px;
border-radius: 8px;
margin-bottom: 20px;
}
nav a {
color: white;
margin-right: 15px;
text-decoration: none;
}
nav a:hover {
text-decoration: underline;
}
main {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
form {
margin-top: 20px;
}
form div {
margin-bottom: 15px;
}
form label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
form input[type="text"],
form input[type="password"],
form input[type="email"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box; /* Include padding in width */
}
form input[type="submit"] {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
form input[type="submit"]:hover {
background-color: #0056b3;
}
.flashes {
list-style: none;
padding: 0;
margin: 0 0 20px 0;
}
.flashes li {
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
}
.flashes .success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.flashes .error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.flashes .info {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
Your User model will represent users in your database. It needs specific fields for
authentication.
my_auth_app/models.py:
def __repr__(self):
return f"User('{self.username}', '{self.email}')"
Flask-Login handles the common tasks of logging in, logging out, and remembering users'
sessions.
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login' # Redirects to 'login' route if
@login_required is used on an unauthenticated user
# ...
5.3 User Loader Function
Flask-Login needs a "user loader" function that tells it how to load a user from the database
given their ID.
# ...
@login_manager.user_loader
def load_user(user_id):
"""
Given a user ID, return the corresponding User object.
This is used by Flask-Login to reload the user object from the session.
"""
return User.query.get(int(user_id))
my_auth_app/forms.py:
class RegistrationForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2,
max=20)])
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(),
Length(min=6)])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(),
EqualTo('password')])
submit = SubmitField('Sign Up')
The registration route will handle displaying the form and processing its submission.
form = RegistrationForm()
if form.validate_on_submit(): # If form is submitted and passes
validation
hashed_password = generate_password_hash(form.password.data)
user = User(username=form.username.data, email=form.email.data,
password_hash=hashed_password)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You are now able to log in',
'success')
return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form)
my_auth_app/templates/register.html:
{% extends "base.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }} {# Renders CSRF token #}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Join Today</legend>
<div class="form-group">
{{ form.username.label }}
{{ form.username(class="form-control") }}
{% if form.username.errors %}
<ul class="errors">
{% for error in form.username.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-group">
{{ form.email.label }}
{{ form.email(class="form-control") }}
{% if form.email.errors %}
<ul class="errors">
{% for error in form.email.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-group">
{{ form.password.label }}
{{ form.password(class="form-control") }}
{% if form.password.errors %}
<ul class="errors">
{% for error in form.password.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-group">
{{ form.confirm_password.label }}
{{ form.confirm_password(class="form-control") }}
{% if form.confirm_password.errors %}
<ul class="errors">
{% for error in form.confirm_password.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
{% endblock %}
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me') # For persistent sessions
submit = SubmitField('Login')
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and user.check_password(form.password.data):
login_user(user, remember=form.remember.data) # Log user in
with Flask-Login
next_page = request.args.get('next') # Redirect to 'next' page
if user was trying to access a protected route
flash('Login successful!', 'success')
return redirect(next_page or url_for('home'))
else:
flash('Login Unsuccessful. Please check email and password',
'error')
return render_template('login.html', title='Login', form=form)
@app.route('/logout')
@login_required # User must be logged in to log out
def logout():
logout_user() # Log user out with Flask-Login
flash('You have been logged out.', 'info')
return redirect(url_for('home'))
my_auth_app/templates/login.html:
{% extends "base.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Log In</legend>
<div class="form-group">
{{ form.email.label }}
{{ form.email(class="form-control") }}
{% if form.email.errors %}
<ul class="errors">
{% for error in form.email.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-group">
{{ form.password.label }}
{{ form.password(class="form-control") }}
{% if form.password.errors %}
<ul class="errors">
{% for error in form.password.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-check">
{{ form.remember(class="form-check-input") }}
{{ form.remember.label(class="form-check-label") }}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
{% endblock %}
@app.route('/dashboard')
@login_required # This route requires the user to be logged in
def dashboard():
return render_template('dashboard.html', title='Dashboard')
my_auth_app/templates/dashboard.html:
{% extends "base.html" %}
{% block content %}
<h2>Welcome to your Dashboard, {{ current_user.username }}!</h2>
<p>This is a protected page. Only logged-in users can see this
content.</p>
<p>Your email: {{ current_user.email }}</p>
{% endblock %}
Task: Extend the authentication system to create a very basic blog where:
Steps:
1. Update models.py: Add a Post model with title, content, and a ForeignKey to
User (for the author).
2. Update app.py:
o Create a create_post route (GET and POST) that's @login_required.
o Create a posts route (GET) to list all posts.
o Create a post_detail route (GET) to show a single post.
3. Create Forms: Add a PostForm in forms.py for creating posts (title, content).
4. Create Templates:
o create_post.html (for the form).
o posts.html (to list all posts).
o post_detail.html (to show a single post).
5. Update base.html: Add navigation links for "Posts" and "Create Post" (conditionally
for logged-in users).
6. Run Migrations: After updating models.py, run python manage.py
makemigrations and python manage.py migrate.
11. Assignment Solution (Conceptual)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
class PostForm(FlaskForm):
title = StringField('Title', validators=[DataRequired(),
Length(max=100)])
content = TextAreaField('Content', validators=[DataRequired()])
submit = SubmitField('Post')
@app.route('/posts')
def posts():
posts = Post.query.order_by(Post.date_posted.desc()).all() # Fetch all
posts, ordered by date
return render_template('posts.html', posts=posts)
@app.route('/post/<int:post_id>')
def post_detail(post_id):
post = Post.query.get_or_404(post_id) # Get post by ID or return 404
return render_template('post_detail.html', title=post.title, post=post)
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
4. my_auth_app/templates/create_post.html:
{% extends "base.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Create New Post</legend>
<div class="form-group">
{{ form.title.label }}
{{ form.title(class="form-control") }}
{% if form.title.errors %}
<ul class="errors">
{% for error in form.title.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-group">
{{ form.content.label }}
{{ form.content(class="form-control") }}
{% if form.content.errors %}
<ul class="errors">
{% for error in form.content.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
{% endblock %}
5. my_auth_app/templates/posts.html:
{% extends "base.html" %}
{% block content %}
<h2>All Blog Posts</h2>
{% for post in posts %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="#">{{ post.author.username }}</a>
<small class="text-muted">{{
post.date_posted.strftime('%Y-%m-%d') }}</small>
</div>
<h2><a class="article-title" href="{{
url_for('post_detail', post_id=post.id) }}">{{ post.title }}</a></h2>
<p class="article-content">{{ post.content[:200] }}...</p>
{# Show first 200 chars #}
</div>
</article>
{% endfor %}
{% endblock %}
6. my_auth_app/templates/post_detail.html:
{% extends "base.html" %}
{% block content %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="#">{{ post.author.username }}</a>
<small class="text-muted">{{ post.date_posted.strftime('%Y-
%m-%d') }}</small>
</div>
<h2 class="article-title">{{ post.title }}</h2>
<p class="article-content">{{ post.content }}</p>
</div>
</article>
{% endblock %}
This structured approach will guide you through building a secure and functional
authentication system in Flask, along with a basic application to demonstrate its usage.