+1 (315) 557-6473 

Building an HTTP Server with User Authentication and File Access

June 24, 2024
John Doe
John Doe
United States
Web Development
John Doe, Experienced in HTML, CSS, JavaScript, and backend technologies. Skilled in frontend frameworks, database management, and responsive design. Provides detailed assignment solutions, code reviews, and tutorials. Strong problem-solving skills and excellent communicator. Passionate about helping students excel in web development. Certified Web Developer (CIW).

Programming assignments can be challenging, especially when they require creating complex systems such as an HTTP server with user authentication and file access. However, breaking down the problem into manageable steps can make the task much more approachable. In this blog, we'll provide a comprehensive guide to help you solve your web development assignments, with a particular focus on understanding the requirements, planning your solution, implementing the server, and testing it thoroughly.

Understanding the Assignment Requirements

Before diving into coding, it's crucial to thoroughly understand the project requirements. This ensures that you know what the final product should accomplish and how it should behave. Let's break down a typical assignment that involves creating an HTTP server with user authentication and file access.

User Authentication

User authentication is a fundamental aspect of any secure system. Here are the key requirements for user authentication:

Handling Login Requests

Mastering Programming Assignments

Users should be able to log in using a username and password. The server will handle these login requests through POST requests. Here's a detailed look at the process:

  1. Receiving Login Requests: The server should listen for POST requests directed to the root URL ("/"). These requests will contain the username and password in the headers.
  2. Validating Credentials: The server will check the provided credentials against a stored list of user accounts. Passwords are stored as SHA-256 salted-hashed values to enhance security.
  3. Creating Sessions: Upon successful authentication, the server will create a session for the user, tracked using a cookie with a session ID.

Storing Passwords Securely

Storing passwords securely is critical to prevent unauthorized access. The server should use SHA-256 hashing with a salt for each password. This adds an extra layer of security, making it more difficult for attackers to guess passwords through dictionary attacks.

Managing Sessions

User sessions are managed using cookies. Each session has a unique session ID and a timeout period. The server should track these sessions and ensure they expire after the specified timeout to maintain security.

File Access

Once authenticated, users should be able to access specific files. Here’s how file access is typically handled:

Directory Restrictions

Authenticated users should only have access to files within their designated directory. This ensures that users cannot access files that do not belong to them.

Handling File Requests

File requests are handled using GET requests. The server should verify the user's session before allowing access to any files. Unauthorized access should be denied.

Logging Access Attempts

The server should log all file access attempts, successful or unsuccessful. This logging helps in monitoring user activity and detecting any potential security breaches.

Session Management

Session management is crucial for maintaining the security and usability of the server. Here’s what effective session management entails:

Generating Session IDs

Each session should be tracked using a randomly generated session ID, stored as a cookie. This session ID is used to identify the user and manage their session.

Tracking Session Data

The server should maintain a dictionary mapping session IDs to usernames and login times. This dictionary helps track active sessions and ensures users remain authenticated.

Expiring Sessions

Sessions should expire after a configurable timeout period. This means that if a user is inactive for too long, their session will end, and they will need to log in again.

Planning Your Solution

Once you understand the requirements, the next step is to plan your solution. Planning involves deciding on the structure and flow of your code. Here’s a high-level plan to guide you through the process.

Setting Up the Server

The first step is to set up the server to listen for incoming connections. This involves parsing command-line arguments to get server configurations and initializing the server socket.

Parsing Command-Line Arguments

Your server should be able to process specific command-line arguments to determine its configuration. These arguments include the IP address, port number, accounts file, session timeout, and root directory for file downloads.

Initializing the Server Socket

Once the configuration is set, initialize the server socket to bind to the specified IP address and port number. The server should then start listening for incoming connections.

Implementing User Authentication

User authentication is a critical component of the server. This involves reading and parsing the accounts file, validating login credentials, and managing user sessions.

Reading the Accounts File

The accounts file contains the list of user accounts with their hashed passwords and salts. The server should read and parse this file to retrieve the necessary information for authentication.

Validating Login Credentials

When a user attempts to log in, the server should validate their credentials by hashing the provided password with the stored salt and comparing it with the stored hash. If the credentials are valid, a session should be created for the user.

Creating and Managing Sessions

Upon successful login, the server should generate a session ID and create a session for the user. This session is tracked using a cookie, and the server should update the session timestamp with each request to ensure it remains active.

Implementing File Access

Authenticated users should be able to access files within their designated directory. This involves handling GET requests, verifying user sessions, and ensuring secure file access.

Handling GET Requests

The server should handle GET requests for file retrieval. It should verify the user's session before allowing access to the requested file.

Verifying User Sessions

Before granting access to a file, the server should check the user's session ID stored in the cookie. If the session is valid and within the timeout period, the server should proceed with the file retrieval.

Returning File Contents

If the requested file exists within the user's directory, the server should read the file contents and return them in the HTTP response body. If the file does not exist, the server should return a 404 Not Found status.

Implementing Logging

Logging is essential for monitoring server activity and detecting security breaches. The server should log important events such as login attempts, file access, and session expirations.

Logging Login Attempts

The server should log all login attempts, including successful and failed attempts. This helps in tracking user activity and identifying potential unauthorized access attempts.

Logging File Access

All file access attempts should be logged, including successful and unsuccessful attempts. This provides a record of which files were accessed and by whom.

Logging Session Expirations

The server should log session expirations to track when users' sessions end. This helps in managing user activity and ensuring sessions are properly managed.

Implementing the Server

With a solid plan in place, the next step is to implement the server. Here’s a step-by-step guide to help you through the process.

Setting Up the Server

The first step is to initialize the server and start listening for incoming connections. This involves parsing command-line arguments and setting up the server socket.

import socket import json import random import datetime import hashlib import sys def start_server(ip, port, accounts_file, session_timeout, root_directory): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind((ip, int(port))) server_socket.listen(5) print(f"Server started on {ip}:{port}") sessions = {} while True: client_socket, addr = server_socket.accept() request = client_socket.recv(1024).decode() method, target, _ = parse_request(request) if method == "POST" and target == "/": handle_post(client_socket, request, accounts_file, sessions, session_timeout) elif method == "GET": handle_get(client_socket, request, root_directory, sessions) else: client_socket.sendall(b"HTTP/1.0 501 Not Implemented\r\n\r\n") client_socket.close()

Parsing HTTP Requests

To handle incoming HTTP requests, the server needs to parse the request method, target, and version. This helps in determining the appropriate action to take.

def parse_request(request): lines = request.split("\r\n") method, target, version = lines[0].split(" ") return method, target, version

Handling User Authentication

User authentication involves handling POST requests for login, validating credentials, and managing user sessions.

Handling POST Requests

The server should handle POST requests directed to the root URL ("/"). This involves reading the username and password from the headers and validating the credentials.

def handle_post(client_socket, request, accounts_file, sessions, session_timeout): headers = parse_headers(request) username = headers.get("username") password = headers.get("password") if not username or not password: log("LOGIN FAILED", username, password) client_socket.sendall(b"HTTP/1.0 501 Not Implemented\r\n\r\n") return with open(accounts_file, 'r') as f: accounts = json.load(f) if username in accounts: stored_hash, salt = accounts[username] hashed_password = hashlib.sha256((password + salt).encode()).hexdigest() if hashed_password == stored_hash: session_id = generate_session_id() sessions[session_id] = { "username": username, "timestamp": datetime.datetime.now() } log("LOGIN SUCCESSFUL", username, password) response = f"HTTP/1.0 200 OK\r\nSet-Cookie: sessionID={session_id}\r\n\r\nLogged in!" client_socket.sendall(response.encode()) return log("LOGIN FAILED", username, password) client_socket.sendall(b"HTTP/1.0 200 OK\r\n\r\nLogin failed!")

Creating and Managing Sessions

Sessions are managed using cookies with unique session IDs. The server should generate a session ID upon successful login and track the session information.

def generate_session_id(): return hex(random.getrandbits(64)) def log(message, username, target=""): print(f"SERVER LOG: {datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')} {message}: {username} : {target}")

Handling File Access

Authenticated users should be able to access files within their designated directory. This involves handling GET requests, verifying user sessions, and ensuring secure file access.

Handling GET Requests

The server should handle GET requests for file retrieval. It should verify the user's session before allowing access to the requested file.

def handle_get(client_socket, request, root_directory, sessions): headers = parse_headers(request) session_id = headers.get("Cookie").split('=')[1] if session_id not in sessions: log("COOKIE INVALID", "") client_socket.sendall(b"HTTP/1.0 401 Unauthorized\r\n\r\n") return session = sessions[session_id] if (datetime.datetime.now() - session['timestamp']).seconds > session_timeout: log("SESSION EXPIRED", session['username']) del sessions[session_id] client_socket.sendall(b"HTTP/1.0 401 Unauthorized\r\n\r\n") return target_file = request.split(' ')[1][1:] file_path = f"{root_directory}/{session['username']}/{target_file}" try: with open(file_path, 'r') as f: content = f.read() log("GET SUCCEEDED", session['username'], target_file) response = f"HTTP/1.0 200 OK\r\n\r\n{content}" client_socket.sendall(response.encode()) except FileNotFoundError: log("GET FAILED", session['username'], target_file) client_socket.sendall(b"HTTP/1.0 404 Not Found\r\n\r\n")

Implementing Logging

Logging is essential for monitoring server activity and detecting security breaches. The server should log important events such as login attempts, file access, and session expirations.

Logging Login Attempts

The server should log all login attempts, including successful and failed attempts. This helps in tracking user activity and identifying potential unauthorized access attempts.

def log(message, username, target=""): print(f"SERVER LOG: {datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')} {message}: {username} : {target}")

Logging File Access

All file access attempts should be logged, including successful and unsuccessful attempts. This provides a record of which files were accessed and by whom.

Testing Your Server

Testing your server is crucial to ensure it works as expected. Use various tools and scripts to test different scenarios, such as successful logins, failed logins, file access, and session expiration.

Using curl for Testing

You can use the curl command-line tool to simulate HTTP requests and test your server's functionality. Here's how to test different aspects of your server:

Testing Login Functionality

Use curl to send a POST request with a username and password to test the login functionality. Check the server's response and logs to verify successful and failed login attempts.

curl -X POST -H "username: testuser" -H "password: testpass"

Testing File Access

Use curl to send a GET request for a specific file. Ensure that the request includes the session ID in the cookie header to simulate an authenticated user.

curl -X GET -b "sessionID=0xabcdef

Testing Session Expiration

Simulate session expiration by waiting for the session timeout period and then attempting to access a file. Check the server's response and logs to ensure that expired sessions are handled correctly.

Debugging and Optimization

After testing, debug any issues that arise and optimize your code for better performance and readability. Here are some tips for debugging and optimizing your server.

Debugging Common Issues

Identify and fix common issues such as malformed requests, missing files, and session management errors. Use detailed logging to trace the source of problems and validate fixes.

Optimizing Performance

Optimize your server's performance by implementing efficient data structures, minimizing redundant operations, and ensuring proper resource management. Test your server under different loads to identify and address performance bottlenecks.

Enhancing Security

Enhance your server's security by implementing additional measures such as input validation, rate limiting, and improved session management. Regularly review and update your security practices to protect against emerging threats.

Conclusion

Creating an HTTP server with user authentication and file access involves understanding the requirements, planning your solution, implementing the server, and thorough testing. By following this structured approach, you can tackle similar assignments effectively and enhance your programming skills. With careful planning and implementation, you can develop a robust and secure HTTP server that meets the assignment requirements and serves as a valuable learning experience in web development and cyber security.


Comments
No comments yet be the first one to post a comment!
Post a comment