Guide 07 · Developer docs

Members, accounts and gated content

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

Signing in and out

Password sign-in posts to /login, passes through the platform's authenticate step and lands the member where they were going. The form, its error states and what they must not reveal, where the member lands and how to keep the page they wanted, and what signing out does and does not end.

Who this is for. The theme developer building the sign-in page and the account menu's sign-out link. Afterwards members land where they meant to go, and the error states say no more than they should.

The form

The sign-in page, users/login.html, posts username and password to /login. The account is looked up by username; a member signs in with an email address only where the username is the email address, as it is with the Users setting Use email as username on. Label the field to match.

The page receives localPasswordEnabled, false when password sign-in is switched off for the site (the Local Password setting Enable), so show the password form only when it is true. Buttons for sign-in providers and passkeys are covered in Authenticators, SSO and linked accounts.

Error states, and what not to reveal

A failed sign-in renders the page again with HTTP 400, the message, and the username in user so the field stays filled:

CaseMessage
A field left empty"Please specify username and password."
An unknown username, a wrong password, or an account with no password sign-in"Username / password combination invalid."
The right password for an account that is not active"The user account is inactive - please ensure you have verified your email address."
Password sign-in switched off for the site"Login with local account has been disabled"

The second row is deliberate: the page never says whether a username exists. Keep it that way in your template. Do not add "No account with that email", and do not show different messages for the cases the platform answers alike. Offer the way forward instead: links to the forgotten-password page and, where it is open, registration.

What happens on success

Sign-in is a short chain of redirects. /login checks the password and redirects to /oauth/authenticate, which starts the session, sets the sign-in cookie and redirects again: to the remember-me prompt when that is on, otherwise to the destination. A script that signs a member in must follow redirects rather than expect an answer from the first response. The sign-in lasts twelve hours (What a sign-in is).

Where the member lands

The destination is the first of these that is set:

  1. The page they wanted. When a members-only page sends a visitor to sign in, it keeps the page in their session, and they return to it once. A redirectUrl field posted with the sign-in form replaces it; only its path, query and fragment are kept, so it cannot send anyone to another site.
  2. The Users setting Login redirect url.
  3. /my-account.

A ?redirectUrl= on the sign-in page's own URL does nothing until your template copies it into the form. It matters: a record refused under the security setting Access Denied set to redirect sends the visitor to /login?redirectUrl=<the record's path>, and the framework's own sign-in template does not pass it on, so the member lands on their account instead of the record (What a refused visitor sees). Copy it into a hidden field, escaped, because it came from the URL:

Template code templates/users/login.html, inside the form
{$next = $_request->getParameter('redirectUrl')}
{if $next}<input type="hidden" name="redirectUrl" value="{$next|escape}">{/if}
{% set next = _request.getParameter('redirectUrl') %}
{% if next %}<input type="hidden" name="redirectUrl" value="{{ next|e }}">{% endif %}

Signing out

Sign-out is GET /logout, route name logout, a plain link. It:

  • ends the sign-in in this browser, and removes this browser's remember-me cookie if there is one;
  • clears the cart cookie and the checkout in this browser; a member's cart is loaded again the next time they sign in;
  • redirects, with "You have successfully logged out.", to the Users setting Logout redirect url, or to /login when it is empty, not to the home page.

It does not sign the member out anywhere else: their other devices and browsers stay signed in until those sign-ins expire. A member who wants to end every session uses their list of signed-in sessions (Signed-in sessions), and changing the password ends them all except the current one (Changing a password).

The sign-out link is personal output, so on a cached public page it belongs in the account fragment, not in the page (A cached shell with member fragments).

Go deeper

Was this page helpful?

Your answer goes to the docs team.