Rails authentication
Status: Implemented for the Field-only public Web Account boundary. Registration meaning outside it is listed as partial gaps.
Account meaning lowers to Rodauth Rails with email and password, plus self-service registration, email verification,
password reset, and lockout when the Account requests each. Public signup collects the Account's required Fields with
Scaffold-style controls, and the GapSet lists registration meaning it cannot generate. Generated Rails views and a
generated mailer replace Rodauth's built-in rendering. Before Rodauth inserts the Account, a hook runs the full Active
Record validation stack on the submitted product values. Every realized Account receives one authenticated /account
page that reads current_account. Every sign-in is remembered with Rodauth remember, in browsers and native clients
alike. Selected iPhone and Android clients receive the Account entry and sign in through the generated forms; Native
Accounts describes the flow. Two private qualifiers exercise Association
registration inputs and an iPhone session. The Accounts owner (repository-only) states what Account topology
means.
On this page
- Account prerequisite
- Public registration
- Account self and navigation
- Generated files
- Create-account form
- Rodauth configuration
- Accounts without verification
- Accounts without registration, recovery, or lockout
- Private qualifiers
- Code and checks
Account prerequisite
Support: see the support inventory row (repository-only).
Model Account semantics first and lower them with Rodauth Rails in the first profile. The pure prerequisite accepts every email/password topology, including the ordinary representative corpus and the reduced Movie Catalog fixture, retains Analyzer-valid registration inputs for the Account Entity, and qualifies the fixed runtime namespace: Account columns, password methods, feature-dependent model Associations, derived nested constants, top-level runtime constants, and middleware routes. Model members and top-level constants are checked against target and Project claims. Nested constants are self-checked against the Rodauth Associations. Middleware routes are checked against the pinned target route namespace; general Project-route qualification remains deferred. Ordinary public input consumes this result for the Field-only Web boundary below.
AccountLowering is the target prerequisite shared by ordinary public input and the private qualifiers. It consumes
the exact ordered Domain catalog from one sealed Project generation, a valid same-generation Reference catalog, and an
existing Account registration-analysis result without SQL. It admits every email/password Account topology, enabling
each optional flow only when requested, and retains Analyzer-valid registration inputs for that Account Entity.
Ordinary public input then applies the stricter Field-only projection below; private qualifiers retain their reduced
fixture shapes. Its immutable result distinguishes a declined Plan from a selected but blocked Account, retains the
registration-analysis result as evidence, and owns only target-lowering diagnostics. The Account value exists only
when both stages succeed.
The value retains the Account Entity, analyzed default bindings, observed conventional Rodauth dependency baseline,
fixed feature set, target-owned storage, runtime namespace, and migration rank after Domain References. Its namespace
claims the Account columns, password interface, feature-dependent model Associations, derived nested constants,
fixed top-level constants, and middleware routes. The target-owned top-level set includes RodauthApp,
RodauthController, RodauthMailer, and RodauthMain. Focused analysis tests source-address Project member and
constant collisions, self-check the nested constants, and reject middleware overlap with the pinned target route
namespace. Every Account adds the application-wide remember feature and its table. Selecting iOS or Android adds
the /native-session-changed middleware route on every path.
A complete Rails lowering for Account meaning would derive:
- authentication-owned storage and model integration;
- sign-in, sign-out, registration, verification, recovery, and lockout flows requested by the Account;
- sessions, routes, controllers, mail, and secure parameter handling;
- a runtime representation of
current_accountfor Policies, supported defaults, and Scaffold requests; - create-request assignment for bindings whose Value is
current_accountor one of its Fields or References; - external transactional-mail prerequisites;
- allowed, denied, expired, recovery, and lockout behavior tests; and
- setup and repository guidance for owner-controlled credentials.
The first pure target prerequisite now derives the Account Entity's table, reserved email and account_status
columns, one authentication migration slot, and the auxiliary table names of the requested flows. All auxiliary
names use the Account Entity stem,
including user_password_hashes; the emitted configuration overrides each of Rodauth's independent account_* table
defaults explicitly. Within the current reduced Domain slice, the prerequisite permits Analyzer-valid registration
inputs and retains each source binding's identity, authored position, source pointer, and requiredness plus every
analyzed default binding. Every Account enables i18n, login, logout, remember, change_login,
change_password, and the target-chosen disallow_common_passwords safeguard. Registration adds create_account,
recovery adds reset_password, and lockout adds lockout. Verification adds verify_login_change, and also
verify_account when the Account has registration. Each optional feature brings only its own auxiliary tables, nested
models, and routes. It derives and self-checks the Rodauth model's
nested Association constants without claiming a general nested-constant namespace. The top-level set includes
dependency-owned BCrypt, Roda, Rodauth, and Sequel plus RodauthApp, RodauthController, RodauthMailer,
and RodauthMain. A Domain Entity such as Sequel therefore blocks the current Account qualification before source
emission. That is a Rails target gap, not invalid Account or Entity meaning in the Foundation Plan; a different or
later lowering may omit affected output or rename only compiler-private machinery while preserving public vocabulary.
That prerequisite records the exact dependency versions exercised by the conventional application: Roda 3.105.0,
Rodauth Rails 2.1.2, Rodauth 2.44.0, and their named authentication support dependencies. Generated applications use
Rodauth Rails 2.2.0 and
Rodauth 2.48.0, whose real source still exposes the
conventional storage configuration and remember APIs. Rodauth 2.48.0 includes the 2.47.0 security
fixes, which make each emailed key work once
under concurrent requests. The older observed lock remains a
reproducibility fact for the conventional application. The isolated generated bundle routes Gemfile and
Gemfile.lock through the application-wide dependency plan. Account contributes its complete immutable Gemfile block,
four direct requirements, empty prerequisite list, and source identity but owns neither file. ApplicationTaskPlan
retains one plan: Core returns its exact pair without projection, while a nonempty contributor set is inserted once at
Core's dependency marker, projected exactly once by the exact vendored Bundler 4.0.10, and closure-verified against
the maximal universe. No production Account Compilation consults an overlay. The resulting Account selection pins
bcrypt 3.1.22, Roda 3.106.0, Rodauth 2.48.0, rodauth-i18n 0.11.0, rodauth-model 0.5.0, rodauth-rails 2.2.0, Sequel
5.107.0, and sequel-activerecord_connection 2.0.1.
Public registration
Public Account realization first requires one email identifier, one password sign-in method, and a successfully qualified Account-self surface. Registration, verification, recovery, and lockout may each be present or omitted; the projection below is empty without registration. The Accounts owner (repository-only) lists the signup members and the partial gaps for registration meaning the target cannot generate. An Association input, a required Reference, or an unsupported input no longer leaves the Account unrealized. Ordinary output gives selected native clients the same navigation as Web, Account self included, with or without email verification. Public Policy admission consumes this exact Account result and admits no Policy root without it. When Account and admitted State Machine behavior coexist, the dependency planner composes their qualified Gemfile blocks and projects one complete lock from the maximal universe. When admitted public Policy rules also coexist, the same projection adds Action Policy 0.7.6, ruby-next-core 1.2.1, and AASM 6.0.0 without a combination lookup or lockfile-section splice.
Ordinary input builds an immutable registration projection from the authored inputs it can generate, followed by a
derived input for every other required stored Account Field that the model does not fill. Each input carries the
Scaffold control kind for its Field: short_text and time_zone keep their text inputs, an enum becomes a select of
its values, and the other stored kinds use the Scaffold helper for that kind. The projection also carries one omission
per unsupported subject, which becomes a foundation_plan.gap.account.partially_generated record. Before the Sequel
insert, the hook assigns every input, runs validations, and copies each input plus each required State Machine,
realized-default, secure token, or appended position Field from the Rails model into the insert. When an omission
blocks signup, such as a required Reference, a failed validation reports the model's full messages instead of a
generic invalid-input message. Such a signup cannot succeed for any input, so it conflicts with the
code-worth-keeping requirement. Omitting signup would leave no
way into the application, so the owner decided that
format 0.24 will reject (repository-only) a required Account Reference that
signup cannot supply. A proposed Skill change teaches group
membership in its place. Ordinary input then qualifies the exact
Account self result and carries all three values as input-owned evidence. The default /account edit form uses the
same control kinds. When an admitted State Machine and Account both contribute dependencies, the dependency plan
composes their qualified Gemfile blocks and projects one complete lock from the maximal universe.
Account self and navigation
Every realized Account path derives one browser Account entry. ApplicationNavigation preserves its semantic
person icon and fixed /account destination. Web navigation places the localized Account link after resource
entries and before Sign out. It renders only for current_account, which exists only when Rodauth reports
logged_in?. All Account controller actions use that current record, never a supplied record ID.
Without an authored profile, /account shows signup Fields and the normalized email. Without an authored update,
/account/edit and PATCH /account permit the mutable, non-derived signup Fields. An update-only customization
replaces editing inputs and the update Policy while retaining signup details. Custom scaffold.profile instead
supplies the displayed details and collections, guarded by its read Policy. Display and edit selections are
independent. A denied profile hides its details and queries while retaining the Account destination
and credential links. Unsupported custom definitions remain gaps and do not activate permissive defaults.
The serialized return route profile resolves to Account. Native clients reach the same /account page; for a
Hotwire Native request its credential actions also include Sign out, because the native layout omits the web
header.
The toolbar uses stock ghost actions with matching Lucide icons for Edit details, Change email, and
Change password. Credential links use Rodauth's change_login, verify_login_change, and change_password
routes. Email change retains the existing address until verification at the new one. The login_change_keys
auxiliary table, nested Rodauth model, and mailer follow rodauth-rails 2.2.0's generator and Rodauth 2.48.0's
features. Rodauth's own key lookup enforces the one-day email-change deadline and locks the key row, so a link works
once. Profile fields remain ordinary Active Record updates with Rails errors.
The pure AccountSelf qualifier consumes that exact Account lowering and Domain catalog. It derives the fixed
/account path, account helper, AccountsController, accounts view namespace, account-self:<Account subject_uuid> identity, Account Entity label, semantic person icon, and source provenance. It rejects collisions
with the target namespace, Account authentication middleware routes, Project constants, public-index claims, and the
private Policy entry. ApplicationNavigation combines this optional result with the exact public-index projection and
preserves same-generation diagnostics. Its semantic entries contain no SF Symbol, tab-bar, or other iOS-specific fact.
Generated files
The path emits seven runtime files and up to 21 web-flow files, including a custom mailer and up to four text
templates. Each omitted flow omits its own views and mail templates, and Accounts that send no mail have no mailer.
The default
Account renderer owns the controller, show/edit/form views, shared credential/default-detail partials, and locale;
edit/form are omitted when no editable fields exist. For admitted custom profile/update definitions, Scaffold
renderers supply those four controller/view files while Account retains its shared partials and locale. The Account
runtime replaces DEPLOY.md with a provider-neutral mail launch prerequisite and restores the Account clauses that
Core's Account-free Terms omit, in the Terms view and its locale text. A separate schema-quality renderer owns
.active_record_doctor.rb. It retains Core's global exceptions for the frameworks the application loads and adds
detector-specific exceptions for the exact Rodauth auxiliary tables, nested models, and login/status attributes. Its
missing-presence exceptions also name each required numeric Field and AASM-managed state attribute:
active_record_doctor 2.0.1 recognizes neither numericality nor AASM's native validation. AASM deliberately relies on
initialization and NOT NULL for internal nil state errors. No extra validator is emitted to satisfy the detector.
Its length exceptions name each string or text attribute whose first length maximum differs from its column limit,
such as a long_text maximum, because the detector reads only column limits.
Data integrity owns which rules receive a limit. Fields owns the
exceptions for counter caches and for Active Storage's
tables and record index. Without any such exception, the Core config keeps
its detectors and drops only the framework models the application does not
load. Account-free output retains Core's DEPLOY.md and Terms. Separately, the conditional
ErrorsController renderer replaces Core's error controller with the same controller plus a current_account helper
returning nil, allowing public error navigation without an account lookup. Account-free output retains Core's error
controller. The application-global ApplicationController plus generic Domain-route and main-navigation renderers
retain their shared ownership. Account does not claim Core's application layout. Appearance replaces it for an omitted
theme or light, dark, or auto; toggle retains the Core layout. Realized Account navigation does not transfer
the stylesheet; Core owns its responsive rules. Appearance also selects the branding and theme-behavior paths that
Appearance lowering describes. The iOS variant additionally lowers the same
ApplicationNavigation entries and composes generated iPhone source without changing Rails dependency selection.
Ordinary input lowers the same navigation for iOS and Android. When a client is absent, its projection declines
with no native output while the Web self surface remains. Android remains excluded from the private Account
qualifier. The smoke proves deterministic
materialization, frozen dependency installation, asset builds, boot, migration/schema parity, password-hash storage
and verification, the configured identifier label and auxiliary-table names, and cascading cleanup. On both runtime
legs it drives freshly rendered authenticity tokens through browser and native-user-agent Rails integration sessions.
Public Compilation realizes the Web Account only for the Field-only boundary above. Its separate Policy lowering
consumes that realized Account context but does not widen registration. Name collisions remain ordinary-success
omissions in the reviewed GapSet.
The runtime and web-flow Account families return no tasks unless the context carries the exact input-owned Account
realization; otherwise they select seven runtime paths and the requested flows' web-flow paths. The runtime family
conditionally replaces DEPLOY.md with the provider-neutral Account mail launch prerequisite and the Terms view with
its Account clauses. One separate schema-quality renderer owns .active_record_doctor.rb, combining exact Rodauth
exceptions with the required numeric, AASM-managed state, and model-only length attributes that its detectors do not
recognize, and with the counter-cache and Active Storage exceptions that Fields owns. The Account-self family selects
its default page and shared partials from that input's exact self projection; admitted profile/update definitions
supply the custom controller and views.
The artifact emits the retained plan's verified Gemfile and Gemfile.lock through generic one-path dependency
renderers, plus the authentication migration and schema projection, Account model integration, fixed Rodauth
application and initializer configuration, Rails ERB forms, Account-aware navigation, a custom RodauthMailer, four
text email templates, localized interface text, and the credential-redaction request spec. The conditional runtime and
web-flow renderers own these paths. Account-only output exposes private current_account and authenticate methods
backed by rodauth.rails_account and rodauth.require_account. A private Policy gate or public Policy lowering keeps
those helpers and adds nullable Action Policy user context plus the narrow ActionPolicy::Unauthorized
response (repository-only). Generalized Web Scaffold controllers consume that
context for their protected actions and custom Account details. The Account destination itself requires
authentication. Runtime tasks replace Core files only for an input-owned Account realization. Generated request
examples cover the default Account page's guest guard without duplicating an authored-profile example. Constructible
registration setup submits all selected scalar inputs, checks that missing Terms persists no Account, and checks
assignment with at least one value distinct from its registration default. With verification, the same new-Account
example checks refusal to sign in before email verification; without it, the example checks that the new Account
reaches /account signed in. These examples exercise application-owned registration wiring; generic Rodauth
recovery, token, and mail behavior stays with upstream and Service qualification. When bounded setup cannot supply
valid or distinguishable values, the generated README names the missing coverage. Account-free output retains those
Core files byte-for-byte. Its generic shared-navigation seam stays immutable when Appearance is absent; authored
Appearance is the sole conditional whole-file owner.
Create-account form
The public create-account view renders every admitted Field control in authored order and marks it required. Before
product assignment, an ordinary Rodauth before_create_account check requires terms-accepted=1 from the initially
unchecked Terms checkbox. Its label links to public /terms; /privacy is a nearby notice. Missing or unchecked
acceptance returns a localized, accessible 422 field error before any Account/authentication write or
verification work.
The signup links depend on Core's pages.terms.heading and pages.privacy.heading translations as well as
terms_path and privacy_path; preserve these names when selecting or updating the Core pin.
Before Rodauth's Sequel insert, the generated hook assigns product input to Rodauth's existing rails_account
object and runs its full Active Record validation stack. Sequel does not invoke those normalizations or validations.
Both assignment and failed-form redisplay use Rodauth's param_or_nil, retaining its configurable
max_param_bytesize (1024 bytes by default) and NUL handling. Non-string and invalidly encoded input is rejected
before the accessor can stringify it. Missing input may use its analyzed default; a present rejected value never
does. Defaults fit the pinned 1024-byte request boundary so their forms can round trip. The Rails model owns
authored length rules after normalization; registration adds no character cap. With CSRF protection enabled,
Rails' ActionDispatch parameter-encoding check rejects malformed UTF-8 form input with 400 before the hook.
An invalid product value returns 422, owns its Field error, and rolls the
Sequel transaction back before
Account/authentication writes, and enqueues or delivers no mail. Valid normalized values are copied into the one
base insert. Failed-insert handling keeps the submitted Rails model in a local variable and checks the login with
Rodauth's _account_from_login, because since Rodauth 2.47.0 a route that loads a second
account in place of its new one raises. It then attributes product uniqueness errors to
that candidate.
Create, verification-resend, and reset-request paths use generic direct HTML outcomes; executable
tests prove equal redirect status, destination, notice, and alert text for their exercised eligible/ineligible
pairs. A reset form POST whose key is used, expired, or invalid gets the same generic outcome, because Rodauth sends
that failure through reset_password_email_sent_redirect. Concurrent eligible resend/reset requests are not
single-flight: more than one local/queued message can be
produced. HTTP response parity is normalized, but timing, rate limiting, duplicate-send suppression, and broader
abuse controls remain unproved. Generated login and unlock-request responses still distinguish known from unknown
email identifiers, so create/resend/reset normalization does not close application-wide enumeration. Field errors
use shared field-errors composition and the stock destructive theme token.
For ordinary public output, the create-account view maps all admitted Field controls in authored order and marks
each required. Before the base Sequel insert, the generated hook assigns the request values to an Active Record
Account and runs its full validation stack. Invalid input returns 422, attributes errors to the authored control,
rolls back before any Account/authentication write, and produces no mail job. Valid normalized values are copied into
the one Sequel Account insert. The generated create, verification-resend, and reset-request responses use generic
notices, and the deep public smoke proves equal redirect status, destination, notice, and alert text for its eligible
and ineligible pairs. Concurrent eligible resend/reset requests are not single-flight: more than one local/queued
message can be produced. HTTP response parity is normalized, but timing, rate limiting, duplicate-send suppression,
and broader abuse controls remain unproved. Generated login and unlock-request responses still distinguish known
from unknown email identifiers. Inline errors use the shared field-errors composition and destructive theme token.
All controls are required; a Field's analyzed default appears only when the parameter is absent, while an explicitly
blank submission remains blank and invalid. The public representatives cover one short_text, two short_text
Fields, and a short_text plus time_zone. Product text uses Rodauth's configurable max_param_bytesize, whose
pinned default is 1024 bytes. This byte limit is a request boundary. Applications can customize Rodauth's normal input
handling in their emitted source. Failed forms retain accepted parameter values and never redisplay either password.
The Appearance renderer derives a destructive text color with sufficient contrast against its surfaces.
Self-service Web signup also requires an initially unchecked Terms acceptance checkbox. Its label links to the public
/terms page, and the nearby /privacy link is a notice, not another consent request. The ordinary Rails checkbox
submits terms-accepted=1 when checked and 0 when unchecked; a missing value is also rejected. The hyphenated
request parameter cannot collide with an authored Field key. Rodauth's existing before_create_account hook requires
the exact checked value before assigning product inputs or saving the Account. Its throw_error_status
API returns a localized field
error associated with the checkbox. Rejected submissions retain safe product and email input, clear both passwords,
and write no Account, authentication row, or verification mail/job. Checked submissions retain the existing
validation, normalization, and duplicate-login behavior. The checkbox remains checked after an unrelated field error.
Programmatic clients of the signup endpoint must submit the same value; direct model, factory, and seed creation are
unaffected. This is a signup requirement with no stored acceptance/version history or reacceptance workflow.
Before Rodauth executes its Sequel Account insert, the generated hook assigns every admitted product value to
Rodauth's existing rails_account object and calls its complete Rails validation stack. This follows the upstream
model-validation integration. Sequel's insert
does not run Active Record normalization or validation, so that pre-insert check and the normalized-value copy remain
necessary. The pinned Rails
integration provides the
unsaved model; no separate registration model or instance variable is needed. Invalid product input reports
Field-owned errors and marks the Sequel transaction for rollback before any Account or auxiliary row can be written.
Valid normalized values are then copied into the Sequel Account hash for the one base insert; no callback or
post-create update repairs the row afterward. Submitted authentication-owned columns such as account_status remain
ignored. An existing login keeps the generic create response; a product uniqueness collision revalidates the submitted
candidate and reports its Field-owned errors. Rodauth 2.48.0's parameter
handling owns the byte and NUL
handling used by both registration assignment and redisplay.
Rodauth configuration
The explicit path emits an Account migration and complete schema projection, Account-only Rodauth model integration,
fixed Rodauth application and initializer configuration, a Core-qualified dependency pair through the generic
dependency tasks, Rails Account forms, Account-aware navigation, a custom mailer with four text templates, and
localized interface text. Its runtime family replaces DEPLOY.md with the provider-neutral mail launch prerequisite;
Account-free output retains that Core file. The separate schema-quality renderer combines exact Rodauth exceptions
with required native numeric, AASM-managed state, and model-only length attributes, and with the counter-cache and
Active Storage exceptions that Fields owns. When none of those exceptions is needed, it keeps Core's
.active_record_doctor.rb detectors and drops only the framework models the application does not load. The generic
ApplicationController task owns that global Core path for every application.
Without Account qualification it copies the exact Core bytes with empty subject provenance. With qualification it
extends those bytes with current_account and authenticate; the first returns rodauth.rails_account only when
Rodauth reports logged_in?, and the second delegates to rodauth.require_account. The task records the Account
Entity as provenance. The configuration realizes the authored
identifier name as Rodauth's login label. It keeps built-in Tilt rendering disabled and constructs each requested
flow's mail through the generated mailer. Its emitted send hook wraps
deliver_later in Sequel's db.after_commit callback. Rodauth errors use Rails' alert flash key so they enter
the Core's assertive live region.
The emitted configuration uses Rodauth 2.48.0's documented
already_logged_in hook as already_logged_in { redirect login_redirect }.
Login, registration, verification, reset, and unlock routes call it before processing an active Account's request.
Signed-in GETs and CSRF-valid POSTs return to the existing login destination, currently /, without processing
another identity. Recovery starts signed out. Rails rejects missing or pre-login-session CSRF tokens before the
hook runs.
Guest destinations use two settings from the rodauth-rails 2.2.0 generator. With
login_return_to_requested_location? true, require_account remembers a signed-out GET
path, so a guest sent to sign-in from a protected page returns there afterwards. reset_password_redirect sends a
completed password reset to sign-in, and create_account_redirect and default_post_email_redirect do the same for
a guest after registration or an email request. The step's notice therefore stays visible even when Home requires
an Account. A signed-in Account keeps the existing destination.
Generated Account forms use Core's centered card, stock large submit control, and exactly one in-card pair of
persistent flash regions. Field errors stay attached to their controls and submitted passwords stay blank.
The documented login_failed_reset_password_request_form hook returns nil:
Rodauth's ordinary Forgot Password link opens the standalone recovery page instead of embedding that complete
page inside failed Login. The built-in login header and footer hooks remain available. Sign-in and sign-out use
login_notice_flash nil and logout_notice_flash nil, matching Core's choice to let the destination communicate
those outcomes. Email next-step notices remain persistent guidance. Account settings uses the shared detail card
and a quiet password-change action.
Account settings links to the built-in change_password feature. Its Rails form uses
Rodauth's current-password, new-password, and confirmation parameters with the existing Account field partial.
Rodauth checks the current password, confirmation, and password requirements, then changes the stored hash and
redirects to /account. Since Rodauth 2.46.0, a successful change also calls
clear_tokens(:change_password). That deletes any pending reset or email-change
key and the Account-wide remember key, so remember cookies held by other clients stop working. The current session
stays signed in, and load_memory gives it a new remember key at its next hourly renewal.
Other open Rails sessions also stay signed in. This is the library's conventional password-change behavior; it is
not an Account-wide sign-out action.
The emitted web configuration sets Rodauth's login label from the authored identifier name, limits normalized email
login to 254 characters, checks trim and case in Postgres, admits internal statuses 1 and 2, uses password bounds of
15 characters and 72 bytes, enables common-password rejection, and permits ten invalid logins. Verification, reset,
and email-change keys expire after 24 hours; lockout state has a separate 24-hour deadline. Expired verification
lookup is read-only until loaded-Account resend rotates the key. Each emailed key works once, even when two requests
present it together: inside the route's transaction, Rodauth locks the key row with
FOR UPDATE, and the generated verification lookup takes the same lock. A form POST reads its key only from the
session that the link's GET set. A completed verification replaces the key instead of
deleting its row. Token IDs that are not UUIDv7 stop before SQL: the emitted convert_token_id rejects them for
emailed keys, and remembered_session_id rejects them for the remember cookie. The verification-resend and
reset-request hooks load the Account before Rodauth's route loads it again, so each eligible request logs Rodauth's
repeated-retrieval warning. Every variant emits a one-year remember deadline, no /remember route, and an HttpOnly,
request-secure, SameSite=Lax cookie at /. With extend_remember_deadline? true,
load_memory moves the deadline a year out on each restoration and at most hourly while a
remembered client stays signed in, so people who keep using the app stay signed in. As in the rodauth-rails
generator, after_login remembers every login, and an autologin account creation is
remembered too; the login form has no "Remember me" checkbox. load_memory runs before ordinary Rails routes, and
explicit logout removes the Account-wide server key. When its hourly renewal falls due on an email-change
confirmation, it loads the Account by session before the route loads it by key. Since Rodauth 2.47.0 that second
load raises, so the emitted account_reload allows exactly that pair and the link still
works. A native login, autologin account creation, or logout then
redirects to /native-session-changed so the native shell restarts its tabs; browsers keep their ordinary redirects.
Its built-in signed-out reset calls clear_tokens(:reset_password), whose remember extension
removes the Account-wide key. Former clients' stale remember cookies are rejected and
expired when next presented without a Rails session. No custom post-reset hook is needed for that flow. Existing Rails
sessions are not revoked by this configuration. The native redirects depend on the declared Hotwire Native user-agent
marker, not device attestation. The smoke supplies that marker directly rather than deriving it from the emitted iOS
shell.
The artifact keeps built-in Tilt rendering disabled and uses its generated Rails views and RodauthMailer. The
emitted configuration registers verification, reset, and unlock deliver_later calls through Sequel's
db.after_commit; focused tests pin those exact bytes, while the smoke does not independently observe enqueue
ordering. Rodauth errors map to Rails' alert flash key. Generated DEPLOY.md requires an owner to configure a
production delivery method, verified sender, canonical HTTPS link host, queue operation and retention,
SPF/DKIM/applicable DMARC, monitoring, and external verification, reset, and unlock delivery proof. The profile does
not select or prove a provider.
Every Account requires Rodauth's application-wide remember feature and an Entity-scoped remember table. This follows
the rodauth-rails generator, which remembers all logged-in users and extends the deadline, and the Rails 8
authentication generator, which stores every session in a permanent cookie. The earlier conventional experiment
remembered only native logins; this prerequisite follows its one-year deadline and renewal, matching Joe Masilotti's
advice to treat native sign-in as signed in
forever, and keeps its own cookie settings.
The configuration sets no fixed maximum since the last password sign-in, although NIST SP
800-63B-4 recommends one of at most 30 days for password-only (AAL1)
sessions. Rodauth keeps one remember key per Account, so a later login on any client reuses that key and its current
deadline. Explicit logout reads the Account identifier before the session is cleared and deletes the single
server-side remember row, which revokes remember cookies held elsewhere. A Rails session still open on another browser
or app stays signed in, and its next hourly renewal issues a fresh remember key; a password reset leaves those
sessions the same way. Hotwire Native is selected by the client-declared user-agent marker, not an authenticated
device signal; it now chooses only the session-change redirects.
The Account iPhone session smoke (repository-only) compiles the Movie Catalog
Plan through the private qualifier and runs one XCUITest in an iPhone Simulator. It observes native WebKit login, the
Account tab's /account page before termination and after restoration, deliberate Rails-session loss, same-Account
remember restoration that extends the deadline, restored logout, a second login, and a clear-both signed-out control.
Login and logout redirect through the session-change reset, which reopens the public Movies tab, so the test reads the
signed-out state from the Account tab's sign-in form. Ordinary Compilation of the same Plan realizes the same Account
and remember feature and omits only the Favorite movie signup input, which the test never uses. The runner checks
that before building. The smoke is not physical-device, signing, release, production, hosted Compilation, or
authored-profile evidence.
The emitted configuration sets Rodauth's login label from the authored identifier name, limits login identifiers to 254 characters, normalizes trim and case, and backs that choice with a database check and unique index. Reset, unlock, and email-change lookups are Rodauth's own, including its bounded deletion of expired reset and email-change rows. These emitted configuration and storage boundaries remain covered alongside the browser journey.
Accounts without verification
An Account that omits verification lowers to the same configuration without Rodauth's verify_account and
verify_login_change features. The emitted application has no verification or login-change key tables, nested
models, routes, views, mail templates, or mailer methods, and no verification-specific hooks or notices.
- Signup. Rodauth's
create_accountkeeps its defaultcreate_account_autologin?, so a successful signup signs the new Account in and redirects to/. Withoutverify_account, Rodauth skips status checks by default and never writes the status column. The configuration setsskip_status_checks? false, so signup writes the open status 2 and login and recovery check it. Theaccount_statuscolumn defaults to 2 so Accounts created outside Rodauth can also sign in. Seeds and factories already write 2. - Email confirmation field. Rodauth's
verify_accountandverify_login_changeturn offrequire_login_confirmation?. Without them, Rodauth's default applies: signup and email change ask for the email twice, and the generated views already render that field. The generated registration spec sends it. - Duplicate email. The verified topology answers a duplicate signup with the same generic notice as a new one.
Here the signed-in redirect already reveals the difference, so
save_accountlets Rodauth report its ordinary existing-login field error. Product uniqueness errors still revalidate the submitted Account. - Email change. Rodauth's
change_loginrequires the current password and updates the login immediately. The form's button says Change email, and the notice confirms the update. - Mail. Password reset and unlock keep their mailer methods, templates, cooldowns, and after-commit delivery.
Generated
DEPLOY.mdtitles its section Account email. It says that signup and email changes send no mail, that signup does not confirm address ownership, and that reset and unlock mail is not delivered until a provider is configured. It shows Action Mailer SMTP settings read fromSMTP_*environment variables withENV[]: the Dockerfile'sassets:precompilestep andbin/rails runnerrecipes boot production without them.
The unverified Account smoke (repository-only) compiles the
unverified probe Plan (repository-only) and runs the
generated specs. It then drives CSRF-protected signup to a signed-in /account, a duplicate signup,
a rejected and an accepted email change, password reset, and lockout with unlock, using the test mail adapter.
Accounts without registration, recovery, or lockout
Registration, recovery, and lockout each switch one Rodauth feature, as verification does. An omitted flow emits no
feature, auxiliary table, nested model, route, view, mail template, mailer method, locale entry, or hook for it.
Policies over current_account and the pages they guard still lower, because they need only a Rodauth session.
- Without registration.
create_accountstays disabled, so/create-account, the navigation's Create account link, and the login page's signup link do not exist. Rodauth documentscreate_account_route nilfor keeping the feature without its route; rodauth-rails 2.2.0 needs neither, and its model'spassword=creates Accounts without it.verify_accountdepends oncreate_account, so verification enables onlyverify_login_change, and the status column defaults to open. The registration projection is empty, so the default/accountpage shows only the email. The generated README tells the owner to add Accounts to development seeds and, because Render Free has no Shell, to runbin/rails runnerwith the productionDATABASE_URLandSECRET_KEY_BASE_DUMMY=1. - Without recovery.
reset_password, its pages, mail, login-page link, and thelogin_failed_reset_password_request_formhook are absent. The README shows the runner call that sets a new password. - Without lockout.
lockout, its two tables, unlock pages, mail, andmax_invalid_loginsare absent. Instead,RodauthControllercarries therate_limitthat Rails 8.1's authentication generator puts onSessionsController#create: 10 attempts per IP address in three minutes, then a redirect to/loginwith an alert. rodauth-rails runs controller callbacks around each Rodauth route with the route name as the action name, soonly: :loginselects sign-in, andif: -> { request.post? }leaves the form's GET uncounted, as the rodauth-rails README's ownbefore_actionexample does. Every Rodauth route shares this controller, and Rails keys unnamed limits by controller and IP address alone, soname: "login"keeps a limit the owner adds later on its own counter. The limit counts in the Rails cache, Solid Cache in production. Tests use Core's null cache, so generated specs that sign in repeatedly never reach it.DEPLOY.md's Failed sign-ins section names what a per-IP limit leaves open and theby:key a classroom on one campus address needed (repository-only).
DEPLOY.md names only the requested mail flows. Without verification, recovery, or lockout it says that Accounts send
no mail, and the configuration omits send_email and the email-token convert_token_id hook.
The no-signup smoke (repository-only) compiles the no-signup probe Plan (repository-only) and runs the generated specs. It creates an Account with the README's runner recipe in the production environment against the local database, signs in, uses a Policy-guarded page, confirms an email change by mail, resets the password, and locks and unlocks the Account. The no-recovery smoke (repository-only) compiles the no-recovery probe Plan (repository-only), signs up, reaches the same pages, fails sign-in fifteen times without a lockout, and sets a new password with the README's runner recipe. It then counts the limiter in Solid Cache: the eleventh sign-in post from one address is redirected, while page views, another address, and the next window are not. Neither smoke proves the recipe on Render or Neon.
Private qualifiers
The private Account-only artifact qualifier admits reduced web-only and iOS Movie Catalog variants. Both derive
/account with signup-field details and editing, without consulting a Policy. Web admits no Scaffold and emits
no ios/; selected iOS admits only public indexes on non-Account Entities and lowers the same target-neutral
navigation. The primary
path requires the full reduced registration shape: one required Account-owned short_text Name input with a
nonblank analyzed literal default of at most 1024 bytes and one optional conventional forward Favorite movie
Association whose target has a required short_text Primary Descriptor. The fixture projection pins those local
keys to name and favorite_movie; generated parameters and locale entries use the corresponding name,
favorite_movie_id, and favorite_movie vocabulary. The typed projection retains authored order and covers every
required Account product member in this slice. The Account iPhone harness uses this projection. The qualifier
rejects zero-input, partial, mixed, or broader input populations, uncovered
required members, other native clients, delivery channels, Scaffolds on the Account Entity, and broader Scaffold
behavior. The application-wide dependency plan receives Account's immutable Gemfile block, direct requirements,
prerequisite list, and source identity. It projects one complete Core-qualified pair and retains it for the
separate Gemfile and Gemfile.lock tasks. Account owns neither file.
A separate private AccountQualificationInput reconstructs the narrow reduced Movie Catalog prerequisites from one
sealed generation and returns a valid Domain input paired with the exact selected Account lowering. It also
qualifies one
AccountSelf result against target, Project, and generated route, constant, controller, and view claims. That result
fixes GET/HEAD /account, account-self:<subject_uuid>, the semantic person icon, the Account Entity label, and
source provenance. ApplicationNavigation combines it with the exact public indexes without adding client-specific
meaning. RenderingContext may carry those Account values only when they are the exact input-owned realization for
the same generation.
ApplicationTaskPlan then selects the complete seven-task Account runtime family, 18-task web-flow family, and 3-task
self family. The application-global ApplicationController task reads the same whole-Project context. The generic
Domain-route and main-navigation tasks add the self route and authenticated link; Account owns neither global path.
The application-wide dependency plan observes that exact context, adds the Account requirement contribution and
source identity, and projects the exact Gemfile and Gemfile.lock pair before Core composition. Normal
Compilation constructs the same output context only for an input-owned public Field-only Account realization.
Ordinary input does not admit Association registration. The private qualified input has a distinct internal type,
and the rendering context rejects forged or mismatched input-owned evidence so this seam cannot silently emit a
Domain-only subset.
The builder owns an explicit handled-root list. The private qualification treats capability warnings as blocking so
that every root must be handled; every other nonempty captured Project root rejects before
shared Domain serialization can widen this seam. Predicates remain in that list only so the existing Reference
catalog can return their source-addressed warnings. Validation roots are rejected before the deliberately empty
Validation declaration value is constructed. The Account-only iOS variant admits Scaffold roots only when they
describe public indexes on non-Account Entities; the Account-only web variant still rejects every Scaffold. Account
registration-input roots
are handled only through an immutable typed projection built from the exact Account lowering plus same-generation
Domain, Reference, and Primary Descriptor evidence. The primary admitted projection contains exactly one required
Account-owned short_text Field with a nonblank analyzed literal default of at most 1024 bytes and one optional
conventional forward Association whose target has a required short_text Primary Descriptor. It retains authored
input order and proves that every required Account product member in this slice has an input. This fixture projection
pins the Field key to name and the Reference key to favorite_movie; generated parameters and locale entries use
the corresponding name, favorite_movie_id, and favorite_movie vocabulary. The generated Account iOS
request-oracle gate records the Account-specific harness selecting this primary projection. Zero-input evidence is
no longer admitted. The harness prepares a disposable instrumented copy from the primary
projection, and one selected live test completed its bounded WebKit lifecycle. The qualifier and both Account emitter
families independently reject zero-input, forged, repeated, unmatched-default, or broader input evidence. The
separate self renderer rejects forged or mismatched Account-self evidence.
The private Association create-account view maps those typed inputs in authored order. Missing Name displays and
applies the analyzed default; present blank, whitespace-only, nested, NUL-bearing, or over-limit values fail. Missing
or empty Favorite movie becomes nil; every nonempty value, including whitespace-only input, must be a canonical
UUIDv7 resolving in the admitted Movie table under PostgreSQL FOR KEY SHARE. Rodauth's before_create_account
hook completes every product check before
any DML and merges accepted values into its base Account insert. It does not use an Active Record callback or an
after_create_account update. Failed forms redisplay only bounded safe Name strings and valid admitted Movie
selections, clear passwords, and leave the Account and all authentication tables unchanged.
The private Favorite movie control enumerates the complete admitted Movie table into an anonymous response, including descriptor labels and UUID values. This is bounded evidence for the small non-sensitive fixture only. Target-level authorization, filtering, search, and pagination must be designed before a public profile admits comparable Association choices. In the private Policy-gate qualifier, this control lists Movies to anonymous visitors although its gated Movie index shows them only to signed-in Accounts. That output conflicts with the code-worth-keeping requirement and is a known Compiler defect (repository-only).
The private Association create-account view maps its typed projection in authored order. An absent Name displays
its analyzed default.
A present String with valid encoding uses Rodauth's param_or_nil for assignment and redisplay. Its configurable
byte limit defaults to 1024, and admitted defaults fit that boundary so their forms can round trip. Authored length
rules run after normalization in Rodauth's rails_account model. Registration adds no character cap. Admitted values
redisplay, including whitespace-only values that the creation hook rejects as blank; every rejected shape
redisplays empty. The optional Favorite movie select orders admitted Movies by their qualified direct scalar Primary
Descriptor and id, retains only a valid admitted selection after failure, and leaves password controls empty.
Rodauth's
before_create_account hook performs every product check before any DML, merges the Name and foreign key into the
base Account hash, and never relies on an Active Record callback or post-create update. Missing Name applies the
analyzed default, while present blank, whitespace-only, nested, NUL-bearing, or over-limit values
fail. Missing or empty Favorite movie becomes nil; every nonempty value, including whitespace-only input, must be
a canonical UUIDv7 resolving under FOR KEY SHARE to the exact admitted target table. Malformed, nested, stale, and
foreign values fail before Account or authentication rows exist. Authentication-owned
columns are not request inputs. The private form currently publishes every admitted Movie descriptor and UUID to an
anonymous caller. It is proof for a small non-sensitive fixture, not a general Association-choice lowering;
authorization filters, bounded search, and pagination remain absent.
The private Policy-gate qualifier admits only the favorite_movie-targeted Movie index plus its existing private Web
Account prerequisite. It admits the optional Association registration input only when Movie has a direct required
short_text Primary Descriptor; the 193-file hosted predecessor and the current-base Core-repin's 194-file one-hop
control therefore omit it. This does not broaden public Account registration. Association admission and native session
restoration remain outside ordinary public CompilationInput, CompileCapturedProject, and the durable Compilation
lifecycle.
The optional Favorite movie select has one empty choice and orders the admitted Movie targets by their qualified
Primary Descriptor and id. Ordinary Compilation omits an Association registration input and lists the gap. Under
the code-worth-keeping requirement, a later public select would
need to limit an anonymous visitor's choices to records that the target's Policies allow.
Code and checks
On both runtime legs, the generated-app smoke enables Rails forgery protection and uses one Rails integration session
to submit freshly rendered authenticity tokens. It creates an unverified Account, proves that login remains closed,
requests a verification resend, follows the emailed two-step verification flow, and observes the exact signed-in
Account through current_account. The same session signs out through the generated POST navigation control, signs
in, requests and follows password-reset mail, rejects the old password, accepts the replacement, triggers the fixed
ten-failure lockout, requests unlock mail on the next attempt, and follows it to unlock and sign in. A separate
tokenless create request returns HTTP 422 before any write.
Before that longer journey, the smoke verifies both product controls, authored order, requiredness, descriptor choices, defaulted and explicit Name values, missing and empty Favorite movie values, and one exact Movie selection. Invalid Name and Favorite movie shapes leave the Account table and all six authentication tables unchanged, enqueue no job, deliver no mail, safely redisplay admitted product values, and never redisplay either password. A valid registration adds exactly one Account, password-hash, and verification row and no other authentication row.
The smoke executes generated messages only through the test Active Job and Action Mailer adapters. It proves local
text rendering, addressing, job execution, link behavior, and one redirect-delivered error and notice through the
Core's alert and status live regions. An ordinary browser session proves remember issuance, restoration after removing
the Rails session cookie, the disabled /remember route, and logout revocation. Separate native-user-agent sessions
prove issuance, restoration, deadline extension on restoration, expired-credential rejection, and Account-wide logout
and password-reset revocation. The emitted configuration registers deliver_later through Sequel's db.after_commit,
and focused tests pin those exact bytes; the smoke does not independently observe enqueue ordering. It does not prove
production mail, broader registration-input assignment or other Account product-member assignment, bounded or
authorization-filtered Association choices, production queue operation, equal response timing, closing the generated
login/unlock identifier disclosures, Policy enforcement outside the separate record-gate slice, broader abuse
controls, public native Account/session restoration, or WebKit session continuity for ordinary Compilation.
The separate compiler_account_runtime_smoke (repository-only) materializes the
qualified iOS reduced Movie Catalog Account bundle twice and compares every path, byte, and mode. Its fixture has one
public Movie index, a required Name registration input with analyzed Movie fan default, and an optional Favorite
movie Association input. It verifies the frozen generated bundle, composed iPhone source, migration/schema parity,
fresh schema loading, both boot paths, the configured runtime namespace, Account-only model integration, password-hash
behavior, auxiliary storage, and cascading cleanup. It builds the generated Rails assets once, then on both runtime
legs drives the generated CSRF-protected forms through browser sessions and sessions that declare the
client-controlled Hotwire Native user-agent marker. The smoke supplies that marker directly rather than deriving it
from the emitted iOS shell. The browser journey completes creation, resend and email verification, exact
current_account continuity, POST logout and login, password reset, fixed lockout, email unlock, and final logout. An
ordinary browser session proves remember issuance, restoration after session-cookie loss, and logout revocation.
Native sessions separately prove issuance, restoration on the next page request after session-cookie loss, deadline
extension on restoration, expired-credential rejection, disabled /remember, and logout and password-reset revocation
from a restored session. Generated mail executes through the test Active Job and Action Mailer adapters. A separate
tokenless create request returns HTTP 422 before any write. On both database setup legs, anonymous /account enters
Login and signed-in /account renders only the exact current_account identifier despite unrelated Account
parameters. The manifest's generated iOS application definition carries account-self:<subject_uuid>, /account, and
the lowered person symbols. The same Association-bearing shape does not realize Account through ordinary public
CompilationInput. This smoke does not build the iPhone source or prove a live tab transition, WebKit self
navigation, Simulator behavior for this entry, production mail, broader registration inputs, authorization, or
ordinary native Account admission.
The Policy-gate smoke repeats this current-record and anonymous-link boundary beside conditional Movie navigation. Focused Web-only output tests prove the self renderer selects without iOS. Focused native tests prove semantic-icon mapping and iOS task selection, not a live tab transition.
Generator tests pin route-title wiring for every Account page, and the smoke confirms that exercised flow headings
appear in their rendered document titles. It does not prove a production sender identity, provider or network
delivery, DNS, retries, bounce handling, or deliverability. Verification resend is exercised after an already
committed unverified Account exists; an initial enqueue or delivery failure is not simulated. deliver_later
serializes verification, reset, and unlock key arguments into the queue backend, so public production-mail
qualification must review queue access and bounded-retention controls.
The public deep smoke compares redirect status, destination, notice, and alert text for duplicate versus successful
create, unknown versus eligible verification resend, and unknown versus eligible reset request. Each pair is equal,
while only eligible flows change state or send local test mail. Photogram's concurrent unique-handle create race
returns 422 to the loser while the pair produces exactly one Account row, one Authentication row, and one
verification message.
The old resetting-client cookie-expiry assertion belonged to the superseded signed-in recovery setup; logout and subsequent stale-cookie presentation retain the relevant cleanup checks. Broader Policy algebra and native enforcement, timing parity, closing the known login and unlock-request identifier disclosures, broader abuse controls, session expiry and concurrency, broader registration input kinds and populations, bounded or authorization-filtered Association choices, other Account product-member assignment, live Account-self cross-navigator behavior, native profile behavior, signed-device behavior, deployment, and production mail remain absent. Exact-topology public Web Account Compilation does not imply any of those safeguards or native-session behavior.
The separate opt-in compiler_account_ios_session_smoke (repository-only) now
uses that primary Account shape and materializes the generated Rails and iOS application around one public Movie
index.