{"id":22246601,"url":"https://github.com/el634dev/acs-1220-auth-lab","last_synced_at":"2025-03-25T11:24:44.530Z","repository":{"id":222572345,"uuid":"757776259","full_name":"el634dev/ACS-1220-Auth-Lab","owner":"el634dev","description":null,"archived":false,"fork":false,"pushed_at":"2024-05-06T16:57:23.000Z","size":69,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-30T10:30:29.999Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/el634dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-15T00:24:33.000Z","updated_at":"2024-02-15T00:30:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"07578e43-1822-4d17-bae9-bbfd32c21725","html_url":"https://github.com/el634dev/ACS-1220-Auth-Lab","commit_stats":null,"previous_names":["el634dev/acs-1220-auth-lab"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/el634dev%2FACS-1220-Auth-Lab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/el634dev%2FACS-1220-Auth-Lab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/el634dev%2FACS-1220-Auth-Lab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/el634dev%2FACS-1220-Auth-Lab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/el634dev","download_url":"https://codeload.github.com/el634dev/ACS-1220-Auth-Lab/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245451250,"owners_count":20617480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-03T05:28:19.753Z","updated_at":"2025-03-25T11:24:44.503Z","avatar_url":"https://github.com/el634dev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lab - Books App Part 3 (Authentication)\n\n## Why should I do this?\n\nThis lab will guide you through the process of adding authentication - that is, fully functional login, signup, \u0026 logout routes - to your Flask application. By the end of this lab, you should be ready to use the Flask-Login library to add user login functionality to your projects. \n\n## Setup\n\nClone this repository to your computer. \n\n**Take a look at the code** - it looks a bit different than what you're used to. Namely, the code is now separated out into several files rather than being written in a single `app.py` file. Since we're now writing model and form code as well as route code, this will help us to maintain some structure and separation.\n\n**To run the code**, navigate to the project folder and run the following to create a virtual environment and install the required packages:\n\n```\npython3 -m venv venv\nsource venv/bin/activate\npip install -r requirements.txt\n```\n\nThen, rename the `.env.example` file to `.env`:\n\n```\nmv .env.example .env\n```\n\nThen you can run the following to run the Flask server:\n\n```\npython app.py\n```\n\n## Part 1: Tutorial Instructions\n\n### Setup Code\n\nIn `books_app/extensions.py`, add the following code under the \"Authentication\" header:\n\n```py\nlogin_manager = LoginManager()\nlogin_manager.login_view = 'auth.login'\nlogin_manager.init_app(app)\n```\n\nThis will create a login manager and initialize it with our app. We are also telling the login manager where to find the login route, namely that it's inside of the `auth` blueprint and is called `login`.\n\nBelow that, add the following code:\n\n```py\nfrom .models import User\n\n@login_manager.user_loader\ndef load_user(user_id):\n    return User.query.get(user_id)\n```\n\nThis tells the login manager how to load a user with a particular id. We're using Flask-SQLAlchemy for this project, but technically, you could use any other database, or even make up your own `User` object!\n\nFinally, let's initialize Bcrypt which we'll use later for hashing passwords:\n\n```py\nbcrypt = Bcrypt(app)\n```\n\n_Side note: All of the required imports should already be present in the project, but it's still a good idea to run your code as you go along, to make sure you catch any missing imports or syntax errors!_\n\n### Models\n\nIn `books_app/models.py`, **update the `User` model so that it inherits from `flask_login.UserMixin`**:\n\n```py\nclass User(UserMixin, db.Model):\n    # ... fields go here\n```\n\nThis will super-charge our `User` model with all of the functionality it needs to support authentication! To read more about what functionality the `UserMixin` provides, read the documentation [here](https://flask-login.readthedocs.io/en/latest/#your-user-class).\n\n### Forms\n\nIn `books_app/auth/forms.py`, let's create a few forms to use for login \u0026 signup.\n\nStart by creating the `SignUpForm`:\n\n```py\nclass SignUpForm(FlaskForm):\n    username = StringField('User Name',\n        validators=[DataRequired(), Length(min=3, max=50)])\n    password = PasswordField('Password', validators=[DataRequired()])\n    submit = SubmitField('Sign Up')\n\n    def validate_username(self, username):\n        user = User.query.filter_by(username=username.data).first()\n        if user:\n            raise ValidationError('That username is taken. Please choose a different one.')\n```\n\nOur signup form has only 2 entry fields, `username` and `password`, but we could certainly add more fields if there is any other relevant information we want to store in the `User` object.\n\nNote that we have an extra method, `validate_username`, which makes sure that a new user can't sign up with a username that is already taken. If that happens, we'll display an error and the user can try signing up again.\n\nNow, fill out the `LoginForm`:\n\n```py\nclass LoginForm(FlaskForm):\n    username = StringField('User Name',\n        validators=[DataRequired(), Length(min=3, max=50)])\n    password = PasswordField('Password', validators=[DataRequired()])\n    submit = SubmitField('Log In')\n\n    def validate_username(self, username):\n        user = User.query.filter_by(username=username.data).first()\n        if not user:\n            raise ValidationError('No user with that username. Please try again.')\n\n    def validate_password(self, password):\n        user = User.query.filter_by(username=self.username.data).first()\n        if user and not bcrypt.check_password_hash(\n                user.password, password.data):\n            raise ValidationError('Password doesn\\'t match. Please try again.')\n```\n\nThis form has the same fields as the signup form. This time, we actually need _two_ validators: The first is `validate_username`, which throws an error if the user _doesn't_ exist in the database. This makes sense - you can't log in as a user that doesn't exist yet!\n\nThe second validator is how we check that the user's password matches what is listed in the database. Here, we are checking that the user's password matches the password hash we stored in the database. Because we're storing a hash, and not the real password, we need to use the bcrypt `check_password_hash` function to decode it. If the passwords match, then `check_password_hash` returns `True`.\n\nSince this function uses `bcrypt` you need to import it at the of your file (forms.py)! \n\n```python\nfrom books_app.extensions import app, db, bcrypt\n```\n\n### The Sign Up Route\n\nIn `books_app/auth/routes.py`, let's fill out the sign up route:\n\n```py\n@auth.route('/signup', methods=['GET', 'POST'])\ndef signup():\n    form = SignUpForm()\n    if form.validate_on_submit():\n        hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')\n        user = User(\n            username=form.username.data,\n            password=hashed_password\n        )\n        db.session.add(user)\n        db.session.commit()\n        flash('Account Created.')\n        return redirect(url_for('auth.login'))\n    return render_template('signup.html', form=form)\n```\n\nThe first part of this route should look familiar! We are initializing a `SignUpForm` instance, and checking if it was submitted and is valid.\n\nIf it is valid, then we:\n\n- Generate a hashed password using the `bcrypt` library - this is very important for website security! **Never store user passwords in plaintext.** Always use a library like `bcrypt` to hash it first.\n- Create a new `User` object with the given username and hashed password and committing it to the database\n- Flash a success message to the user\n- Redirect the user to the login page so that they can log in with their newly created account.\n\nWhew, that was a lot! But we're not done yet, though - we still need to display the signup form to the user.\n\nFill out the `books_app/templates/signup.html` file with the following form contents:\n\n```html\n\u003cform action=\"/signup\" method=\"POST\"\u003e\n    {{ form.csrf_token }}\n    \u003cfieldset\u003e\n        {{ form.username.label }}\n        {{ form.username }}\n        \u003cul\u003e\n        {% for error in form.username.errors %}\n            \u003cli class=\"error\"\u003e{{ error }}\u003c/li\u003e\n        {% endfor %}\n        \u003c/ul\u003e\n\n        {{ form.password.label }}\n        {{ form.password }}\n        \u003cul\u003e\n        {% for error in form.password.errors %}\n            \u003cli class=\"error\"\u003e{{ error }}\u003c/li\u003e\n        {% endfor %}\n        \u003c/ul\u003e\n\n        {{ form.submit }}\n    \u003c/fieldset\u003e\n\u003c/form\u003e\n```\n\nThis will allow us to display any errors to the user so that they know to try again if their username or password is too long or short, or if their chosen username is taken.\n\nNow that that step is done, be sure to try out the `/signup` URL in your browser! What happens? Do you see your newly created user listed?\n\n### The Login Route\n\nIn `books_app/auth/routes.py`, let's fill out the login route so that our users can log in:\n\n```py\n@auth.route('/login', methods=['GET', 'POST'])\ndef login():\n    form = LoginForm()\n    if form.validate_on_submit():\n        user = User.query.filter_by(username=form.username.data).first()\n        login_user(user, remember=True)\n        next_page = request.args.get('next')\n        return redirect(next_page if next_page else url_for('main.homepage'))\n    return render_template('login.html', form=form)\n```\n\nBecause we did all of the heavy lifting of checking usernames \u0026 passwords in the form, here we can work based off of the assumption that the user's password matched and that the user exists in the database. Cool!\n\nNote that here, we're using a `next_page` variable to determine where to send the user to. This is because we may have routes in the future that require the user to be logged in. If the user tries to visit a page while logged out, they will be redirected to the login page. Using the `next` query parameter allows us to send the user back where they wanted to go once they are logged in.\n\nNext, let's fill out the template for the login route. Fill out the `books_app/templates/login.html` file with the following form contents:\n\n```html\n\u003cform action=\"/login\" method=\"POST\"\u003e\n    {{ form.csrf_token }}\n    \u003cfieldset\u003e\n        \u003clegend\u003eEnter your credentials\u003c/legend\u003e\n\n        {{ form.username.label }}\n        {{ form.username }}\n        \u003cul\u003e\n        {% for error in form.username.errors %}\n            \u003cli class=\"error\"\u003e{{ error }}\u003c/li\u003e\n        {% endfor %}\n        \u003c/ul\u003e\n\n        {{ form.password.label }}\n        {{ form.password }}\n        \u003cul\u003e\n        {% for error in form.password.errors %}\n            \u003cli class=\"error\"\u003e{{ error }}\u003c/li\u003e\n        {% endfor %}\n        \u003c/ul\u003e\n\n        {{ form.submit }}\n    \u003c/fieldset\u003e\n\u003c/form\u003e\n```\n\nThis one is pretty similar to the sign up template. As a stretch challenge, consider putting the form code in a partial template that can be re-used in both the login and signup routes.\n\nLet's also fill out the log out route in `books_app/auth/routes.py`:\n\n```py\n@auth.route('/logout')\ndef logout():\n    logout_user()\n    return redirect(url_for('main.homepage'))\n```\n\nFinally, let's add some code to the `base.html` file so that the user can see that they are logged in.\n\n**Modify the `books_app/templates/base.html` file** to add the following just above the `{% block content %}{% endblock %}`:\n\n```html\n{% if current_user.is_authenticated %}\n\u003cp\u003eYou are logged in as {{ current_user.username }}\u003c/p\u003e\n{% endif %}\n```\n\nNow, try out your routes. What happens when you log in? Is anything different?\n\n### Checking the `current_user` Object\n\nUsually, when we implement logins, we also want to hide some information from the user depending on their logged-in status.\n\nLet's start by modifying `templates/base.html` so that it doesn't show so many buttons. It's quite confusing to see a `Log In` button if you are already logged in!\n\nBecause we are using the Flask-Login library, it's super easy to check if a user is logged in. We can do so like the following:\n\n```html\n{% if current_user.is_authenticated %}\n    \u003c!-- stuff you only want to show to logged-in users --\u003e\n{% else %}\n    \u003c!-- stuff you only want to show to logged-out users --\u003e\n{% endif %}\n```\n\n**Modify the `base.html` file** so that it only shows the appropriate links to logged-in or logged-out users.\n\n### Protecting Routes\n\nEven though the buttons are hidden, a logged-out user could still go directly to the URLs for (for example) Create Book, Create Author, etc. But we want to restrict these routes to only logged-in users.\n\nWe can do this with the `login_required` decorator, which looks like this:\n\n```py\n@main.route('/create_book', methods=['GET', 'POST'])\n@login_required\ndef create_book():\n```\n\n**In `books_app/main/routes.py`, add `@login_required`** to the routes for Create Book, Create Author, and Create Genre.\n\n## Part 2: Challenge\n\nBy now, hopefully you've gotten the hang of using Flask-Login! Let's test your knowledge by completing the `favorite_book` and `unfavorite_book` routes.\n\nFirst, navigate to `book_detail.html` and complete the TODOs to appropriately display either the \"Favorite Book\" form or \"Unfavorite Book\" form to logged-in users.\n\nThen, navigate to `main/routes.py` and complete the TODOs in the `favorite_book` and `unfavorite_book` routes. (No need to complete the other routes.) Make sure to add `@login_required` to these routes, so that only logged-in users can favorite a book.\n\nMake sure to test your work!!\n\n\n## Resources\n\nIf you'd like more resources on working with Flask-Login, check out the following:\n\n- [Intro to Flask-Login [VIDEO]](https://www.youtube.com/watch?v=K0vSCCAM2ss) - an excellent 35 minute walkthrough that gives you the basics.\n- [Python Flask Tutorial: User Authentication [VIDEO]](https://www.youtube.com/watch?v=CSHx6eCkmv0)\n- [Flask-Login Quickstart](https://flask-login.readthedocs.io/en/latest/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fel634dev%2Facs-1220-auth-lab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fel634dev%2Facs-1220-auth-lab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fel634dev%2Facs-1220-auth-lab/lists"}