Lab 3: Connecting front end and back end
In this lab we will connect your front end with your back end. You will also implement a simple authentication and authorization system. Using authentication we ensure that only logged-in users have access to the data we want to protect. With the authorization system we ensure that different users have different permissions, giving them different rights to access and manipulate the data.
AJAX
AJAX makes it possible to fetch and send data even after a web page has loaded. This is done using JavaScript. Read an introduction here.
Preparatory questions
Answer the following questions and write down your answers before the demonstration: 1. What does AJAX stand for? 2. Sketch a solution for how your server could find out which user is sending a particular request.
Part 1: AJAX
Start by following Starting a new lab.
We start by modifying the current client-side implementation so that the client fetches data from your Flask server (from lab 1) asynchronously.
Replace serverStub with AJAX calls in
client.jsand removeserverStub.js; test whether your new implementation works by removing the fileserverStub.jsand seeing if everything still works.Tip
Use the following code sketch to replace the call
serverStub.getCars(). To replace the other methods the example below can be modified with respect to url and type. Your server’s address should be saved in a variable for easier modification in the future.Note that CORS distinguishes between
http://127.0.0.1andhttp://localhost. Instead of having a hard-coded name you can use:host = window.location.protocol + '//' + location.host
fetch(host + '/cars', // url { // options method: 'PUT', // eller 'GET', 'POST' eller 'DELETE' headers: { 'Content-Type': 'application/json' // Behövs ibland för PUT eller POST } } ).then(response => { if (!response.ok) { throw new Error('...'); } return response.json(); }).then(cars => { // cars innehåller nu den JSON-data som servern svarar med på /cars // Gör någonting med /cars här });
The variable cars is not visible after the call and code that runs here cannot see the changes made in the function that has the cars variable - that code runs asynchronously when it has received a response from the server.
This is the difficulty with AJAX - understanding when code runs.
Note that all calls to the server should be asynchronous, i.e. async: false (if using XMLHttpRequest), await or similar methods must not be used.
Part 2: The user model
We will build an authentication system in Flask where we handle registration, login and access to data.
As a first step we will now make some changes to the current implementation to make it more suited for handling different types of users.
Refactor (rename) your model for
CustomertoUserand add a new field,is_admin. The field should be of type boolean with the default valueFalse.There are also other solutions for distinguishing between different types of users. In this lab however this solution is practical since it essentially only requires adding a field to the database.
As you can see we have set an initial value for the field
is_admin; this is solely to ensure that new users are always set as non-admin. Note that you also need to update relations and methods in all places where you have usedCustomer.Tip
Use
Ctrl+Shift+Fin VSCode to search forcustomerin the entire folder.
Part 3: Registration and password handling on the server
The next step in our authentication process is to give the user the ability to identify themselves by providing a password when logging in. A first idea for solving this task is to create a column in our database to store the user’s password as a string. But now we need to be careful - is it reasonable to save a password as plain text in a database? The answer is of course no, no and no again. It is never a good idea to save passwords as plain text in a database. We solve this instead by using a hash function on the password before we store it in the database.
In this lab we will continue using helper libraries just as before. In this case we will use bcrypt which is a hashing library. Read through the bcrypt documentation to find methods that can help us hash passwords before they are stored in the database.
When we then need to validate whether a user is authentic we will run the same hash function on the claimed user’s provided password and compare it with the hashed password we stored in the database.
Install
flask-bcryptin your development environment and add the following two lines at appropriate places in your main.py:from flask_bcrypt import Bcrypt
bcrypt = Bcrypt(app)
Now add a field to your user model to store a hashed password. Name it
password_hashand set the type toString. It can then be convenient to add a method to the model as below to easily hash and set passwords.def set_password(self, password): self.password_hash = generate_password_hash(password).decode('utf8')
- Now create functionality for the route
/sign-up.You should receive JSON with an email address, a name and a password. This data should be used to save a new user in the database. The password should be hashed before it is saved. The server should then respond with status code 200.Tip
Use the method
set_passwordto hash the password before you save it.
Part 4: Token-based authentication
The overall structure we will follow is token-based authentication. In summary this means that a token is generated on the server upon login. This token is then used in all requests to determine who the user is (see the figure below). Read more about token-based authentication here.
Since anyone can use Postman or other tools to send arbitrary requests to a web server it is not enough that the user must log in on the client - the server must always be protected against all conceivable requests.
To create and read tokens we will use the package Flask-Jwt-Extended. This package will be referred to as Flask-JWT going forward.
Install Flask-JWT by running the command:
(venv) $ pip install flask-jwt-extended
or by using your requirements.txt if you have one.
Configure Flask-JWT in
main.pyby:Importing
JWTManagerfromflask_jwt_extendedSetting
app.config['JWT_SECRET_KEY']to some hard-to-guess stringInitializing JWTManager:
jwt = JWTManager(app)
Create functionality for
POST /login. The method should receive an email and a password. Verify that they are correct and then respond with a token and user data (make sure not to send the password hash). The response should thus look like this:
If the login credentials are not correct the server should respond with HTTP status code 401.
Tip
Use create_access_token from Flask-JWT.
The server must also keep track of ensuring the right users get access to the right functionality and data. Modify your server code so that all routes except
/sign-upand/loginrequire an authenticated user. If a user tries to access a route and cannot be authenticated, the server should respond with status code 401. Test with Postman both with and without the auth header.Tip
Use the decorator
@jwt_requiredas in the documentationIn Postman you can include a token in this way:
Part 5: Registration, login and logout on the client
Create registration capability:
Skapa menyalternativet “Registrera dig”.
Create a new view containing an HTML form that accepts email, name and password.
Create functionality to send the form data to
/sign-upso that a new user is created.When the user has been created, make sure the welcome view is displayed instead of the form.
Create login capability:
Skapa menyalternativet “Logga in”.
Create a new view containing an HTML form that accepts email and password.
Create functionality to send the form data to
/loginwhere the credentials will be validated.Save the token and user data that the server responds with after a successful login.
Tip
sessionStorage can be used to save data in the browser in the following way:
sessionStorage.setItem('auth', JSON.stringify(loginResponse))
When the user has successfully logged in, make sure the welcome view is displayed.
Tip
The browser’s developer tools can be used to read the contents of sessionStorage. Values can also be deleted using this. The developer tools in Chrome are opened by pressing the F12 key.
Dölj menyalternativen “Logga in” och “Registrera dig” om det finns ett token sparat i webbläsaren. Se även till att dessa menyalternativ visas om ett token inte finns sparat.
Tip
Use
classList.toggle()and the Bootstrap classd-noneto control the visibility of an element. This logic can advantageously be placed so that it runs when the page loads.var signedIn = sessionStorage.getItem('auth').length > 0; document.getElementById("...").classList.toggle('d-none', !signedIn)
Create logout capability:
Create the menu item Log out and show it only when a user is logged in.
Remove the token from
sessionStoragewhen the user logs out.
Tip
Use
sessionStorage.removeItem()
Part 6: Authentication with AJAX
Visa menyalternativet “Bilar” endast om användaren är inloggad.
Send the token you saved at login in every request that needs to be authenticated (not for
/loginand/sign-up). This can be done by passing the following argument tofetchand so on.headers: {"Authorization": "Bearer " + JSON.parse(sessionStorage.getItem('auth')).token}
Visa knapparna “Redigera” och “Ta bort” på varje bil endast om användaren är admin.
Skapa och visa en “Boka”-knapp på varje bil som visas om användaren inte är admin. Dock, om bilen redan är bokad, ska “Bokad av namn” visas istället för boka-knappen.
Skapa
POST /cars/[int:car_id]/bookingsom sätter en relation mellan den inloggade användaren och bilen ifall den inte redan är bokad. Servern ska svara med{success: true}om bokningen gick igenom och{success: false}om den redan var bokad av någon annan. Gör så att en förfrågan till denna route skickas när användaren klickar på en bils bokningsknapp och att texten “Bokad av namn” visas om bokningen lyckades.Tip
Use get_jwt_identity to find the logged-in user.
Now create a solution for cancelling car bookings in the same way as cars are booked.
Demonstration
See Demonstration.