Skip to content

Conversation

@dhh
Copy link
Member

@dhh dhh commented Jul 14, 2024

Adds a basic sessions generator to get people started with their own authentication system. This is not intended to be an all-singing, all-dancing answer to every possible authentication concern. It's merely intended to illuminate the basic path, and reveal that rolling your own authentication system is not some exotic adventure.

So do not expect magic links or passkeys or 2FA. That's not going to happen with this generator.

Outstanding work:

  • Add tests

@dhh
Copy link
Member Author

dhh commented Jul 15, 2024

This is not meant to be a system where all factors are accounted for. User is the most common model name for the credentials holder. An account has_many users, but adding an account model is an exercise for the programmer.

@dixpac
Copy link
Contributor

dixpac commented Jul 16, 2024

Add --allow-resets which will add a reset password controller/mailer

Maybe, it would be more sensible to generate resets by default, and have --without-resets option or don't have that option at all. Shouldn't be to hard to remove it from the generated code if necessary

@smitssjors
Copy link

Genuine questions: why use a separate Session model over the build in session store (by default CookieStore)?

@dhh
Copy link
Member Author

dhh commented Jul 16, 2024

@smitssjors Because then you can do session indexes, alert when new sessions are created from unknown locations, do statistics on user agents, etc. Session tokens in the DB with a drip of metadata is a wonderful pattern for all of this, and something the majority of applications should use.

@dhh
Copy link
Member Author

dhh commented Jul 16, 2024

Actually, will tackle the idea of a password reset mailer/flow as a separate upgrade, if necessary. There are a significant number of views involved with that flow. On the fence about whether that's a good fit for a generator like this.

@dhh dhh marked this pull request as ready for review July 16, 2024 15:23
@dhh
Copy link
Member Author

dhh commented Jul 16, 2024

Unrelated test failures (again!).

@dhh dhh merged commit 7b1ceb7 into main Jul 16, 2024
@dhh dhh deleted the sessions-generator branch July 16, 2024 16:05
@fatkodima
Copy link
Member

Wdyt about enabling automatic squashing when merging PRs?

Schwad pushed a commit to Shopify/rails that referenced this pull request Jul 18, 2024
* Add basic sessions generator

* Excess CR

* Use required fields

* Add sessions generator test

* Fix generated migration

* Test migration content

* Appease rubocop

* Add CHANGELOG
@@ -0,0 +1,64 @@
module Authentication
Copy link
Contributor

Choose a reason for hiding this comment

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

Why isn't this called Authenticate like in 37signals apps? Makes more sense imo since controllers are almost always plural, and they authenticate requests versus the singular more object-oriented approach for models.

Choose a reason for hiding this comment

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

The module is named Authentication because it covers everything related to authentication, not just a single action. Rails prefers naming modules after what they do overall, which makes the code easier to understand and maintain.

Rails conventionally prefers naming modules and concerns in a way that represents concepts or groups of behaviours. This makes the naming consistent with other modules such as Authorization, Logging, Monitoring, etc.

Besides, Writebook name their model Authentication.

CleanShot 2024-07-20 at 17 57 13@2x

rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_url, alert: "Try again later." }

def new
redirect_to root_url if authenticated?

Choose a reason for hiding this comment

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

I don't think this redirect will work. It looks like Current.session is only being set through require_authentication which gets skipped when using allow_unauthenticated_access. So Current.session would always end up nil here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in 1dd37e0

@ezekg ezekg mentioned this pull request Jul 20, 2024
8 tasks

def start_new_session_for(user)
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
set_current_session session
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: The name session could be confused with request's session object. Should it be called current_session for clarity ?

Same for set_current_session(session)

Copy link

@Cluster444 Cluster444 Jul 25, 2024

Choose a reason for hiding this comment

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

Yup, in fact authenticated? seems pointless with this implementation. There's 3 paths here

  1. require_authentication && resume > authenticated? is always true
  2. require_authentication && request > it redirects to login
  3. skip_authentication > authenticated? is always false

Seems like resume session needs to be called regardless, and request_authentication is the skippable part

In my setup, which is very similar to this I have 3 before action type helpers

require_authentication (resume || redirect_to new_session)
allow_authentication (resume)
despise_authentication (resume && redirect_to root)

require covers most of the cases, allow is useful in some areas where it's not necessary but we don't forbid, and despise will kick you out if your signed in. Think invitation/signup type controllers.

@uxxman
Copy link
Contributor

uxxman commented Aug 4, 2024

Why store a long string in database and then run a sequential find by query for finding a Session? @dhh

Why can't we use signed_id for this purpose here so that the look up would be done using the integer primary key?

So, we can drop the token column for sessions and instead of doing

Session.find_by(token: token)

# and 

cookies.signed.permanent[:session_token] = { value: session.token, httponly: true, same_site: :lax }

we could do

Session.find_signed(token)

# and 

cookies.signed.permanent[:session_token] = { value: session.signed_id, httponly: true, same_site: :lax }

PR => #52504

packagethief added a commit to basecamp/fizzy that referenced this pull request Aug 20, 2024
DanielaVelasquez pushed a commit to DanielaVelasquez/rails that referenced this pull request Oct 3, 2024
* Add basic sessions generator

* Excess CR

* Use required fields

* Add sessions generator test

* Fix generated migration

* Test migration content

* Appease rubocop

* Add CHANGELOG
@rutte
Copy link

rutte commented Nov 17, 2024

Could it be possible to change what id is for us who prefer uuid v4, without hassle with migrations? The generator seems to assume everyone uses sequential bigint for ids.

@rutte
Copy link

rutte commented Nov 17, 2024

mongo?

end

private
def authenticated?
Copy link

@emiliocm9 emiliocm9 Jan 7, 2025

Choose a reason for hiding this comment

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

We need to adjust the indentation in this file

@laptopmutia
Copy link

This is not meant to be a system where all factors are accounted for. User is the most common model name for the credentials holder. An account has_many users, but adding an account model is an exercise for the programmer.

I agree with the user but I'm wondering why you choose email_address over email as the default

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.