35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English 35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English
Rails 8 Authentication Generator: Bouw Productie-Sessies Zonder Devise

Rails 8 Authentication Generator: Bouw Productie-Sessies Zonder Devise

Roger Heykoop
Ruby on Rails, Security
Rails 8 authentication generator in productie: sessies, wachtwoord reset, rate limiting, OAuth en het migratiepad vanaf Devise na negentien jaar Rails.

Een SaaS-oprichter stuurde me vorige maand een bericht met de vraag of het eindelijk veilig was om een Rails 8 app te lanceren zonder Devise. Hij was drie jaar geleden gebrand door een Devise-upgrade die zijn omniauth-flow een week lang stuk had gemaakt, en hij wilde weten of de nieuwe ingebouwde generator productiewaardig was of slechts een demo. Het korte antwoord: ja, hij is productiewaardig. Het lange antwoord is dat de Rails 8 authentication generator nu mijn standaardkeuze is voor greenfield-projecten, en na negentien jaar Rails heb ik een mening over waarom.

Devise heeft nog steeds zijn plek — als je confirmable e-mails, lockable accounts, OmniAuth providers en account-uitnodigingen out of the box nodig hebt, is het nog steeds minder code dan zelf bouwen. Maar voor de zeventig procent van de apps die alleen e-mail + wachtwoord + sessies + wachtwoord reset nodig hebben, geeft de Rails 8 authentication generator je een fundament dat je daadwerkelijk bezit en in twintig minuten van begin tot eind kunt lezen.

Wat de Rails 8 Authentication Generator Eigenlijk Genereert

Voer bin/rails generate authentication uit in een Rails 8 app en je krijgt een kleine, eigenwijze stack: een User model met has_secure_password, een Session model dat cookies en IP-adressen bijhoudt, een PasswordsMailer, en drie controllers — SessionsController, PasswordsController, en een Concerns::Authentication module die je includeert in ApplicationController. Dat is het. Geen engine, geen DSL, geen devise_for :users route die tien controllers achter één regel verbergt.

Het session-model is het deel dat de Rails 8 authentication generator meer maakt dan een speeltje. In plaats van een user id in de cookie-sessie te bewaren en die te vertrouwen, maakt de generator een sessions-tabel met een user_id, user_agent en ip_address, en bewaart hij een signed cookie met alleen het session id. Uitloggen vernietigt de rij. Gestolen cookies werken niet meer zodra je de sessie verwijdert. Hetzelfde bouwen op Devise kost een custom Warden-strategie en een paar weekenden.

class Session < ApplicationRecord
  belongs_to :user
end

class User < ApplicationRecord
  has_secure_password
  has_many :sessions, dependent: :destroy

  normalizes :email_address, with: ->(e) { e.strip.downcase }

  validates :email_address, presence: true, uniqueness: true
end

Dat is de complete kern van de Rails 8 authentication generator. Lees het, begrijp het, verander het. Geen magie.

Waarom Ik Devise Niet Meer Reflexmatig Pak voor Greenfield Projecten

Devise is een van de beste gems in het Rails-ecosysteem. Ik gebruik het in productie sinds 2010 en het heeft me door tientallen apps gedragen. Maar het heeft een soort wrijving opgebouwd die de Rails 8 authentication generator door zijn ontwerp vermijdt.

Wanneer iets stuk gaat in Devise, debug je door controllers die je niet hebt geschreven, modules in modules, en een routing-laag die opzettelijk ondoorzichtig is. Ik heb hele middagen besteed aan uitzoeken waarom een before_action twee keer draaide, of waarom current_user nil was binnen een callback die na sign_in vuurde. De Rails 8 authentication generator heeft in totaal ongeveer tweehonderd regels code. Wanneer iets stuk gaat open je het bestand, fix je het en ga je verder.

Devise maakt ook moeilijke dingen makkelijk en makkelijke dingen vreemd. Rate limiting toevoegen aan login? Je patcht een controller die je niet bezit. Een custom claim toevoegen aan de session cookie? Je overschrijft een Warden-strategie. Elke succesvolle aanmelding loggen naar je audit-tabel? Je haakt in op een Devise-event. Met de Rails 8 authentication generator pas je gewoon SessionsController#create aan. Het mentale model klapt in elkaar.

De afweging is eerlijk: als je OmniAuth nodig hebt voor Google en GitHub, account-bevestigings-e-mails, account-vergrendeling na N mislukte pogingen, en de mogelijkheid voor admins om gebruikers te impersoneren, dan is Devise plus een paar extensies nog steeds minder code. Kies het juiste gereedschap. Maar pak Devise niet reflexmatig.

Rate Limiting Toevoegen aan de Rails 8 Authentication Generator

De generator komt niet met rate limiting op het login-endpoint. Dat is het eerste wat ik in productie toevoeg. Rails 8 levert ActionController::RateLimiting mee en de integratie is één blok:

class SessionsController < ApplicationController
  allow_unauthenticated_access only: %i[ new create ]

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

  def create
    if user = User.authenticate_by(params.permit(:email_address, :password))
      start_new_session_for user
      redirect_to after_authentication_url
    else
      redirect_to new_session_url, alert: "Probeer een ander e-mailadres of wachtwoord."
    end
  end

  def destroy
    terminate_session
    redirect_to new_session_url
  end
end

Tien pogingen per drie minuten per IP is een redelijke standaard voor een B2B app. Voor consumenten-apps met gedeelde NAT (denk aan bedrijfsnetwerken achter één egress IP) wil je sleutelen op e-mail + IP in plaats van alleen IP, anders sluiten echte gebruikers elkaar uit. Dat is een wijziging van vijf regels in de rate limiter:

rate_limit to: 10, within: 3.minutes, only: :create,
           by: -> { "#{request.remote_ip}:#{params.dig(:email_address)&.downcase}" },
           with: -> { redirect_to new_session_url, alert: "Probeer het later opnieuw." }

Combineer dit met Rack::Attack aan de rand voor een grovere laag (blokkeer IPs na duizend requests in vijf minuten ongeacht route) en je hebt verdediging in de diepte.

Wachtwoord Reset die het Bestaan van Gebruikers niet Lekt

De PasswordsController van de Rails 8 authentication generator is goed, maar de standaard create actie zal een aanvaller vertellen of een e-mail in je database bestaat, afhankelijk van hoe je het bericht flasht. Repareer dit in de controller:

class PasswordsController < ApplicationController
  allow_unauthenticated_access
  before_action :set_user_by_token, only: %i[ edit update ]

  def create
    if user = User.find_by(email_address: params[:email_address])
      PasswordsMailer.reset(user).deliver_later
    end
    redirect_to new_session_path,
                notice: "Als dat e-mailadres bestaat, hebben we instructies voor het resetten verzonden."
  end

  def update
    if @user.update(params.permit(:password, :password_confirmation))
      redirect_to new_session_path, notice: "Wachtwoord is gereset."
    else
      redirect_to edit_password_path(params[:token]),
                  alert: "Wachtwoorden kwamen niet overeen."
    end
  end

  private
    def set_user_by_token
      @user = User.find_by_password_reset_token!(params[:token])
    rescue ActiveSupport::MessageVerifier::InvalidSignature
      redirect_to new_password_path, alert: "Wachtwoord-reset link is ongeldig of verlopen."
    end
end

Dezelfde respons of de e-mail nu bestaat of niet. Dezelfde timing (de find_by is snel genoeg dat het verschil onder de netwerkruis ligt). Reset-tokens worden gesigneerd met een vervaltijd van vijftien minuten via het gebruik van generates_token_for door de generator. Dat is de lat; lever niet daaronder.

Sessiebeheer dat Mensen Daadwerkelijk Gebruiken

De Rails 8 authentication generator slaat één rij per sessie op, wat betekent dat je in ongeveer dertig regels een “beheer je apparaten” pagina kunt bouwen. Dit is een feature die je gebruikers daadwerkelijk waarderen nadat ze hun laptop in een vliegtuig hebben laten liggen:

class SessionsController < ApplicationController
  before_action :resume_session, only: :index
  before_action :require_authentication, only: %i[ index destroy ]

  def index
    @sessions = Current.user.sessions.order(created_at: :desc)
  end

  def destroy
    Current.user.sessions.find(params[:id]).destroy
    redirect_to sessions_path, notice: "Uitgelogd op dat apparaat."
  end
end

Render in de view session.user_agent geparseerd door een kleine gem zoals useragent zodat gebruikers “Chrome op macOS” zien in plaats van een Mozilla-string uit 1998. Markeer de huidige sessie. Voeg een “Overal uitloggen” knop toe die Current.user.sessions.destroy_all aanroept en je hebt pariteit met wat Google en GitHub aanbieden.

De Rails 8 authentication generator maakt dit goedkoop omdat het session-model van jou is. Wil je last_seen_at bijwerken op elke geauthenticeerde request, voeg dan een kolom toe en update die vanuit resume_session. Probeer dat eens schoon te doen via Devise’s Warden-integratie.

OAuth Toevoegen Zonder OmniAuth Binnen te Halen

De Rails 8 authentication generator heeft geen mening over OAuth, en dat is prima. Voor de meeste apps die “Inloggen met Google” nodig hebben, heb je het OmniAuth-ecosysteem niet nodig — je hebt drie dingen nodig: een authorize redirect, een callback die de code inwisselt voor een token, en een gebruikerszoekopdracht of aanmaakstap.

class Oauth::GoogleController < ApplicationController
  allow_unauthenticated_access

  def show
    redirect_to "https://accounts.google.com/o/oauth2/v2/auth?" + {
      client_id: Rails.application.credentials.google.client_id,
      redirect_uri: oauth_google_callback_url,
      response_type: "code",
      scope: "openid email profile",
      state: form_authenticity_token
    }.to_query, allow_other_host: true
  end

  def callback
    raise ActionController::InvalidAuthenticityToken if params[:state] != form_authenticity_token

    token  = exchange_code(params[:code])
    claims = JWT.decode(token["id_token"], nil, false).first

    user = User.find_or_create_by!(email_address: claims["email"]) do |u|
      u.password = SecureRandom.hex(32)
    end

    start_new_session_for user
    redirect_to after_authentication_url
  end

  private
    def exchange_code(code)
      Net::HTTP.post_form(URI("https://oauth2.googleapis.com/token"), {
        code: code, client_id: Rails.application.credentials.google.client_id,
        client_secret: Rails.application.credentials.google.client_secret,
        redirect_uri: oauth_google_callback_url, grant_type: "authorization_code"
      }).then { |r| JSON.parse(r.body) }
    end
end

Honderd regels en je hebt Google sign-in dat integreert met het session-model van de Rails 8 authentication generator. Voeg GitHub toe met dezelfde template. Voeg Microsoft toe wanneer een klant erom vraagt. Je schrijft meer code dan bij het installeren van omniauth-google-oauth2, maar je leest elke regel en je CSP- en CSRF-gedrag komt overeen met de rest van de app. Dit is het soort afweging waar de Rails 8 authentication generator je naar toe duwt — bezit het oppervlak dat ertoe doet.

Voor details over het veilig opslaan van die credentials, zie Rails Credentials and Secrets Management for Production.

Migreren van Devise naar de Rails 8 Authentication Generator

Als je een bestaande Devise-app hebt en wilt overstappen naar de Rails 8 authentication generator, is de migratie mechanisch maar saai. Doe het niet tijdens een feature-push.

De wachtwoord-kolom is het makkelijke deel. Devise slaat BCrypt-hashes op in encrypted_password en has_secure_password slaat ze op in password_digest. Het is hetzelfde algoritme en dezelfde kosten. Voeg een kolom toe, kopieer de data, drop de oude in een vervolg-deploy:

class MigrateDeviseToHasSecurePassword < ActiveRecord::Migration[8.0]
  def up
    add_column :users, :password_digest, :string
    User.in_batches.update_all("password_digest = encrypted_password")
  end
end

Het moeilijke deel zijn sessies. Devise-gebruikers worden ingelogd via een cookie die direct naar een user id mapt. De Rails 8 authentication generator gebruikt een session-rij. Schakel over door beide naast elkaar te deployen: houd Devise actief voor bestaande logins, route nieuwe logins via de nieuwe SessionsController, en voeg een before_action toe die een Devise-cookie detecteert, een Session-rij ervoor aanmaakt, en de gebruiker uit Devise uitlogt. Binnen een week zijn al je gebruikers natuurlijk gemigreerd, en kun je Devise uit de Gemfile halen.

Als je custom Warden-strategieën hebt gebouwd of vertrouwt op Devise-modules zoals :trackable of :lockable, port ze dan bewust. trackable wordt kolommen op User die worden bijgewerkt vanuit resume_session. lockable wordt een failed_attempts-teller en een locked_until-timestamp die wordt gecontroleerd in User.authenticate_by. Niets hiervan is moeilijk. Het is gewoon werk, en het is werk waar je blij mee bent zal zijn de volgende keer dat er om 2 uur ‘s nachts iets stukgaat.

Voor het bredere draaiboek voor gefaseerd Rails-werk zoals dit, zie Rails Upgrade: An Incremental Strategy.

Productie Hardening Checklist

Voordat je de Rails 8 authentication generator productie-klaar noemt, loop je deze lijst door. Niets ervan is optioneel voor een echte app:

  • Forceer HTTPS met config.force_ssl = true en HSTS-headers. Sessies verzonden over HTTP zijn sessies gestolen op de wifi van een koffietent.
  • Stel de session cookie SameSite in op Lax, of Strict als je geen cross-site navigatieflows nodig hebt. De generator gebruikt standaard Lax; verzwak dat niet.
  • Hash reset-tokens in rust als je ze opslaat. De generator gebruikt signed tokens die niet worden opgeslagen, wat correct is — verander dit niet.
  • Log authenticatie-events naar je audit-tabel vanuit start_new_session_for en terminate_session. Je wilt dit de eerste keer dat een klant vraagt “is er iemand vanuit Rusland in mijn account ingelogd?”
  • Stuur sign-in notificatie e-mails voor nieuwe apparaten. Vergelijk user_agent en ip_address met de recente sessies van de gebruiker en mail ze bij een mismatch.
  • Roteer de session cookie bij privilege-escalatie (wanneer een admin-rol wordt toegekend, wanneer een wachtwoord wordt gewijzigd). terminate_session; start_new_session_for(user) doet dit in één regel.
  • Test wachtwoord-reset end-to-end met een integratietest die de e-mail-body bevat. Het aantal apps dat met kapotte resetflows live gaat is deprimerend.
  • Voeg CAPTCHAs toe bij herhaalde mislukkingen, niet bij elke login. CAPTCHAs bij elke aanmelding zijn een UX-ramp; CAPTCHAs na drie mislukte pogingen zijn redelijk.

Sla de rate limiting en de audit-logging niet over. Dat zijn de twee die in elke security-review terugkomen en de twee die het makkelijkst correct zijn toe te voegen terwijl je nog aan het bouwen bent.

Veelgestelde Vragen

Is de Rails 8 authentication generator productie-klaar?

Ja, met twee voorbehouden: voeg rate limiting toe op de login- en wachtwoord-reset-endpoints, en voeg audit-logging toe op session create en destroy. De generator geeft je out of the box een session-model, BCrypt wachtwoord-hashing via has_secure_password, signed reset-tokens en CSRF-bescherming. Dat is hetzelfde fundament waarop Devise zit. Het overige werk is hetzelfde hardening-werk dat je op elke auth-stack zou doen.

Moet ik mijn bestaande Devise-app migreren naar de Rails 8 authentication generator?

Waarschijnlijk niet, tenzij je een reden hebt. Devise is stabiel en goed onderhouden, en een migratie is echt werk zonder voor de gebruiker zichtbaar voordeel. Migreer wanneer je toch al authenticatie aanraakt om een andere reden — OAuth toevoegen, een ingewikkelde session-flow vereenvoudigen, of een Devise-module verwijderen die je niet meer nodig hebt. Greenfield-projecten zijn een ander verhaal; pak daar eerst de Rails 8 authentication generator.

Hoe verhoudt de Rails 8 authentication generator zich tot Devise voor OmniAuth?

Devise plus devise-omniauth is minder code als je drie of meer OAuth-providers nodig hebt. De Rails 8 authentication generator heeft geen OmniAuth-integratie, dus schrijf je de OAuth-flow zelf per provider. Voor één of twee providers is het zelf doen prima en geeft het je volledige controle over de callback. Voor vijf providers met enterprise SSO, SAML en social login, installeer OmniAuth en koppel het aan het session-model van de generator.

Kan ik de Rails 8 authentication generator gebruiken met API-only Rails apps?

Ja, maar je vervangt de cookie session storage door token storage. Houd de sessions tabel; in plaats van het session id op te slaan in een signed cookie, geef je het terug als een bearer token in de Authorization header. De session-rij, wachtwoord-hashing en reset-flow blijven identiek. Dit geeft je token-gebaseerde API-auth zonder een aparte gem zoals devise-jwt.

Hulp nodig bij het lanceren van productie-klare authenticatie op Rails 8, of bij migratie van een ingewikkelde Devise-setup? TTB Software is gespecialiseerd in Rails security, architectuur en fractional CTO-werk voor groei-fase SaaS. We doen dit al negentien jaar.

#rails-8-authentication-generator #rails-authentication-without-devise #rails-sessions-cookies #rails-password-reset #rails-bcrypt-has-secure-password #rails-authentication-rate-limiting #ruby-on-rails
R

About the Author

Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.

Get in Touch

Share this article

Need Expert Rails Development?

Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise

Schedule a Free Consultation