Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New lesson: Session-based authentication #29082

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

MaoShizhong
Copy link
Contributor

Because

As part of the Node revamp's 2nd milestone

This PR

Issue

Closes #28847

Additional Information

Pull Request Requirements

  • I have thoroughly read and understand The Odin Project curriculum contributing guide
  • The title of this PR follows the location of change: brief description of change format, e.g. Intro to HTML and CSS lesson: Fix link text
  • The Because section summarizes the reason for this PR
  • The This PR section has a bullet point list describing the changes in this PR
  • If this PR addresses an open issue, it is linked in the Issue section
  • If any lesson files are included in this PR, they have been previewed with the Markdown preview tool to ensure it is formatted correctly
  • If any lesson files are included in this PR, they follow the Layout Style Guide

'Persisting logins' was a glorified intro anyway
Only up to the original login process
Reordered setup code blocks for easier content flow.
Decided to introduce the session store and secret explanations here
instead of their own sections later on (not really needed there).
New order allows for a more natural way of explaining how
express-session populates req.session.
That can be explained at the start and options explained to reflect
that, instead of explaining options in a bit of a black hole then
provide the context later in the lesson.
Made more sense to talk about logging out after logging in, then talk
about password hashing afterwords.
Use subsections for better content organisation and linking
@github-actions github-actions bot added the Content: NodeJS Involves the NodeJS course label Nov 13, 2024
"Session-based authentication" is a more appropriate title for the
lesson than just "Sessions", given that sessions are not exclusively
used for auth purposes.
@01zulfi 01zulfi self-requested a review November 13, 2024 16:19
Makes sense to showcase them directly rather than just via text example after the fact

#### Session store

Let's talk about our session config which we apply to every incoming request (by mounting it on `app`). Firstly, we use the [connect-pg-simple](https://www.npmjs.com/package/connect-pg-simple) library to make express-session store session data in our database (creating a "session" table if it does not already exist). Without this, sessions would be stored in memory by default which would not persist through any server restarts!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there pedagogical benefit to having the learner make their own sessions table and writing their own helper functions to do CRUD ops on them? If the aim of the lesson is to get to grips with sessions, I'm not sure it's a good idea to abstract away the actual persistent session info

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly. My understanding from prior discussion (which may be flawed) was to stay with express-session but handle it manually rather than as part of passport and its strategies. But if @01zulfi thinks it'll be even better to do this lesson with properly manual session management then the lesson can be amended accordingly.


By far the worst way we can store passwords is to just store them in plaintext like we've done in our example app earlier. Even if we encrypted the passwords, all an attacker would need is the key to decrypt all the passwords. Let's face it, if someone managed to gain access to your database, it probably wouldn't be very hard for them to get the encryption key (assuming they don't already have it).

Remember [hash functions](https://www.theodinproject.com/lessons/javascript-hashmap-data-structure#what-is-a-hash-code) from the Hashmap lesson? We want to hash our passwords, then store the hash since hashes are one-way functions. We also want to [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) the password when hashing to prevent identical passwords from being stored with identical hashes. On top of all that, we also want the hash function to be purposely slow - not so slow that a normal user will be waiting ages just to log in but certainly slow enough to minimize the number of attempts an attacker might be able to make in a given amount of time.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "prevent identical passwords from being stored with identical hashes" doesn't do the best job at making the point. I can see that we don't want to rabbit hole into rainbow tables here, but I wonder if "prevent attackers being able to compare hashes against precomputed hashes of common passwords" is just enough to get the idea across

const user = rows[0];

// argon2.verify requires an argon2 hash as the first argument
// so we must early return if there is no matching user
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a timing attack here, which leaks usernames. Do we think a note about this? Or just ignore it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised I glossed over this, considering I mention generic validation feedback earlier in the lesson. Will address this - thinking of using a predetermined argon2id hash as a fallback if user?.password short circuits so that argon2.verify() can still run fine. Then only go down the happy path if both user exists and password matches.

Any alternative suggestions?

Copy link
Contributor

@damon314159 damon314159 Nov 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's probably the simplest approach. You probably want to pick a huge random string to hash, since if you use something that someone actually tries as a password, you might accidentally grant a session to a user that doesn't exist. Maybe hash a uuid.
Obligatory I am not a cybersec guy message.

Or some logic such that if you use the fallback, you return false regardless, and the verify is awaited only as time wasting

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the random argon2 hash would just be to force the hash verification. Then change the following conditional to if (user && isMatchingPassword)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Content: NodeJS Involves the NodeJS course
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New Lesson: Sessions
2 participants