janko / rodauth-rails

Rails integration for Rodauth authentication framework
https://github.com/jeremyevans/rodauth
MIT License
565 stars 40 forks source link

Can't login to two configurations at the same time #180

Closed chika-kasymov closed 1 year ago

chika-kasymov commented 1 year ago

I created two configurations with separate databases. Example:

## The base configuration
class RodauthBase < Rodauth::Rails::Auth
  configure do
    # List of authentication features that are loaded.
    enable :login, :logout # and other

    # remaining shared config
  end
end

## Seller config
class RodauthSeller < RodauthBase
  configure do
    accounts_table :seller_accounts # base feature
    verify_account_table :seller_account_verification_keys # verify_account feature
    verify_login_change_table :seller_account_login_change_keys # verify_login_change feature
    reset_password_table :seller_account_password_reset_keys # reset_password feature
    remember_table :seller_account_remember_keys # remember feature

    prefix "/seller"
    session_key_prefix "seller"
    remember_cookie_key "_seller_remember"

    # Specify the controller used for view rendering and CSRF verification.
    rails_controller { SellerAccount::RodauthController }
  end
end

## Customer config
class RodauthCustomer < RodauthBase
  configure do
    accounts_table :customer_accounts # base feature
    verify_account_table :customer_account_verification_keys # verify_account feature
    verify_login_change_table :customer_account_login_change_keys # verify_login_change feature
    reset_password_table :customer_account_password_reset_keys # reset_password feature
    remember_table :customer_account_remember_keys # remember feature

    prefix "/customer"
    session_key_prefix "customer"
    remember_cookie_key "_customer_remember"

    # Specify the controller used for view rendering and CSRF verification.
    rails_controller { CustomerAccount::RodauthController }
  end
end

## App
class RodauthApp < Rodauth::Rails::App
  # primary configuration
  configure RodauthSeller, :seller

  # secondary configuration
  configure RodauthCustomer, :customer

  route do |r|
    rodauth(:seller).load_memory # autologin remembered users
    rodauth(:customer).load_memory # autologin remembered users

    r.rodauth(:seller) # route rodauth seller requests
    r.rodauth(:customer) # route rodauth customer requests
  end
end

But for some reason, the session has always this format: _rails_app_name_session. I'm not sure if it's related to this library or to the rodauth. Does anybody have a similar issue?

janko commented 1 year ago

Rails session will always be stored (encrypted & signed) under a single cookie. The session_key_prefix modifies the keys that are used by Rodauth within the session data.

By default, Rodauth stores the logged in account ID under session[:account_id], but with session_key_prefix "seller" it will store it under session[:selleraccount_id]. BTW, that's why you typically want to end the prefix with an underscore, e.g. session_key_prefix "seller_", so that the session key ends up being session[:seller_account_id] 😉

If you've used Devise, it uses Warden which stores account data in separate subhashes inside session data, so all session data for the seller account would be saved to session["warden.user.seller.key"]. Rodauth takes a different approach, where it saves session data at the top-level, so that's why you need a prefix if you want to differentitate them.

The remember cookie is different, because that one really is stored as a separate cookie, and is independent from the Rails session cookie.

chika-kasymov commented 1 year ago

Oh, I see now. @janko thanks for the explanation!

In that case, my issue is not about the session key. Basically, if I log in with a new configuration (ex: seller) the previous one (ex: customer) logs out automatically. I want to be able to login to both configurations simultaneously. Is it possible to achieve?

janko commented 1 year ago

Yes, that's another difference in Rodauth, it resets the whole session on logout, to prevent session fixation attacks. However, that means that session data for other accounts are also cleared. Warden is a bit smarter, and it resets the session only when you told it to logout all users, otherwise it just deletes the session data for the specific account.

At the moment, I'm not sure how you could get the same behaviour with Rodauth, since it's not straightforward to get all session keys. I would try this:

class RodauthBase < Rodauth::Rails::Auth
  configure do
    clear_session do
      methods.grep(/_session_key$/).each |session_key_method|
        remove_session_value(send(session_key_method))
      end
    end
  end
end

However, then you're vulnerable to session fixation attacks. Pinging @jeremyevans in case he wants to chime in 🙂

chika-kasymov commented 1 year ago

Your suggested config helped to solve my issue. I edited it a little bit following a Github comment in one of your PRs :)

clear_session do
  methods.grep(/cookie|session/).grep(/_key$/).each do |session_key_method|
    session.delete(send(session_key_method))
  end
end

But I'll note about the session fixation attacks. Thanks!

janko commented 1 year ago

FYI, since the remember cookie is not part of the session, attempting to delete it from the session won't have any affect. Unless I'm mistaken, it should already be deleted automatically on logout.

chika-kasymov commented 1 year ago

You're correct. I deleted the cookie part.