Building a Simple Login System with Flask

Welcome, aspiring web developers! Today, we’re going to embark on an exciting journey to build a fundamental component of almost every web application: a login system. We’ll be using Flask, a super friendly and lightweight web framework for Python, which is perfect for beginners to get started with web development.

A login system allows users to identify themselves to your application, usually by providing a username and password. Once logged in, the application can remember who they are and offer personalized content or restrict access to certain features.

Why Flask?

Flask is often called a “micro-framework” because it keeps things simple and gives you a lot of flexibility. It doesn’t force you to use specific tools or libraries, which makes it easy to learn and get a basic application up and running quickly. If you’re new to web development, Flask is an excellent choice to understand the core concepts without getting overwhelmed.

Prerequisites

Before we start coding, make sure you have the following installed on your computer:

  • Python 3: The programming language we’ll be using. You can download it from python.org.
  • pip: Python’s package installer. It usually comes bundled with Python. We’ll use it to install Flask.

That’s it! Let’s get our hands dirty.

Setting Up Your Project

First, let’s create a dedicated folder for our project and set up a “virtual environment.”

What is a Virtual Environment?

Imagine you’re working on multiple Python projects, and each project needs different versions of the same library. A virtual environment creates an isolated space for each project, ensuring that the libraries installed for one project don’t conflict with another. It’s like having separate toolboxes for different jobs.

  1. Create a Project Folder:
    Open your terminal or command prompt and create a new directory:
    bash
    mkdir flask_login_app
    cd flask_login_app

  2. Create a Virtual Environment:
    Inside your flask_login_app folder, run:
    bash
    python -m venv venv

    This creates a folder named venv which contains our isolated Python environment.

  3. Activate the Virtual Environment:

    • On macOS/Linux:
      bash
      source venv/bin/activate
    • On Windows:
      bash
      venv\Scripts\activate

      You’ll notice (venv) appears at the beginning of your terminal prompt, indicating that the virtual environment is active.
  4. Install Flask:
    With your virtual environment active, install Flask using pip:
    bash
    pip install Flask

Building the Basic Flask Application

Now that our environment is ready, let’s create our main application file.

  1. Create app.py:
    In your flask_login_app folder, create a new file named app.py. This will contain all our Flask code.

  2. Basic Flask Structure:
    Open app.py and add the following code:

    “`python
    from flask import Flask, render_template, request, redirect, url_for, flash, session

    app = Flask(name)
    app.secret_key = ‘your_secret_key_here’ # IMPORTANT: Change this in a real app!

    A simple home page

    @app.route(‘/’)
    def home():
    return “Hello, welcome to our simple login system!”

    if name == ‘main‘:
    app.run(debug=True)
    “`

    Let’s break down what’s happening here:
    * from flask import ...: This line imports necessary tools from the Flask library.
    * Flask: The main class for our web application.
    * render_template: A function to display HTML files.
    * request: An object that holds information about incoming web requests (like form submissions).
    * redirect: A function to send the user to a different URL.
    * url_for: A function to generate URLs based on function names.
    * flash: A way to show one-time messages to the user (e.g., “Login successful!”).
    * session: A special dictionary to store data specific to a user’s visit to your website. We’ll use this to keep track of whether a user is logged in.
    * app = Flask(__name__): This creates our Flask application instance.
    * app.secret_key = '...': This is crucial for security, especially when using sessions. Flask uses this key to securely sign session cookies. In a real application, make this a long, random, and hard-to-guess string!
    * @app.route('/'): This is called a decorator. It tells Flask that when someone visits the root URL (/) of our website, it should run the home() function right below it. The URL path is also known as a route.
    * if __name__ == '__main__': app.run(debug=True): This standard Python idiom ensures that our application runs only when the script is executed directly. debug=True is helpful during development as it automatically reloads the server on code changes and provides a debugger for errors. Never use debug=True in a production environment!

Running the App (First Test)

Save app.py and run it from your terminal (make sure your virtual environment is active):

python app.py

You should see output similar to * Running on http://127.0.0.1:5000/. Open your web browser and navigate to http://127.0.0.1:5000/. You should see “Hello, welcome to our simple login system!”.

Creating HTML Templates

Flask uses a template engine called Jinja2 to display dynamic web pages. This means we can write HTML files with special placeholders that Flask will fill with data from our Python code.

  1. Create a templates Folder:
    In your flask_login_app directory, create a new folder named templates. Flask automatically looks for HTML files in this folder.
    bash
    mkdir templates

  2. Create login.html:
    Inside the templates folder, create login.html:

    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
    <style>
    body { font-family: sans-serif; margin: 2em; background-color: #f4f4f4; }
    .container { max-width: 400px; margin: auto; padding: 20px; border: 1px solid #ddd; background-color: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
    h2 { text-align: center; color: #333; }
    form div { margin-bottom: 1em; }
    label { display: block; margin-bottom: 0.5em; color: #555; }
    input[type="text"], input[type="password"] { width: calc(100% - 20px); padding: 10px; border: 1px solid #ccc; border-radius: 4px; }
    input[type="submit"] { width: 100%; padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1em; }
    input[type="submit"]:hover { background-color: #0056b3; }
    .flash-message { padding: 10px; margin-bottom: 1em; border-radius: 4px; }
    .flash-message.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
    .flash-message.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
    </style>
    </head>
    <body>
    <div class="container">
    <h2>Login</h2>
    {% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
    {% for category, message in messages %}
    <div class="flash-message {{ category }}">{{ message }}</div>
    {% endfor %}
    {% endif %}
    {% endwith %}
    <form action="{{ url_for('login') }}" method="post">
    <div>
    <label for="username">Username:</label>
    <input type="text" id="username" name="username" required>
    </div>
    <div>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password" required>
    </div>
    <div>
    <input type="submit" value="Login">
    </div>
    </form>
    </div>
    </body>
    </html>

    * {{ url_for('login') }}: This is Jinja2 syntax. It tells Flask to generate the URL for the login function we’ll define in app.py. This is better than hardcoding URLs, as Flask can change them if needed.
    * method="post": This means when the form is submitted, the data will be sent using a POST request.

  3. Create dashboard.html:
    Inside the templates folder, create dashboard.html:

    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard</title>
    <style>
    body { font-family: sans-serif; margin: 2em; background-color: #f4f4f4; }
    .container { max-width: 600px; margin: auto; padding: 20px; border: 1px solid #ddd; background-color: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
    h2 { text-align: center; color: #333; }
    p { text-align: center; }
    .logout-button { display: block; width: 100px; margin: 20px auto; padding: 10px; background-color: #dc3545; color: white; border: none; border-radius: 4px; text-align: center; text-decoration: none; }
    .logout-button:hover { background-color: #c82333; }
    .flash-message { padding: 10px; margin-bottom: 1em; border-radius: 4px; }
    .flash-message.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
    </style>
    </head>
    <body>
    <div class="container">
    <h2>Welcome to your Dashboard!</h2>
    {% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
    {% for category, message in messages %}
    <div class="flash-message {{ category }}">{{ message }}</div>
    {% endfor %}
    {% endif %}
    {% endwith %}
    <p>You are successfully logged in.</p>
    <p>This is your personalized content.</p>
    <a href="{{ url_for('logout') }}" class="logout-button">Logout</a>
    </div>
    </body>
    </html>

Implementing Login Logic

Now let’s add the login and logout functionality to our app.py.

What are GET and POST Requests?

When you type a URL in your browser and press Enter, your browser sends a GET request to the server. This request asks the server to get information (like a web page).

When you fill out a form and click submit, the browser usually sends a POST request. This request posts (sends) data to the server, often to create or update something. Our login form will use a POST request to send the username and password.

Update your app.py with the following code. We’ll replace the simple home route and add login, dashboard, and logout routes.

from flask import Flask, render_template, request, redirect, url_for, flash, session

app = Flask(__name__)
app.secret_key = 'a_very_secret_and_long_random_string_replace_me' # Replace with a strong, unique key!

USERS = {
    "user1": "pass123",
    "admin": "adminpass"
}

@app.route('/')
def home():
    if 'logged_in' in session and session['logged_in']:
        return redirect(url_for('dashboard'))
    return redirect(url_for('login'))

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username'] # Get data from the form
        password = request.form['password']

        if username in USERS and USERS[username] == password:
            session['logged_in'] = True
            session['username'] = username # Store username in session
            flash('Logged in successfully!', 'success')
            return redirect(url_for('dashboard'))
        else:
            flash('Invalid username or password. Please try again.', 'error')
            # You can also use return redirect(url_for('login')) here
            # but for displaying flash messages on the same page, rendering the template is better.
    return render_template('login.html')

@app.route('/dashboard')
def dashboard():
    # Check if the user is logged in
    if 'logged_in' in session and session['logged_in']:
        return render_template('dashboard.html', username=session['username'])
    else:
        flash('Please log in to access the dashboard.', 'error')
        return redirect(url_for('login'))

@app.route('/logout')
def logout():
    session.pop('logged_in', None) # Remove 'logged_in' from session
    session.pop('username', None) # Remove username from session
    flash('You have been logged out.', 'success')
    return redirect(url_for('login'))

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

Explanations for New Code:

  • app.secret_key: As mentioned, this is essential for session security. Flask uses it to encrypt/sign the data stored in the user’s session cookie.
  • USERS dictionary: This is a very simple way to store usernames and passwords for demonstration. NEVER do this in a real application! Real applications use databases and securely hash passwords.
  • @app.route('/login', methods=['GET', 'POST']): This route now accepts both GET and POST requests.
    • When you first visit /login in your browser, it’s a GET request, and the login.html template is displayed.
    • When you fill out the form and click “Login”, it’s a POST request.
  • if request.method == 'POST':: This checks if the incoming request is a POST request (i.e., a form submission).
  • username = request.form['username']: The request.form dictionary contains the data submitted from the HTML form. We access the values using the name attributes of the input fields (name="username", name="password").
  • session['logged_in'] = True: This is where session management comes in.
    • What is a Session? A session is a way for a web server to store information about a specific user across multiple requests. When a user logs in, the server creates a unique session for them. This session ID is usually stored in a cookie in the user’s browser. When the user makes another request, the browser sends this cookie, allowing the server to retrieve their session data (e.g., logged_in: True).
    • By setting session['logged_in'] = True, we’re essentially putting a flag in the user’s session data that says “this user is authenticated.”
  • flash('message', 'category'): The flash function allows you to store a message that will be displayed on the next request. This is great for showing “Login successful!” or “Invalid credentials!” messages. The ‘category’ helps us style the message (e.g., ‘success’ or ‘error’).
  • redirect(url_for('dashboard')): After a successful login, we use redirect to send the user’s browser to the /dashboard URL. url_for('dashboard') dynamically generates the URL for the dashboard function.
  • session.pop('logged_in', None): In the logout function, session.pop() removes the logged_in key from the session, effectively logging the user out. None is provided as a default value if the key isn’t found, preventing an error.

Running the Complete Application

  1. Save all files: Make sure app.py, login.html, and dashboard.html are saved in their correct locations.
  2. Activate your virtual environment (if not already):
    • source venv/bin/activate (macOS/Linux)
    • venv\Scripts\activate (Windows)
  3. Run app.py:
    bash
    python app.py
  4. Open your browser: Go to http://127.0.0.1:5000/. You should be redirected to the login page.
  5. Test the login:
    • Try user1 / pass123.
    • Try admin / adminpass.
    • Try incorrect credentials to see the error message.
  6. Test the logout:
    • Click the “Logout” button on the dashboard.

Congratulations! You’ve successfully built a simple login and logout system using Flask!

Next Steps and Improvements

This simple system is a great starting point, but a real-world application would need more robust features:

  • Database Integration: Instead of a hardcoded dictionary, users and their (hashed!) passwords would be stored in a database (like SQLite, PostgreSQL, or MySQL) using an ORM like SQLAlchemy.
  • Password Hashing: Crucially, never store passwords directly! Always hash them using a strong hashing algorithm (like bcrypt) before storing them in a database.
  • User Registration: Allow new users to create accounts.
  • Form Validation: Add checks to ensure users enter valid data (e.g., strong passwords, valid email formats).
  • Security Measures: Implement measures against common web vulnerabilities like CSRF (Cross-Site Request Forgery) and XSS (Cross-Site Scripting). Flask-WTF is a popular extension for handling forms and security.
  • More Advanced Authentication: For larger applications, consider extensions like Flask-Login for handling user sessions and authentication more formally.
  • User Roles: Differentiate between different types of users (e.g., admin, regular user) and control access based on their roles.

Keep experimenting, keep learning, and happy coding!

Comments

Leave a Reply