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
endSource
# 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
endRoutes
| Helper | Verb | URL | Action |
|---|---|---|---|
new_session_path | GET | /session/new | new |
session_path | POST | /session | create |
session_path | DELETE | /session | destroy |
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
| Param | Source | Notes |
|---|---|---|
email | POST body | Required. Forwarded to Supabase Auth verbatim. |
password | POST body | Required. Max 72 bytes (bcrypt limit; the shipped view enforces this with maxlength: 72). |
Outcome dispatch
| Branch | Redirect / render | Status | Flash |
|---|---|---|---|
| Success — session returned | redirect_to after_authentication_url | 302 | notice: I18n.t("supabase.rails.sessions.created") |
| Failure — 4xx upstream (invalid credentials, etc.) | render :new | 401 | flash.now[:alert] = I18n.t("supabase.rails.sessions.invalid") |
| Failure — 5xx upstream | raises 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
| Branch | Redirect / render | Status | Flash |
|---|---|---|---|
| Always | redirect_to root_path | 302 | notice: 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:
| Hook | Default | Where to override |
|---|---|---|
after_authentication_url | stored_location_for_redirect || root_url | app/controllers/concerns/authentication.rb (preferred — applies to OTP/OAuth/registration too) or the host's SessionsController |
store_location_for_redirect | Stashes 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
endFor 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
endOverride 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
endReplacing 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
endReplacing 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
endSee also
- Controllers overview — the full route table and the override pattern.
BaseController— the common parent class.Authenticationconcern —authenticate_with_supabase,start_new_session_for,terminate_session,after_authentication_url.supabase_sign_in_with_password—Result-returning low-level alternative used by from-scratch overrides.supabase:viewsgenerator — copysessions/new.html.erbinto the host app to restyle the form.- supabase-rb:
auth.sign_in_with_passwordandauth.sign_out— the underlying Ruby methods these actions delegate to.