Implementing Secure Authentication with PyQt5 and Database Integration
Environment Setup
To develop the login interface, ensure the following development stack is configured:
- Operating System: Windows 10 or later (compatible with Linux/macOS)
- IDE/Editor: Sublime Text 3 or VS Code
- Language: Python 3.x with PyQt5 bindings
Functional Requirements
The authentication module must handle the following logic flow:
- Input Validation: Restrict character length and type for username and password fields.
- Password Masking: Ensure passwords are obscured in the UI field.
- Verification: Compare entered credentials against stored database records.
- Error Handling: Provide feedback for empty inputs, non-existent accounts, or incorrect passwords.
- Role Dispatch: Emit specific signals upon successful authentication to determine if the user is an administrator or a standard student.
User Interface Design
The layout utilizes QFormLayout for aligning labels and input fields, wrapped within horizontal and vertical layouts for centering.
Components:
- One header label for system branding.
- Two input fields for ID and Password entry.
- Two action buttons: "Login" and "Clear".
- Custom font sizing and fixed widget dimensions for consistency.
Implementation Details
Layout Configuration
The visual structure relies on nested widgets to manage spacing and alignment effectively.
def build_layout(self):
self.main_vertical = QVBoxLayout(self)
# Header Section
self.header_label = QLabel("Library Management System")
font_title = QFont()
font_title.setPixelSize(28)
self.header_label.setFont(font_title)
self.header_label.setAlignment(Qt.AlignCenter)
# Input Form
self.form_layout = QFormLayout()
self.form_layout.setSpacing(10)
# Username Field
self.username_lbl = QLabel("User ID : ")
self.user_input = QLineEdit()
self.user_input.setFixedWidth(200)
self.user_input.setMaxLength(10)
self.user_input.setFont(QFont(None, 16))
# Password Field
self.pass_lbl = QLabel("Password : ")
self.pwd_input = QLineEdit()
self.pwd_input.setFixedWidth(200)
self.pwd_input.setMaxLength(16)
self.pwd_input.setEchoMode(QLineEdit.PasswordEchoOnEdit)
self.pwd_input.setFont(QFont(None, 16))
# Validators
id_validator = QRegExpValidator(QRegExp(r"[A-Za-z0-9]{8,}"), self)
pwd_validator = QRegExpValidator(QRegExp(r"^[a-zA-Z0-9]+$"), self)
self.user_input.setValidator(id_validator)
self.pwd_input.setValidator(pwd_validator)
self.form_layout.addRow(self.username_lbl, self.user_input)
self.form_layout.addRow(self.pass_lbl, self.pwd_input)
# Buttons Area
self.btn_container = QWidget()
btn_layout = QHBoxLayout(self.btn_container)
self.btn_login = QPushButton("Log In")
self.btn_reset = QPushButton("Reset")
self.btn_login.setMinimumHeight(30)
self.btn_reset.setMinimumHeight(30)
btn_layout.addWidget(self.btn_login)
btn_layout.addWidget(self.btn_reset)
self.form_layout.addRow(self.btn_container)
# Main Widget Assembly
form_widget = QWidget()
form_widget.setLayout(self.form_layout)
self.container = QWidget()
content_hbox = QHBoxLayout(self.container)
content_hbox.addStretch()
content_hbox.addWidget(form_widget)
content_hbox.addStretch()
self.main_vertical.addWidget(self.header_label)
self.main_vertical.addWidget(self.container)
Initialization Logic
Upon instantiation, the widget initializes the database manager reference and sets window properties.
def __init__(self):
super(AuthScreen, self).__init__()
self.resize(900, 600)
self.setWindowTitle("Authentication Portal")
self.build_layout()
self.db_manager = UserDbManager()
Signal Connections
Event listeners bind UI interactions to handler methods.
# Connect button clicks and enter key presses
self.btn_login.clicked.connect(self.handle_login)
self.btn_reset.clicked.connect(self.handle_clear)
self.user_input.returnPressed.connect(self.handle_login)
self.pwd_input.returnPressed.connect(self.handle_login)
Business Logic
The core verification process hashes the input password using MD5 before qeurying the database.
1. Clear Functionality Resets input fields to default state.
def handle_clear(self):
self.user_input.clear()
self.pwd_input.clear()
self.user_input.setFocus()
2. Login Verification Handles security checks and role detection.
def handle_login(self):
user_id = self.user_input.text()
secret = self.pwd_input.text()
# Validate non-empty inputs
if not user_id or not secret:
QMessageBox.warning(self, "Warning", "Credentials cannot be empty!")
return
# Hash the input password
hasher = hashlib.md5()
hasher.update(secret.encode('utf-8'))
hashed_secret = hasher.hexdigest()
# Query database
record = self.db_manager.query_user(user_id)
if not record:
QMessageBox.information(self, "Info", "Account does not exist.")
self.handle_clear()
return
# Verify hash match
# Assuming record format: [id, name, hashed_pwd, is_admin_flag, ...]
db_id, _, db_hash, admin_flag, _ = record[0]
if db_id == user_id and db_hash == hashed_secret:
if admin_flag == 1:
self.admin_access_signal.emit()
else:
self.student_access_signal.emit(user_id)
else:
QMessageBox.information(self, "Info", "Incorrect password.")
Complete Module Code
This example aggregates the setup into a functional script.
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton, \
QVBoxLayout, QHBoxLayout, QFormLayout, QMessageBox, QRegExpValidator, QRegExp
from PyQt5.QtGui import QFont, QIcon
from PyQt5.QtCore import Qt, pyqtSignal
import hashlib
# Mock external dependency
# from db.userInfoManager import UserDbManager
class AuthScreen(QWidget):
admin_access_signal = pyqtSignal()
student_access_signal = pyqtSignal(str)
def __init__(self):
super().__init__()
self.resize(900, 600)
self.setWindowTitle("System Entry")
self.setup_ui()
# Initialize DB connection here
# self.db_manager = UserDbManager()
def setup_ui(self):
self.layout = QVBoxLayout(self)
# Title
title = QLabel("Library Management Application")
title_font = QFont()
title_font.setPointSize(20)
title.setFont(title_font)
title.setAlignment(Qt.AlignCenter)
title.setStyleSheet("font-weight: bold;")
# Form Container
container = QWidget()
h_box = QHBoxLayout(container)
# Fields
form = QFormLayout()
self.txt_username = QLineEdit()
self.txt_password = QLineEdit()
self.txt_password.setEchoMode(QLineEdit.PasswordEchoOnEdit)
# Validation Rules
reg_id = QRegExp(r"[A-Za-z0-9]{8,}")
reg_pwd = QRegExp(r"[a-zA-Z0-9]+")+"")
validator = QRegExpValidator(reg_id)
self.txt_username.setValidator(QRegExpValidator(reg_id))
self.txt_password.setValidator(QRegExpValidator(reg_pwd))
form.addRow("Username:", self.txt_username)
form.addRow("Password:", self.txt_password)
# Buttons
btn_frame = QWidget()
btn_layout = QHBoxLayout(btn_frame)
self.btn_signin = QPushButton("Sign In")
self.btn_cancel = QPushButton("Reset")
btn_layout.addWidget(self.btn_signin)
btn_layout.addWidget(self.btn_cancel)
form.addRow(btn_frame)
h_box.addStretch()
h_box.addLayout(form)
h_box.addStretch()
self.layout.addWidget(title)
self.layout.addWidget(container)
# Bind Events
self.btn_signin.clicked.connect(self.check_credentials)
self.btn_cancel.clicked.connect(self.reset_fields)
self.txt_username.returnPressed.connect(self.check_credentials)
self.txt_password.returnPressed.connect(self.check_credentials)
def reset_fields(self):
self.txt_username.clear()
self.txt_password.clear()
self.txt_username.setFocus()
def check_credentials(self):
uid = self.txt_username.text().strip()
pswd = self.txt_password.text()
if not uid or not pswd:
QMessageBox.warning(self, "Error", "Please fill in all fields.")
return
# Simulated DB lookup instead of actual call for standalone integrity
# record = self.db_manager.query_user(uid)
# --- Logic Simulation Start ---
# In production, uncomment below block
# if not record:
# QMessageBox.warning(self, "Notice", "User not found.")
# self.reset_fields()
# else:
# pass_hash = hashlib.md5(pswd.encode('utf-8')).hexdigest()
# db_pass = record[0][2] # Adjust index based on schema
# if pass_hash == db_pass:
# if record[0][3] == 1:
# self.admin_access_signal.emit()
# else:
# self.student_access_signal.emit(uid)
# else:
# QMessageBox.warning(self, "Alert", "Wrong password.")
# --- Logic Simulation End ---
QMessageBox.information(self, "Demo", "Simulation completed.")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = AuthScreen()
window.show()
sys.exit(app.exec_())