Homework 2 - Authentication


Jump to current week

Introduction


You will continue to add features to your app from homework 1, though you should pull from the repo to get the new front end features.

Sample Deployment: https://312demo.nico.engineer/


Learning Objective - Authentication


In this objective, you will add all the functionality needed for authentication. By the end of this objective, users will be able to create and login to their account and use the chat feature from HW1 with their chosen username as their display name.


Authentication Method: extract_credentials

Create a file named util/auth.py containing the following functions (This file does not define a class). In this file, write a function named "extract_credentials" that takes a Request object and returns a list containing 2 elements, a username then a password, both as strings (str)

  • The request object will either be in the format of a registration or a login request as generated by the provided front end. You must use the front end as-is since this is the exact format expected by the grader in Autolab
  • These requests will contain a url encoded string (The same format as query strings) containing these two values. You must parse the username and password from this encoded string. This includes decoding the percent-encoded characters
  • You are not allowed to use Python's urllib, or any other library that parses url encoded strings for you, to write this method
  • The username will only ever contain alphanumeric characters (eg. the username will never contain percent-encoded characters)
  • The password can contain alphanumeric characters as well as the following special characters {'!', '@', '#', '$', '%', '^', '&', '(', ')', '-', '_', '='} (Note: We are limiting passwords to these 12 special characters to limit the tedium of writing this method. In the real world, you should support every valid character, and you would also use a library to make this support trivial. Ensure you consider edge cases with parsing.)

Authentication Method: validate_password

In util/auth.py file, write another function named "validate_password" that takes a string (str) and returns a boolean specifying if the password meets all of the following 6 criteria. If the function returns True, the password meets all the criteria and should be considered acceptable

  • The length of the password is at least 8
  • The password contains at least 1 lowercase letter
  • The password contains at least 1 uppercase letter
  • The password contains at least 1 number
  • The password contains at least 1 of the 12 special characters listed above
  • The password does not contain any invalid characters (eg. any character that is not an alphanumeric or one of the 12 special characters)


Authenticated Chat

Add authentication to your app. To start, you will need to add the following paths for HTML pages that you will render using layout.html in the same way as index and chat from HW1.

  • "/register" - Render register.html
  • "/login" - Render login.html
  • "/settings" - Render settings.html
  • "/search-users" - Render search-users.html

The provided register and login pages with send requests in the format expected by the extract_credentials function and you should use this function to extract the username and password provided by your users. Do not modify the way these forms behave.

Your server must contain all of the following features.


Registration

When a user sends a registration request, store their username and a salted hash of their password in your database. Their password must pass the criteria tested by your validate_password method or the registration fails.

If a registration is not successful (eg. password is not valid), return a 400 response with a message of your choosing.

When a user registers, you should also generate a unique id for them in the same way that chat messages have unique ids.

Security: Never store plain text passwords. You must only store salted hashes of your users' passwords. It is strongly recommended that you use the bcrypt library to handle salting and hashing.


Login

When a user sends a login request, authenticate the request based on the data stored in your database. If the [salted hash of the] password matches what you have stored in the database, the user is authenticated.

When a user successfully logs in, set an authentication token named exactly "auth_token" as a cookie for that user with the HttpOnly directive set. These token cannot be session cookies (eg. set max age or expires). You must store a hash (no salt) of each token in your database so you can verify them on subsequent requests. Remember that you do not hash the cookie, but you do hash what's stored in the DB

If a login is not successful (eg. username doesn't exist), return a 400 response with a message of your choosing.

Security: Only hashes of your auth tokens should be stored in your database (Do not store the tokens as plain text). Salting is not expected.

Security: Set the HttpOnly directive on your "auth_token" cookie.


Logout

Add a path for GET "/logout", that is used by the logout button on the front end, that will log a user out. When this button is pressed, the user's auth token must be removed from their cookies and invalided by your server (eg. the auth token they were issued should no longer work even if your server receives it as a cookie value after log out). To remove a cookie, set a cookie with the same name and max age of 0 or an expires date in the past


Authenticated Chat

Whenever a chat message is sent by an authenticated user, the message should contain their actual username. This is done by setting the "author" of their messages to their username.

When a user logs in, any messages that they sent as a guest should retroactively have their author set to their username. Use you "session" cookie to track messages for this purpose. You should also ensure that messages cannot be "claimed" multiple times (eg. If a user sends a guest message, logs in, logs out, then logs in using a different account the guest message should still belong to the account of their first login).


Update and Delete Messages

Modify your update and delete message functionality to be based on user accounts instead of "session" cookies. A user should be able to log out, clear their session cookie, log back in, and still be able to delete their own messages.

When a user attempts to update or remove a message that is not their own, respond to the request with a 403 response code. If it is their own, you may choose the response you send (200 with a short message is fine).


@me

Add an endpoint GET "/api/users/@me" that will return the requesters user profile. You should return everything in your database stored for the user except their [hashed and salted] password and [hashed] auth token. This is used to display a users name on the page when they are logged in. If the user is not logged in, this endpoint returns an empty json object with a 401 status code

Response (JSON): {"username": string "id": string}


Filter/search users

Add an endpoint GET "/api/users/search" that takes a query string containing one key named "user". Return a json array with the following structure { "user": [{ "id": "1", "username": "bob" }] }. This endpoint is case-sensitive, meaning if a username is "Username" and the search is for "use" it will not return "Username" but a search for "Use" will.

If the search term (The value of "user" in the query string) is empty, do not return any results.


Update profile

Add an endpoint POST "/api/users/settings" which will receive requests containing a username and password in the same format as the registration and login endpoints. When you receive a request, update the users username and password to the provided values.

This endpoint must enforce the same password criteria as registering a new user. If the password does not meet all criteria, return a 400 response with a message of your choice. Do not update their username if the password is not valid.

You do not have the change the users prior messages when they change their username. Their old messages can still have their old username.

When a password is changed, they must not be able to login with their old password.



Application Objective 1 - 2-Factor Authentication


Enable time-based one-time password (TOTP) based 2-factor authentication (2FA) on your app. This objective will require inspecting network requests and reviewing some frontend files (you shouldn't edit anything on the front edit any of them) to learn exactly how to implement this feature.

When a user enables 2FA by clicking "Regenerate 2FA" on the setting page, your app should return a json in the format {"secret": string}. Each time a user clicks "Regenerate 2FA", your server is expected to generate a new secret. The frontend will use this secret to generate a QR code which your users can scan using any authenticator app on their phone (eg. Authy, Duo, Google Authenticator).

When a user with 2FA enabled logs in, you should expect them to provide a TOTP code in addition to their username and password. If no TOTP code is provided (The request only contains username and password), return a 401 response with a message of your choosing. If a TOTP code (Study the front end and network traffic to see the exact format of this code in the request. You may want to modify your extract_credentials function to also extract the TOTP if it's present) you will verify this code while authenticating them. If both their password and TOTP code are valid, they should be logged in.

You can, and should, use the PYOTP library to generate secrets and verify TOTPs.



Application Objective 2 - Login with GitHub (Oauth 2.0)


Enable the "login with GitHub" button. This button uses the GET "authgithub" endpoint. You should add functionality on this endpoint to follow the OAuth 2.0 protocol. After authenticating their account through GitHub, use the API to get the user's email address. Use this email address as their username for any messages they send.

You will use the Authorization Code Flow to build this feature and you should follow the documentation to guide your development. You will need a GitHub account to access the dev dashboard and this can be a free account.

Use "http://localhost:8080/authcallback" as your redirect_uri. The course staff will use this exact string in their GitHub app used to test your app.

You may use a library (eg. requests) to send HTTP requests to the GitHub API. You are also allowed to use the base64 library.

Note: You are not required to handle refresh tokens for either of these AOs. You should code this for extra practice and experience, but it would be too tedious to test/grade such a requirement since we'd have to wait for the tokens to expire before refreshing them.

Security: Never share your app's client secret or client id. Your submission must not contain your private information and should instead have placeholders/variables where it should go.

Instead of submitting your code with this secret and id, set these values as environment variables in a file named ".env". See the provided .env.example file for reference on the syntax. Use the variables names "GITHUB_CLIENT_ID" and "GITHUB_CLIENT_SECRET" in this file. You can then use the python-dotenv library to read this file on server startup. You will then have access to these variables anywhere in your code (See database.py for an example of reading an environment variable). Your submission should have placeholder values for these variables (ie. "changeMe" or "Put your client secret here" or ""). For testing, we will paste our own secret/id into your .env file before running docker compose up. You may also set your REDIRECT_URI this way if you prefer not to hardcode it.

Security: You must properly use the Authorization Code Flow. Using a different flow, or improperly using the Authorization Code Flow, that exposes your app to security issues discussed in lecture is a security concern.



Application Objective 3 - GitHub Chat Commands


If a user is logged in using GitHub, as opposed to using the register/login flow from the learning objective, they can enter special commands into the chat. If they type any of these commands into chat, your app will use the GitHub API to generate the text of their message. All commands begin with the character '/'. If any message is sent to chat that starts with /, you should treat it as a chat command and replace whatever was sent with the result of the chat command. The commands are:

  • /repos @user
    • List all GitHub repos of the target user (limit 50). These repos should appear as links where the text is the repo name. You have some flexibility in how to display all the links
    • Example: A message of "/repos ufoscout" will generate a message with 50 links to user ufoscouts GitHub repos
    • Note: This command results in HTML being rendered in chat. Since this HTML is completely under the control of your server, it is ok to render on the front end
  • /star @repo
    • use the current oauth user to star the provided repo. Display a confirmation of the star in chat
    • Example: A message of "/star ufoscout/docker-compose-wait" from a user who logged in with GitHub will result in that user starring the repo ufoscout/docker-compose-wait and a chat message being displayed confirming that it was starred
  • /createissue @repo @title
    • Creates an issue on @repo with a title of @title. The body of this issue should be empty (Unless you want to add this functionality, but it's not required). Display a confirmation of the created issue in chat
    • Example: A message of "/createissue CSE-312/CSE312-Website Finish the HW2 handout doc" will create an issue on the CSE-312/CSE312-Website repo with the title "Finish the HW2 handout doc" and an empty body and a confirmation message will be displayed in chat


Submission


Submit all files for your server to Autolab in a .zip file

If you used any external libraries, be sure to add them to your requirements.txt. Autolab will install all dependencies in this file, and no other dependencies, before starting your server.

It is strongly recommended that you download and test your submission after submitting. To do this, download your zip file into a new directory, unzip your zip file, enter the directory where the files were unzipped, run docker compose up, then navigate to localhost:8080 in your browser. This simulates exactly what the TAs will do during grading.



Grading


Each objective will be scored on a 0-3 scale as follows:

3 (Complete) Clearly correct. Following the testing procedure results in all expected behavior
2 (Complete) Mostly correct, but with some minor issues. Following the testing procedure does not give the exact expected results, but all features are functional
1 (Incomplete) Not all features outlined in this document are functional, but an honest attempt was made to complete the objective. Following the testing procedure gives an incorrect result, or no results at all, during any step. This includes issues running Docker or docker-compose even if the code for the objective is correct
0.3 (Incomplete) The objective would earn a 3, but a security risk was found while testing
0.2 (Incomplete) The objective would earn a 2, but a security risk was found while testing
0.1 (Incomplete) The objective would earn a 1, but a security risk was found while testing
0 (Incomplete) No attempt to complete the objective or violation of the assignment (Ex. Using an HTTP library)

Note that for your final grade there is no difference between a 2 and 3, or a 0 and a 1. The numeric score is meant to give you more feedback on your work.

3 Objective Complete
2 Objective Complete
1 Objective Not Complete
0 Objective Not Complete

Autograded objectives are graded on a pass/fail basis with grades of 3.0 or 0.0.



Security Essay


For each objective for which you earned a 0.3 or 0.2, you will still have an opportunity to earn credit for the objective by submitting an essay about the security issue you exposed. These essays must:

  • Be at least 1000 words in length
  • Explain the security issue from your submission with specific details about your code
  • Describe how you fixed the issue in your submission with specific details about the code you changed
  • Explain why this security issue is a concern and the damage that could be done if you exposed this issue in production code with live users

Any submission that does not meet all these criteria will be rejected and your objective will remain incomplete.

Due Date: Security essays are due 1-week after grades are released.

Any essay may be subject to an interview with the course staff to verify that you understand the importance of the security issue that you exposed. If an interview is required, you will be contacted by the course staff for scheduling. Decisions of whether or not an interview is required will be made at the discretion of the course staff.

When you don't have to write an essay:

  • If you never submit a security violation, you never have to write an essay for this course. Be safe. Be secure
  • If you earn a 0.1, there's no need to write an essay since you would not complete the objective anyway