Building a member area on Core dna: who a member is in the data, the sign-in flows and their templates, single sign-on and passkeys, the my-account routing convention, members-only content, sessions and the page cache, and the security checks to run before launch.
L2
Unit 02 · Registration, sign-in and password recovery
Registering a member
Registration, sign-in and password recoveryPage 7 of 36Updated
Registration is off until switched on, and its settings decide the new account's type, status and groups. The settings, the form and its fields, adding a field, what happens on submit, and the case most sites handle badly: an email address that already has an account.
Who this is for. The theme developer building the registration page, and whoever configures it. Afterwards you can build the form, add fields to it, and say what a new member gets.
The settings
In the Users module's settings, under Registration:
Setting
Default
Effect
Enable
Off
Off, /register answers 404
Type
Frontend
The new user's type. It offers Backend too, which would give every registrant the admin: keep it on Frontend (The rule)
One more Users setting matters here: Use email as username, off by default. When it is on, the username follows the person's email address every time the person is saved, so members sign in with their email address.
The form
users/register.html, which you write, posts to /register. It is open only to signed-out visitors: a signed-in visitor is redirected to their account with "You are not allowed to view this page while logged in". The fields it may send:
Field
Holds
username
The sign-in name. With Use email as username on, send the email address
password, confirmPassword
The password, twice. The only rule is the minimum length setting, six characters by default
The person's custom fields, posted as on the profile form (Worked case)
g-recaptcha-response
Written by the captcha widget when the captcha is on
passkey
1 to register with a passkey, where passkeys are on; the password is then optional (Passkeys)
Only the fields above are accepted. The type, status and groups always come from the settings, so a visitor cannot register themselves into a group or the admin.
Template codetemplates/users/register.html, the form
<form method="post" action="{{ _url.getPath('register') }}">
<label>Email <input type="email" name="person[email]" value="{{ user.getPerson().getEmail()|e }}" required></label>
{% if _validation is defined and _validation.hasFieldError('person[email]') %}
<p class="error">{{ _validation.getFieldErrorMessage('person[email]') }}</p>
{% endif %}
<input type="hidden" name="username" value="" data-copy-from="person[email]">
<label>First name <input name="person[firstName]" value="{{ user.getPerson().getFirstName()|e }}"></label>
<label>Last name <input name="person[lastName]" value="{{ user.getPerson().getLastName()|e }}"></label>
<label>Password <input type="password" name="password" autocomplete="new-password" required></label>
<label>Password again <input type="password" name="confirmPassword" autocomplete="new-password" required></label>
{% if _recaptcha is defined %}{{ _recaptcha.getWidgetHtml() }}{{ _recaptcha.getScriptTag() }}{% endif %}
<button>Create account</button>
</form>
The hidden username is filled from the email field by a line of the site's script, for a site that signs members in by email address. A failed submit renders the form again with what was typed in user, so the values above survive an error; the passwords do not, by design.
Adding a field
A field the person does not have is a custom field: define it for the Person entity under Customization › Custom Fields, then post it under person[customFieldValues]. Loop over the person's custom fields rather than writing their ids into the template (Profile data has the partial). A value that decides access must not be a custom field the member can set: registration accepts custom field values from the form like any other field (Members can edit their own custom fields).
An email address that already has an account
Email addresses are unique among people, so registering with one that exists fails on the email field with "This email is already used." That message tells the visitor the address has an account, which the forgotten-password flow is careful not to do. Handle it as a next step rather than a dead end: when the email field has that error, show the way in instead.
Template code
{if isset($_validation) && $_validation->hasFieldError('person[email]')}
<p>That address may already have an account.
<a href="{$_url->getPath('login')}">Sign in</a> or
<a href="{$_url->getPath('forgotPassword')}">reset your password</a>.</p>
{/if}
{% if _validation is defined and _validation.hasFieldError('person[email]') %}
<p>That address may already have an account.
<a href="{{ _url.getPath('login') }}">Sign in</a> or
<a href="{{ _url.getPath('forgotPassword') }}">reset your password</a>.</p>
{% endif %}
The address may also belong to a person with no login: someone who submitted a form or was imported from a CRM. Registration cannot attach a login to that person, so the visitor is refused all the same. If the site collects people before they register, decide with the client how those people become members, for example by creating their users in an import.
What happens on submit
A form with no password and no passkey is refused at once with "Please choose a password, or register with a passkey."
The captcha is checked next, when it is on. A missing or failed answer re-renders the form with a 422, "Recaptcha validation failed".
The person and user are created, with the type, status and groups from the settings.
The new member is signed in at once, with the message "Successfully Registered", even when the status setting is Inactive. A member registering with a passkey is sent to create it instead, and is signed in once it is created.
They land on the Success Redirect Url, or failing that the Users setting Login redirect url, or failing that /my-account.
No email is sent. The platform sends neither a welcome nor a confirmation. Send a welcome from a job on the user's create event (Send an email when a record is created).