Skip to content

[6.x] Refactor Authentication front-end#18800

Merged
brianjhanson merged 81 commits into
6.xfrom
feature/inertia-auth
May 19, 2026
Merged

[6.x] Refactor Authentication front-end#18800
brianjhanson merged 81 commits into
6.xfrom
feature/inertia-auth

Conversation

@brianjhanson
Copy link
Copy Markdown
Contributor

@brianjhanson brianjhanson commented May 6, 2026

Refactors the LoginForm.js file into a web component. Step one in a bit of a larger plan. For now, this replaces most of the code within _special/login.twig with a new craft-login-form web component. That web component handles the whole of the auth flow. It will submit the username/password, display a two factor challenge and pass you on to the return configured return URL.

Eventually, I'd like to make this web component available to front-end requests so users can just throw a <craft-login-form/> into their templates and get the whole login flow.

How it works

The general flow hasn't changed from the previous LoginForm.js file. The auth flow is made up of three parts. There's the main login form, the password reset form, and the 2fa challenge form (when applicable). In this implementation these are three separate components that communicate with each other via some internal events.

After the initial login, the LoginController will return a JSON object that includes the HTML for the next stage's auth form along with some other data. The full data shape is represented by the TwoFactorData typescript type.

When data is return with authMethod the challenge form is also shown. A successful challenge will eventually emit a craft:login:success event you can listen for in order to act accordingly. I'm still working out / thinking about how to handle events globally in the system, so this may very well change.

By default, this will redirect you to the returnUrl but the event is cancelable so you can handle it as you see fit. For example, within our elevated session manager we prevent the default and just close the modal.

const $loginForm = $container.find('craft-login-form');
$loginForm.on('craft:login:success', (event) => {
  event.preventDefault();
  this.success = true;
  this.loginModal.hide();
});

This PR also removes the idea of an auth handler in favor of listening for events directly. Components that extend the AuthChallengeForm component will emit a login-verified event you can listen to in order to act on this part of the process. For example, within the auth setup of TOTP we use this to close the modal after a successful verification

const totpForm = document.querySelector('craft-totp-form');
totpForm.addEventListener('login-verified', (event) => {
  event.preventDefault();
  Craft.Slideout.instances['{{ containerId }}'].showSuccess();

  // @TODO This will eventually be unnecessary when more of the CP is inertia
  Craft.authMethodSetup.refresh();
})

We recommend extending this CraftAuthChallengeForm base component in order to create your own challenge forms that will follow the convention of ours. Both the TOTP and RecoveryCode forms extend this base component as examples.

Note

All code related to the TotpAsset and the RecoveryCodesAsset has been moved into the yii2-adapter and removed from the main codebase.

@semanticdiff-com
Copy link
Copy Markdown

semanticdiff-com Bot commented May 6, 2026

Review changes with  SemanticDiff

Changed Files
File Status
  resources/js/composables/useCraftData.ts  11% smaller
  resources/js/components/ActionMenu.vue  1% smaller
  package-lock.json  0% smaller
  packages/craftcms-cp/package.json  0% smaller
  packages/craftcms-cp/src/utilities/format.ts  0% smaller
  resources/js/bootstrap/cp.ts  0% smaller
  resources/js/components/Auth/LoginForm.vue  0% smaller
  resources/js/components/Auth/RecoveryCodesForm.vue  0% smaller
  resources/js/components/Auth/TotpForm.vue  0% smaller
  resources/js/layout/AuthBase.vue  0% smaller
  resources/js/pages/Auth/Challenge.vue  0% smaller
  resources/js/pages/Auth/Login.vue  0% smaller
  routes/actions.php Unsupported file format
  routes/cp.php Unsupported file format
  src/Auth/AuthMethods.php Unsupported file format
  src/Auth/Methods/RecoveryCodes.php Unsupported file format
  src/Auth/Methods/TOTP.php Unsupported file format
  src/Cp/Cp.php Unsupported file format
  src/Http/Controllers/Auth/LoginController.php Unsupported file format
  src/Http/Controllers/Auth/TwoFactorAuthenticationController.php Unsupported file format
  src/Http/Middleware/HandleInertiaRequests.php Unsupported file format
  src/Http/RespondsWithFlash.php Unsupported file format

brianjhanson and others added 26 commits May 6, 2026 16:28
It's probably not a bad thing, but having the classname in the URL felt dirty to me
Converts LoginForm.js (Garnish-based) into a self-contained LitElement
web component in the @craftcms/cp package. Handles login, reset-password,
and 2FA views; supports passkeys via @simplewebauthn/browser; dispatches
a craft-login event on success for modal embedding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- craft-login-form: slim orchestrator — login view, passkey, view routing
- craft-login-reset-password: self-contained reset-password form, fires reset-back
- craft-login-2fa: renders and initialises server-side 2FA form, handles method switching

Components communicate via custom events (reset-back, login-success, login-error).
TwoFactorData interface exported from login-2fa for shared typing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Decorated members (@State, @query, @Property) stay with TypeScript private
since decorators need prototype access. Plain implementation methods and
undecorated fields use native # private instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
craft-totp-form and craft-recovery-code-form replace the Garnish-based auth
form handlers. Both render their own UI with craft-input and craft-button,
auto-submit on complete input, and fire login-success / login-error events.

craft-login-2fa now renders these directly via WEB_COMPONENT_METHODS lookup
when the authMethod matches, falling back to Craft.createAuthFormHandler for
any legacy server-rendered form that isn't covered yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Just the first pass, more refinement to come
Comment thread src/Auth/AuthMethods.php

if ($user) {
Session::put('user.id', $user->id);
Session::put('user.pending_2fa_at', now()->timestamp);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 2fa session was previously lasting as long as a regular session. This creates a lil' 5 minute session for completing the 2fa flow

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will eventually take over for Pane.vue

Comment thread src/Route/RouteServiceProvider.php

public function setupHtml(Request $request, HtmlStack $HtmlStack): JsonResponse
{
Session::put('user.pending_2fa_at', now()->timestamp);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a short 2fa session, we need to start one to give you some time between when we show the setup HTML and when you confirm your 2fa method. Without starting a session here (and with the short 2fa session time) you're not able to set up a new auth method.

I think this is safe because this controller is behind the auth:craft middleware, but if this seems like a bad idea I'm open to suggestions.

We can also ditch the idea of opening a short session for finishing the 2fa process but I do feel like it's probably a good idea not to allow you to finish the 2fa process anytime during your (potentially long) session.

@brandonkelly brandonkelly requested a review from riasvdv May 19, 2026 02:06
Comment thread resources/js/components/Auth/login/login-form.ts
Comment thread resources/js/components/Auth/login/login-form.ts Outdated
Comment thread resources/js/components/Auth/login/login-form.ts
Comment thread resources/js/components/Auth/login/login-reset-password.ts Outdated
Comment thread src/Http/Controllers/Auth/LoginController.php Outdated
Comment thread src/Http/Middleware/EnsureTwoFactorChallengeIsRecent.php Outdated
Comment thread src/Http/Middleware/EnsureTwoFactorChallengeIsRecent.php Outdated
Comment thread src/Route/RouteServiceProvider.php Outdated
Comment thread src/Route/RouteServiceProvider.php Outdated
@brianjhanson brianjhanson requested a review from riasvdv May 19, 2026 16:25
Comment thread src/Http/Controllers/Auth/LoginController.php Outdated
@brianjhanson brianjhanson merged commit 3a11060 into 6.x May 19, 2026
18 checks passed
@brianjhanson brianjhanson deleted the feature/inertia-auth branch May 19, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants