supabase-rb-rb
Controllers

SessionsController

Supabase::Rails::SessionsController — email + password sign-in and sign-out.

Supabase::Rails::SessionsController is the email + password sign-in / sign-out controller. The install generator writes a top-level SessionsController that inherits from it; routes flow through that subclass into the gem's action bodies.

Under the hood, create wraps the supabase-rb auth.sign_in_with_password call (via authenticate_with_supabase); destroy wraps auth.sign_out (via terminate_session).

# app/controllers/sessions_controller.rb (written by `bin/rails generate supabase:install`)
class SessionsController < Supabase::Rails::SessionsController
end

Source

# app/controllers/supabase/rails/sessions_controller.rb (in the gem)
class SessionsController < BaseController
  allow_unauthenticated_access only: %i[new create]

  def new; end

  def create
    if (supabase_session = authenticate_with_supabase(email: params[:email], password: params[:password]))
      start_new_session_for(supabase_session)
      redirect_to after_authentication_url,
                  notice: I18n.t("supabase.rails.sessions.created")
    else
      flash.now[:alert] = I18n.t("supabase.rails.sessions.invalid")
      render :new, status: :unauthorized
    end
  end

  def destroy
    terminate_session
    redirect_to root_path,
                notice: I18n.t("supabase.rails.sessions.destroyed")
  end
end

Routes

HelperVerbURLAction
new_session_pathGET/session/newnew
session_pathPOST/sessioncreate
session_pathDELETE/sessiondestroy

Mounted by resource :session, only: %i[new create destroy] inside supabase_authentication_routes. The resource (singular) form is what makes the URL /session (not /sessions).

Actions

new

Renders the sign-in form at /session/new. Action body is empty — Rails renders sessions/new.html.erb. Allowed without authentication.

The shipped view at app/views/supabase/rails/sessions/new.html.erb submits to session_path with two fields (email, password), the oauth/_buttons partial, and links to new_password_path and new_registration_path.

create

Authenticates the supplied params[:email] + params[:password] against Supabase Auth and writes the encrypted session cookie on success. Allowed without authentication.

Params

ParamSourceNotes
emailPOST bodyRequired. Forwarded to Supabase Auth verbatim.
passwordPOST bodyRequired. Max 72 bytes (bcrypt limit; the shipped view enforces this with maxlength: 72).

Outcome dispatch

BranchRedirect / renderStatusFlash
Success — session returnedredirect_to after_authentication_url302notice: I18n.t("supabase.rails.sessions.created")
Failure — 4xx upstream (invalid credentials, etc.)render :new401flash.now[:alert] = I18n.t("supabase.rails.sessions.invalid")
Failure — 5xx upstreamraises AuthError (status 503)503

The 4xx failure flash is generic — the action body calls authenticate_with_supabase, which returns nil on any 4xx (invalid email, invalid password, user not found, weak password) so the response leaks no information about which field was wrong. The 5xx case raises out of the action so the host's standard 500-page handler runs.

On success start_new_session_for writes the encrypted sb-session cookie and populates Current.user / Current.session. The post-sign-in redirect target comes from the after_authentication_url hook (defaults to stored_location_for_redirect || root_url).

destroy

Signs the user out. Required to be authenticated (no allow_unauthenticated_access for this action).

Params

None. The session being terminated is the request's own cookie.

Outcome dispatch

BranchRedirect / renderStatusFlash
Alwaysredirect_to root_path302notice: I18n.t("supabase.rails.sessions.destroyed")

terminate_session makes a best-effort upstream auth.sign_out call, then clears the encrypted cookie and Current.user / Current.session regardless of whether the upstream call succeeded. The local clear is the source of truth — an upstream 5xx will not leave the user signed in locally.

allow_unauthenticated_access

allow_unauthenticated_access only: %i[new create]

The class macro delegates to skip_before_action :require_authentication. new and create are exempted because an unauthenticated user must reach them to sign in. destroy stays gated — only authenticated users can sign out.

Hookable callbacks

Two override hooks shape create's redirect behaviour. Both are defined on the Authentication concern:

HookDefaultWhere to override
after_authentication_urlstored_location_for_redirect || root_urlapp/controllers/concerns/authentication.rb (preferred — applies to OTP/OAuth/registration too) or the host's SessionsController
store_location_for_redirectStashes request.url in session[:return_to_after_authenticating] on GET requests. Called by request_authentication when a gated action 302s to new_session_path.app/controllers/concerns/authentication.rb

For analytics / audit hooks alongside the gem's behaviour, redefine create in the host subclass and call super:

class SessionsController < Supabase::Rails::SessionsController
  def create
    super
    AnalyticsJob.perform_later(event: "sign_in", user_id: Current.user.id) if authenticated?
  end
end

For a Current.user.admin?-aware redirect, override after_authentication_url in the concern (so it applies to every post-auth redirect, including OTP/OAuth/registration):

# app/controllers/concerns/authentication.rb
module Authentication
  extend ActiveSupport::Concern
  include Supabase::Rails::Authentication

  private

  def after_authentication_url
    Current.user&.admin? ? admin_dashboard_path : stored_location_for_redirect || root_path
  end
end

Override patterns

Subclassing (the install-generator default) — the 3-line SessionsController < Supabase::Rails::SessionsController inherits every action body. Redefine the actions you want to change; the rest stay on the gem.

class SessionsController < Supabase::Rails::SessionsController
  def create
    super
    Rails.logger.info("Signed in: #{Current.user&.id}")
  end
end

Replacing the action wholesale — redefine without calling super. The authenticate_with_supabase, start_new_session_for, and terminate_session helpers are all still available from the included Authentication concern:

class SessionsController < Supabase::Rails::SessionsController
  def create
    email = params[:email].to_s.downcase.strip
    session = authenticate_with_supabase(email: email, password: params[:password])
    if session
      start_new_session_for(session)
      redirect_to dashboard_path, notice: t(".welcome", name: Current.user.email)
    else
      flash.now[:alert] = t(".invalid")
      render :new, status: :unauthorized
    end
  end
end

Replacing with a from-scratch controller — point the host subclass at Supabase::Rails::BaseController directly. The supabase_* low-level helpers (supabase_sign_in_with_password) give you the same upstream call wrapped in a Result so you can branch on stable error codes (INVALID_CREDENTIALS, WEAK_PASSWORD, AUTH_UPSTREAM_ERROR):

class SessionsController < Supabase::Rails::BaseController
  allow_unauthenticated_access only: %i[new create]

  def new; end

  def create
    result = supabase_sign_in_with_password(email: params[:email], password: params[:password])
    if result.success?
      redirect_to after_authentication_url, notice: t(".welcome")
    else
      flash.now[:alert] = case result.error.code
                         when Supabase::Rails::AuthError::WEAK_PASSWORD
                           t(".weak_password")
                         when Supabase::Rails::AuthError::AUTH_UPSTREAM_ERROR
                           t(".try_again")
                         else
                           t(".invalid")
                         end
      render :new, status: result.error.status
    end
  end

  def destroy
    terminate_session
    redirect_to root_path, notice: t(".bye")
  end
end

See also

On this page