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 03 · Authenticators, SSO and linked accounts

The sign-in handshake

Signing in through a provider is four requests: login-with, the provider, the callback and authenticate, held together by a state value kept in the visitor's session. Each step, what it protects against, the errors a member can meet and the template that shows them, and how to tell the causes apart.

Who this is for. The developer whose provider sign-in works on one machine and fails on another, or fails with a message nobody understands. Afterwards you can follow a sign-in step by step and name the cause of each failure.

The four steps

  1. /oauth/login-with/<provider>, from the sign-in button. The platform makes a long random state value, keeps it in the visitor's session, and redirects the browser to the provider with the site's client id, the callback URL for this hostname, the scopes it asks for and the state. A provider that is not enabled answers 404. Sent as a POST, this step also keeps a redirectUrl for the end.
  2. At the provider, the person signs in and agrees to share their details. The provider redirects to the callback with a one-time code and the same state, or with an error if they refused.
  3. /oauth/callback/<provider> takes the state out of the session, deleting it, and compares it with the one that came back. It then exchanges the code for tokens in a direct, server-to-server request that includes the client secret, reads the person's identity from the provider, and finds or creates their account (Matching a provider sign-in to a member).
  4. /oauth/authenticate, within five minutes of the callback, starts the session and sets the sign-in cookie, then sends the member on: to the remember-me prompt when that is on, otherwise to their destination (Where the member lands).

What each step protects against

MechanismStops
The state, kept in the visitor's own session and checked at the callbackAnother site starting a sign-in and completing it in your visitor's browser, so the visitor ends up signed in to an account that is not theirs
The state deleted at the first callbackThe same callback URL being replayed, from history or a log
The code exchanged server to server, with the client secretA code seen in a URL being worth anything to someone without the secret
Redirect URIs registered at the providerThe code being sent to anywhere but the site's own callback
Five minutes between callback and authenticateA half-finished sign-in being picked up later

All of it rests on the session cookie reaching the callback. The state lives in the session; if the callback arrives without the cookie the visitor started with, the check fails. That is where most integrations break.

When the handshake fails

A failure in steps 2 to 4 renders an error page with HTTP 400. Override it with errors/oauth.html in your site (on a Twig site, errors/oauth-twig.html is looked for first); it receives content, the message, and code, the error's code. Without an override, the framework's plain page is shown. A request for JSON gets the error as JSON instead.

codeMessageUsual cause
invalid_state"State parameter is incorrect or has expired"The callback arrived without the session it started in: the sign-in began on one hostname and the provider returned to another (cookies belong to the exact hostname), the browser blocked the site's cookies, or the callback URL was opened a second time
access_denied"The authorization request was denied"The person pressed Cancel, or refused to share their details, at the provider
authorization_failed_or_incomplete"Authorization could not be completed due to an error. Authorization server is either unavailable or could be misconfigured."The code exchange failed: a wrong or expired client secret, a Microsoft tenant that does not match, or the provider unreachable
user_authenticator_already_exists_for_user"A user already exists with this email address. Please login and connect your account to enable sign in with this authentication method."The address belongs to an account that already signs in another way (Accounts that already have a way in)

A provider may also send back an error code of its own, which is shown as "An error was encountered. Error code: …" with the provider's code. Two failures never reach this page:

  • A redirect URI the provider does not know is refused at the provider, on the provider's own error page, before the visitor comes back (Redirect URIs).
  • A provider enabled before its client id or secret are filled in fails at step 1, before reaching this page (The steps).

Telling the causes apart

  1. Where does it stop? On the provider's page: the redirect URI or the provider's app settings. On your site's error page: read code.
  2. invalid_state? Compare the hostname in the address bar when the button was pressed with the one in the callback URL. If they differ, sign-in started on an alias: link to the sign-in page on the primary hostname, or register and use the alias consistently.
  3. authorization_failed_or_incomplete? Check the client secret has not expired at the provider (Microsoft secrets have expiry dates) and that the site's settings match the client.
  4. Only on one environment? That environment's hostname is missing from the provider's redirect URIs, or its settings differ.

Write errors/oauth.html to say what the member can do next ("try again", "sign in with your password", "contact us") for each code, rather than printing the platform's message on its own.

Go deeper

Was this page helpful?

Your answer goes to the docs team.