Tehnologii Web/2022-2023/Laborator 12

Another Flask Authentication Methods
Allowing users to log in to the application is one of the most common features in web applications.

Installation gives packages
There are three main packages needed for the application:


 * Flask
 * Flask-Login: To manage user sessions after login
 * Flask-SQLAlchemy: to represent the user model and database interface

So, install the packages using the following line in Terminal: We will use SQLite to avoid installing additional database dependencies.

A new project named flask_auth_app will be created. And, inside it, a new folder called project will be created here.

Creating the main file
We will create the main file, but also an intermediate one (since a lot of main classes are called here), called __init__.py, in the folder named project. Everything we will introduce here will be stored and put in the project folder.

One plan handles the usual routes, which include the index page and the protected profile page. Another one handles everything related to authentication. In a real application, the functionality can be achieved in another way as well, but the solution presented here will work well for this tutorial.

This file will have the function of creating the application, which will initialize the database and register the data in the database.

SQLAlchemy will need to be initialized, some configuration values set, and data saved.

Adding routes
For routes, two Blueprints will be used.

For main_blueprint, there will be a home page (/) and a profile page (/profile).

First, main.py is created: For auth_blueprint there will be routes to fetch both the login page (/login) and the registration page (/signup). Finally, there will also be a logout route (/logout) to log out an active user.

Next, a new file that uses Blueprint authentication will be created. The following code lines are added to the auth.py. For now, log in, registration, and log out are defined with text returns. There will also be routes for handling POST requests from login and register. We will review this code later and update it with the desired functionality.

In a terminal, we can set the FLASK_APP and FLASK_DEBUG values: The FLASK_APP environment variable tells Flask how to load the application. We would want this to indicate where create_app method is. The project folder will be indicated.

The FLASK_DEBUG environment variable is enabled by setting it to 1. This will enable a debugger that will display application errors in the browser.

We will run the application with flask run in Terminal.

After verifying that the routes behave as expected, the templates can be created.

Creating templates (HTML files)
This is the first step before we can implement the actual login functionality. The application will use four templates:


 * index.html
 * profile.html
 * login.html
 * signup.html

There will also be a basic template that will have a common code for each of the pages. In this case, the base template will have navigation links and the overall page layout.

First, create a directory called templates in the project folder. Next, the main HTML page, named base.html, will be created. This code will create a series of menu links to each page of the application. It also sets a block for content that can be overridden by a child template.

In the index.html page that will be created, add the following code sequence: The code will display a title and a subtitle.

Next, a login.html page will be created. This code generates a login page with email and password fields. There is also a checkbox to remember a connected session.

Add the following code to create a page for registration, signup.html, with email, name, and password fields: The code for the profile.html page is the following one:  We will revisit this code later to dynamically greet any user.

Once we've added the templates, we can update the return statements in each of the routes to return templates instead of text.

Update main.py by changing the import line and routes for index and profiles: Also we update auth.py. Nothing will be done to /logout, for now.

Creating user models
This user model represents will be important for storing users. There will be the required email address fields, a password, and a name. In future applications, we can decide whether we want additional information to be stored for each user. Remarks such as birthdays, profile pictures, locations, or any preferences can be added.

Models created in Flask-SQLAlchemy are represented by classes that translate into tables of a database. The attributes of those classes then become columns for those tables.

Create the user model within the models.py file: This code defines a user with columns for an id, email, password, and name.

Now that a user model has been created, we can move on to configuring the database.

Database configuration
An SQLite database will be used. One can create an SQLite database on their own, but it will need to be integrated using Flask-SQLAlchemy. There is already the database path specified in the __init__.py file, so Flask-SQLAlchemy will need to be specified to create the database, by running the code or by using the Python REPL. we will use here the procedure of running the code for creating the database.

We should make sure that we are in the folder of the project.

We can create the database using the create_all method on the db object, in the __init__.py file, after the db.init_app(app) code line:  Being in this position, we can run the application and create the database directly.

Configuring the authorization function
For the register function, the data that the user submits in the form will be retrieved and added to the database. We will need to ensure that a user with the same email address does not already exist in the database. If it doesn't exist, then we need to make sure we've hashed the password before entering it into the database.

It starts by adding a second function to handle the POST form data. The data transmitted from the user is collected.

Update auth.py by changing the import line and implement signup_post:

Testing the registration method
Now that the register method is complete, a new user can be created. Let's test the form to create a user.

There are two ways for checking if the registration was successful:


 * 1) a database viewer can be used to see the row that was added to the table or
 * 2) we can try to sign up again with the same email address and if we get an error we will know the first email has been saved correctly.

The added code will notify the user that the email already exists and will need to be redirected, to go to the login page. By calling the flash function, it will be possible to send a message to the next request, which in this case is the redirect. The page that the user is redirected to will then have access to that template message.

First, the flash is added before redirecting to the registration page.

The auth.py file will be updated. To receive the flash message in the template, we can add this code before the form in the signup.html file: This code will display the message "Email address already exists. Go to login page".

Adding the login method
The login method is similar to the registration function. In this case, we will compare the entered email address, to see if it is in the database. If so, the user-supplied password will be tested by hashing the password that the user submits and comparing it to the hashed password in the database. We'll know the user entered the correct password when both hashed passwords match.

Once the users introduce the password check, they will know they have the correct credentials and can log in using Flask-Login. By calling login_user, Flask-Login will create a session for that user that will persist as long as the user remains logged in, allowing the user to view protected pages.

One can start with a new route for handling POST data. And redirect to the profile page when the user successfully login.

In the auth.py file, add: Credentials must be verified:  Let's add the block to the template so that the user can see the flash message:  The previous code sequence is added to the login.html file.

Add the following code sequence to the models.py file: Next, the user loader must be specified. A load user tells Flask-Login how to find a particular user from the ID that is stored in the session cookie. Add this sequence to the create_app function along with the init code for Flask-Login from the __init__.py file.

It is necessary to import the following libraries: Afterward, the following code will be introduced after the previous version lines in the create_app function. Finally, add the login_user function before redirecting to the profile page to create the session, in the auth.py file. With the Flask-Login configuration, the /login route is used. When everything is typed correctly, we will see the profile page.

At this point, the application can be run and user login to the application can be attempted.

Protecting pages
If the username is not Users, or if we want to make an introduction for a specific user, then the code will need to be changed. The scope of the profile is to display the name in the database. We will need to protect the page and then access the user data to get the name.

To protect a page when using Flask-Login, add the @login_required decorator between the route and the function. This will prevent an unauthenticated user from seeing the route. If the user is not authenticated, the user will be redirected to the login page, according to the Flask-Login configuration.

With routes that are decorated with the @login_required decorator, we can use the current_user object inside the function. This current_user represents the user in the database and provides access to all attributes of that user in dot notation. For example, current_user.email, current_user.password and current_user.name and current_user.id will return the actual values ​​stored in the database for the logged in user.

So, we will use the name current_user and send it to the template:

In the main.py file: In the profile.html file:  Once a user visits his profile page, he will be greeted by his name.

Now, to update the logout view, the logout_user function is called in a logout route, in the auth.py file. The @login_required decorator is used because it doesn't make sense to log out a user who isn't logged in to.

One last thing to do is to put if statements in the templates to show only the links relevant to the user, in the base.html file: Before the user connects to the application, he will have the option to log in or register. After he's logged in, he can go to his own profile or log out of the aplication.