๐Ÿ” OWASP API Security Training

Basic Authentication Implementation with Flask

๐Ÿ“š Overview

This tutorial covers the implementation of Basic Authentication in Flask-based APIs. Basic Authentication is one of the simplest authentication mechanisms where credentials are transmitted as Base64-encoded strings in the HTTP Authorization header. Understanding this method is crucial for API security testing and development.

โ„น๏ธ Important Note: This tutorial uses Flask and Flask-HTTPAuth for demonstration purposes. Ensure you have Python 3 installed before proceeding with the installation steps.

๐Ÿ› ๏ธ Prerequisites and Installation

Python Version Check

Before installing the required packages, verify your Python version:

python3 --version

Installing Required Packages

Install Flask framework (if not already installed):

pip3 install flask

Install Flask-HTTPAuth extension for authentication:

pip3 install flask-httpauth
โš ๏ธ Distribution Note: The installation command may vary depending on your Python distribution. Use pip for Python 2.x or pip3 for Python 3.x systems.

๐Ÿ’ป Code Implementation

Complete Python Code

from flask import Flask, jsonify, make_response from flask_httpauth import HTTPBasicAuth # Initialize Flask application app = Flask(__name__) # Initialize HTTP Basic Authentication auth = HTTPBasicAuth() # Define user credentials (username: password) users = { "user1": "password1", "user2": "password2" } # Password verification method @auth.verify_password def verify_password(username, password): """ Verifies if the provided username exists and if the password matches the stored credentials """ if username in users: if users[username] == password: return username return None # Protected API endpoint @app.route('/api', methods=['GET']) @auth.login_required def protected_api(): """ This endpoint requires authentication. Returns JSON data only if credentials are valid. """ return jsonify({'data': 'This is protected data', 'status': 'success'}) # Error handler for authentication failures @app.errorhandler(401) def unauthorized(error): """ Custom error response for 401 Unauthorized errors """ return make_response( jsonify({'message': 'Authentication required'}), 401, {'WWW-Authenticate': 'Basic realm="Login Required"'} ) # Run the application if __name__ == '__main__': app.run(port=5000, debug=True)

๐Ÿ” Code Breakdown and Explanation

1. Import Statements

from flask import Flask, jsonify, make_response from flask_httpauth import HTTPBasicAuth

Flask: The main web framework for building the API.
jsonify: Converts Python dictionaries to JSON responses.
make_response: Creates custom HTTP responses with specific status codes and headers.
HTTPBasicAuth: Provides Basic Authentication functionality.

2. Application Initialization

app = Flask(__name__) auth = HTTPBasicAuth()

app = Flask(__name__): Creates a Flask application instance. The __name__ variable helps Flask determine the root path of the application.
auth = HTTPBasicAuth(): Initializes the HTTP Basic Authentication object that will handle authentication logic.

3. User Credentials Storage

users = { "user1": "password1", "user2": "password2" }

This dictionary stores username-password pairs. In production environments, credentials should be stored securely using hashing algorithms (like bcrypt) and never in plain text.

๐Ÿšจ Security Warning: Storing passwords in plain text is extremely insecure and should NEVER be done in production environments. This is for educational purposes only.

4. Password Verification Method

@auth.verify_password def verify_password(username, password): if username in users: if users[username] == password: return username return None

The @auth.verify_password decorator marks this function as the password verification callback. The function:

5. Protected API Endpoint

@app.route('/api', methods=['GET']) @auth.login_required def protected_api(): return jsonify({'data': 'This is protected data', 'status': 'success'})

@app.route('/api'): Defines the URL endpoint for the API.
@auth.login_required: Decorator that enforces authentication. This triggers the verify_password method before allowing access to the endpoint.
jsonify(): Returns a JSON response with the protected data.

6. Error Handler

@app.errorhandler(401) def unauthorized(error): return make_response( jsonify({'message': 'Authentication required'}), 401, {'WWW-Authenticate': 'Basic realm="Login Required"'} )

This custom error handler intercepts 401 (Unauthorized) errors and returns a standardized JSON response with appropriate headers, including the WWW-Authenticate header that instructs the client to use Basic authentication.

๐Ÿ”„ Authentication Flow

Basic Authentication Process

1. Client sends request to /api endpoint
โ†“
2. @auth.login_required decorator intercepts
โ†“
3. verify_password() method is called
โ†“
4. Username checked in users dictionary
โ†“
5. Password verified against stored value
โ†“
โœ… Success: Return protected data
โŒ Failure: Return 401 error

๐Ÿš€ Running the Application

Start the Flask Server

python3 app.py

The application will start on port 5000. You should see output similar to:

* Running on http://127.0.0.1:5000/ * Debug mode: on
๐Ÿ“ Note: The tutorial mentions port 101, but the standard Flask development server runs on port 5000 by default. You can modify the port in the app.run() call if needed.

๐Ÿงช Testing the API

Using cURL

Test without authentication (should fail with 401):

curl http://127.0.0.1:5000/api

Test with valid credentials (user1:password1):

curl -u user1:password1 http://127.0.0.1:5000/api

Test with invalid credentials:

curl -u user1:wrongpassword http://127.0.0.1:5000/api

Using Python Requests Library

import requests from requests.auth import HTTPBasicAuth # Without authentication response = requests.get('http://127.0.0.1:5000/api') print(response.status_code) # Should return 401 # With valid authentication response = requests.get( 'http://127.0.0.1:5000/api', auth=HTTPBasicAuth('user1', 'password1') ) print(response.json()) # Should return protected data

Using Browser

Navigate to http://127.0.0.1:5000/api in your browser. You should see a login prompt requesting username and password.

๐Ÿ” Understanding Basic Authentication Headers

How Basic Auth Works

Basic Authentication encodes credentials in the HTTP Authorization header using Base64 encoding:

Step Process Example
1 Combine username and password user1:password1
2 Encode with Base64 dXNlcjE6cGFzc3dvcmQx
3 Add "Basic" prefix Basic dXNlcjE6cGFzc3dvcmQx
4 Send in Authorization header Authorization: Basic dXNlcjE6cGFzc3dvcmQx

Manual Base64 Encoding Example

import base64 credentials = "user1:password1" encoded = base64.b64encode(credentials.encode()).decode() print(f"Authorization: Basic {encoded}") # Output: Authorization: Basic dXNlcjE6cGFzc3dvcmQx

๐Ÿ›ก๏ธ Security Considerations

Critical Security Issues with Basic Authentication

  • No Encryption: Credentials are only Base64-encoded, NOT encrypted. Anyone intercepting the request can easily decode them.
  • Always Use HTTPS: Basic Auth should NEVER be used over HTTP in production. Always use HTTPS/TLS to encrypt the entire communication.
  • Credential Storage: Never store passwords in plain text. Use proper hashing algorithms like bcrypt, Argon2, or PBKDF2.
  • Session Management: Basic Auth requires credentials with every request. Consider using token-based authentication (JWT, OAuth) for better security.
  • Brute Force Protection: Implement rate limiting and account lockout mechanisms to prevent brute force attacks.

๐Ÿ“Š Response Examples

Successful Authentication Response

HTTP/1.1 200 OK Content-Type: application/json { "data": "This is protected data", "status": "success" }

Failed Authentication Response

HTTP/1.1 401 UNAUTHORIZED Content-Type: application/json WWW-Authenticate: Basic realm="Login Required" { "message": "Authentication required" }

๐Ÿ”ง Advanced Modifications

Adding Password Hashing

from werkzeug.security import generate_password_hash, check_password_hash # Store hashed passwords users = { "user1": generate_password_hash("password1"), "user2": generate_password_hash("password2") } @auth.verify_password def verify_password(username, password): if username in users: if check_password_hash(users[username], password): return username return None

Custom Port Configuration

To run on a different port (e.g., port 8080):

python3 app.py

Modify the code:

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

๐Ÿ“š Key Takeaways

๐ŸŽฏ Common Use Cases

Scenario Appropriate Reason
Internal APIs โœ… Yes (with HTTPS) Simple authentication for trusted environments
Public APIs โŒ No Requires more robust authentication mechanisms
Development/Testing โœ… Yes Quick setup for testing authentication flows
Production (over HTTP) โŒ Never Credentials sent in clear text
Production (over HTTPS) โš ๏ธ Maybe Consider OAuth 2.0 or JWT for better security

๐Ÿ› Troubleshooting

Common Issues and Solutions

Issue: "ModuleNotFoundError: No module named 'flask'"

pip3 install flask

Issue: "ModuleNotFoundError: No module named 'flask_httpauth'"

pip3 install flask-httpauth

Issue: Port already in use

Change the port in your code or kill the process using the port:

lsof -ti:5000 | xargs kill -9

Issue: 401 error even with correct credentials

Check that: