Flask-Login for Class Management Application

In this blog, we’ll explain flask Login with examples from a Class Management System Application. We’ll explore how Flask-Login works, how to integrate it with a React frontend, and how to secure your application. By the end, you’ll have an idea of the concept of functional system where a professors can log in, and bring them to a dashboard page where they can manage semesters, classes, and students, and securely log out. What is Flask-Login? Flask-Login is a Flask extension that simplifies user authentication. It handles: User sessions: Keeping users logged in across requests. Access control: Protecting routes from unauthorized access. User loading: Fetching user data from the database based on session information. Remember me: Persisting user sessions across browser restarts. Flask-Login doesn’t handle password hashing or database management directly. Instead, it integrates seamlessly with other libraries like bcrypt for password hashing and SQLAlchemy for database interactions. Core Components in My Code Create a model for user -> Professor model: It represents a professor in the database and includes methods required by Flask-Login. class Professor(db.Model, SerializerMixin): __tablename__ = "professors" serialize_rules = ('-_password_hash', '-semesters.professor',) id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String, nullable=False, unique=True) name = db.Column(db.String, nullable=False) department = db.Column(db.String, nullable=False) office_location = db.Column(db.String, nullable=True) _password_hash = db.Column(db.String, nullable=False) semesters = db.relationship('Semester', back_populates='professor', cascade='all, delete-orphan') def get_id(self): return str(self.id) @property def is_authenticated(self): return True @property def is_active(self): return True @property def is_anonymous(self): return False @hybrid_property def password_hash(self): raise Exception('Password hashes may not be viewed') @password_hash.setter def password_hash(self, password): if len(password) < 5: raise ValueError("Password should be 5 characters or longer") self._password_hash = bcrypt.generate_password_hash(password.encode('utf-8')).decode('utf-8') def authenticate(self, password): return bcrypt.check_password_hash(self._password_hash, password.encode('utf-8')) @validates('username') def validate_username(self, key, username): if not username or not isinstance(username, str): raise ValueError('Username should be a string') if len(username) < 3: raise ValueError('Username should be at 3 characters or more') return username @validates('name') def validate_name(self, key, name): if not name or not isinstance(name, str): raise ValueError('Name must be a string') return name @validates('department') def validate_department(self, key, department): if not department or not isinstance(department, str): raise ValueError('Department must be a non-empty string.') return department @validates('office_location') def validate_office_location(self, key, office_location): if office_location and not isinstance(office_location, str): raise ValueError('Office location should be a string.') return office_location def to_dict(self, rules=()): professor_dict = { "id": self.id, "username": self.username, "name": self.name, "department": self.department, "office_location": self.office_location, "semesters": [semester.to_dict() for semester in self.semesters] } for rule in rules: if rule in professor_dict: del professor_dict[rule] return professor_dict def __repr__(self): return f'' Key Features: get_id(): Flask-Login uses this method to identify the user. It must return a unique identifier (e.g., the user’s ID). is_authenticated: This property checks if the user is logged in. It’s used by Flask-Login to determine if a user can access protected routes. is_active: This property checks if the user account is active. For example, you could deactivate accounts without deleting them. is_anonymous: This property checks if the user is anonymous (not logged in). Password Handling: The password_hash setter hashes the password using bcrypt. The authenticate method compares a provided password with the stored hash. Create a backend user loader in config.py The user loader is a callback function that Flask-Login uses to reload a user from their session ID. @login_manager.user_loader def load_user(professor_id): return db.session.get(Professor, int(professor_id)) What it Does: When a user logs in, Flask-Login

Mar 23, 2025 - 06:51
 0
Flask-Login for Class Management Application

In this blog, we’ll explain flask Login with examples from a Class Management System Application.

We’ll explore how Flask-Login works, how to integrate it with a React frontend, and how to secure your application.

By the end, you’ll have an idea of the concept of functional system where a professors can log in, and bring them to a dashboard page where they can manage semesters, classes, and students, and securely log out.

What is Flask-Login?

Flask-Login is a Flask extension that simplifies user authentication.

It handles:

User sessions: Keeping users logged in across requests.
Access control: Protecting routes from unauthorized access.
User loading: Fetching user data from the database based on session information.
Remember me: Persisting user sessions across browser restarts.
Flask-Login doesn’t handle password hashing or database management directly. Instead, it integrates seamlessly with other libraries like bcrypt for password hashing and SQLAlchemy for database interactions.

Core Components in My Code

  1. Create a model for user -> Professor model: It represents a professor in the database and includes methods required by Flask-Login.
class Professor(db.Model, SerializerMixin):
    __tablename__ = "professors"
    serialize_rules = ('-_password_hash', '-semesters.professor',) 

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String, nullable=False, unique=True)
    name = db.Column(db.String, nullable=False)
    department = db.Column(db.String, nullable=False)
    office_location = db.Column(db.String, nullable=True)
    _password_hash = db.Column(db.String, nullable=False)

    semesters = db.relationship('Semester', back_populates='professor', cascade='all, delete-orphan')

    def get_id(self):
        return str(self.id)

    @property
    def is_authenticated(self):
        return True

    @property
    def is_active(self):
        return True

    @property
    def is_anonymous(self):
        return False

    @hybrid_property
    def password_hash(self):
        raise Exception('Password hashes may not be viewed')

    @password_hash.setter
    def password_hash(self, password):
        if len(password) < 5:
            raise ValueError("Password should be 5 characters or longer")
        self._password_hash = bcrypt.generate_password_hash(password.encode('utf-8')).decode('utf-8')

    def authenticate(self, password):
        return bcrypt.check_password_hash(self._password_hash, password.encode('utf-8'))

    @validates('username')
    def validate_username(self, key, username):
        if not username or not isinstance(username, str):
            raise ValueError('Username should be a string')
        if len(username) < 3:
            raise ValueError('Username should be at 3 characters or more')
        return username

    @validates('name')
    def validate_name(self, key, name):
        if not name or not isinstance(name, str):
            raise ValueError('Name must be a string')
        return name

    @validates('department')
    def validate_department(self, key, department):
        if not department or not isinstance(department, str):
            raise ValueError('Department must be a non-empty string.')
        return department

    @validates('office_location')
    def validate_office_location(self, key, office_location):
        if office_location and not isinstance(office_location, str):
            raise ValueError('Office location should be a string.')
        return office_location

    def to_dict(self, rules=()):
        professor_dict = {
            "id": self.id,
            "username": self.username,
            "name": self.name,
            "department": self.department,
            "office_location": self.office_location,
            "semesters": [semester.to_dict() for semester in self.semesters]
        }
        for rule in rules:
            if rule in professor_dict:
                del professor_dict[rule]
        return professor_dict

    def __repr__(self):
        return f''

Key Features:

  • get_id(): Flask-Login uses this method to identify the user. It must return a unique identifier (e.g., the user’s ID).
  • is_authenticated: This property checks if the user is logged in. It’s used by Flask-Login to determine if a user can access protected routes.
  • is_active: This property checks if the user account is active. For example, you could deactivate accounts without deleting them.
  • is_anonymous: This property checks if the user is anonymous (not logged in).
  • Password Handling:
  • The password_hash setter hashes the password using bcrypt.
  • The authenticate method compares a provided password with the stored hash.
  1. Create a backend user loader in config.py The user loader is a callback function that Flask-Login uses to reload a user from their session ID.
@login_manager.user_loader
def load_user(professor_id):
    return db.session.get(Professor, int(professor_id))

What it Does:

  • When a user logs in, Flask-Login stores their ID in the session cookie.
  • On subsequent requests, Flask-Login calls load_user to reload the user from the database using the stored ID.

2.1 Add Security figure in config.py

app.config['SESSION_COOKIE_SECURE'] = True  # HTTPS only
app.config['SECRET_KEY'] = 'your-secret-key'  # Encrypts cookies

Password Security

  • bcrypt hashes passwords before storage
self._password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
  • Plain passwords never stored in the database
  • authenticate() method compares hashes Authentication: Passwords are verified using bcrypt.check_password_hash().
  1. Create a Login/Logout Route(Backend) ->Make sure to include the resource in the app.py file ->The Login and Logout routes handle user authentication and session management.
class Login(Resource):
    def post(self):
        professor = Professor.query.filter_by(username=username).first()
        if professor.authenticate(password):
            login_user(professor)  # Flask-Login magic!
            return current_user.to_dict()

class Logout(Resource):
    @login_required
    def post(self):
        logout_user()  # Ends the session
        return {'message': 'Logged out!'}

How it Works:

Login:

  • The frontend sends a username and password.
  • The backend verifies the credentials using the authenticate method.
  • If valid, login_user() starts a session and stores the user’s ID in a secure cookie.

Logout:

  • logout_user() clears the session cookie, effectively logging the user out.
  1. Protected Routes You can protect routes using the @login_required decorator.
class Semesters(Resource):
    @login_required
    def get(self):
        return Semester.query.filter_by(professor_id=current_user.id).all()

What Happens:

  • If a user tries to access a protected route without logging in, Flask-Login redirects them to the login page.
  • current_user is a proxy for the logged-in user, accessible in all routes.

*Integrating in frontEnd (React Context) *
-> Create a global state file useContext that include login fetch request to the back end
-> The frontend uses React Context to manage the authenticated user’s state.

// UserContext.js
const UserContext = createContext();

function UserProvider({ children }) {
  const [user, setUser] = useState(null);

  // Auto-login on page load
  useEffect(() => {
    fetch("/check_session")
      .then(res => res.json())
      .then(data => setUser(data));
  }, []);

  // Login function
  const login = (credentials) => {
    fetch("/login", { method: "POST", body: JSON.stringify(credentials) })
      .then(() => fetch("/check_session"))
      .then(res => setUser(res.json()));
  };

  // Logout function
  const logout = () => {
    fetch("/logout", { method: "POST" })
      .then(() => setUser(null));
  };
}

Key Flows:

  • Auto-Login: On page load, the app checks if the user is already logged in by calling /check_session.
  • Persistent Login: The user’s data is stored in React state and shared across components via context.
  • Protected Components: Use the user state to conditionally render UI elements.

WorkFlow:

1) Signup

  • A professor creates an account.
  • The password is hashed and stored in the database.

2)Login

  • Frontend: The professor logs in with their username and password.
  • Backend: verifies credentials, starts session

3)Access Control

  • Protected routes use @login_required
  • Protected routes (e.g., managing semesters) are only accessible to logged-in users.

4)Access Control:

  • Protected routes (e.g., managing semesters) are only accessible to logged-in users.

5)Logout

  • The session is destroyed, and the user is logged out.
  • Client-side user state cleared

Conclusion

Flask-Login manages user authentication, especially when paired with secure password handling via bcrypt and frontend integration. With proper setup, your application can efficiently handle signup, login, logout, and session management while maintaining a level of security.