Skip to content

Commit cedb77b

Browse files
jasonvargaclaude
andcommitted
Move frontend 2FA URLs to config
Replaces the two_factor_challenge_url/two_factor_setup_url tag params and hidden-field plumbing with statamic.users.two_factor_challenge_url and two_factor_setup_url config keys. Eliminates the encrypt/decrypt dance and the session-based URL storage. CP flows are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 18103b3 commit cedb77b

11 files changed

Lines changed: 98 additions & 241 deletions

config/users.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,22 @@
211211

212212
'two_factor_enforced_roles' => [],
213213

214+
/*
215+
|--------------------------------------------------------------------------
216+
| Two-Factor Authentication URLs
217+
|--------------------------------------------------------------------------
218+
|
219+
| When users log in to the frontend and need to verify a two-factor code
220+
| or set up two-factor authentication, they will be redirected to these
221+
| URLs. Leave null to use the built-in pages. Control panel flows are
222+
| unaffected and always use their own pages.
223+
|
224+
*/
225+
226+
'two_factor_challenge_url' => null,
227+
228+
'two_factor_setup_url' => null,
229+
214230
/*
215231
|--------------------------------------------------------------------------
216232
| Default Sorting

src/Auth/UserTags.php

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public function loginForm()
107107
'passkey_verify_url' => route('statamic.passkeys.login'),
108108
]);
109109

110-
$knownParams = ['redirect', 'error_redirect', 'allow_request_redirect', 'two_factor_challenge_url', 'two_factor_setup_url'];
110+
$knownParams = ['redirect', 'error_redirect', 'allow_request_redirect'];
111111

112112
$action = route('statamic.login');
113113
$method = 'POST';
@@ -122,14 +122,6 @@ public function loginForm()
122122
$params['error_redirect'] = $this->parseRedirect($errorRedirect);
123123
}
124124

125-
if ($twoFactorChallengeUrl = $this->params->get('two_factor_challenge_url')) {
126-
$params['two_factor_challenge_url'] = $twoFactorChallengeUrl;
127-
}
128-
129-
if ($twoFactorSetupUrl = $this->params->get('two_factor_setup_url')) {
130-
$params['two_factor_setup_url'] = encrypt($twoFactorSetupUrl);
131-
}
132-
133125
if (! $this->canParseContents()) {
134126
return array_merge([
135127
'attrs' => $this->formAttrs($action, $method, $knownParams),
@@ -1034,7 +1026,7 @@ public function disableTwoFactorForm()
10341026

10351027
$data = $this->getFormSession();
10361028

1037-
$knownParams = ['redirect', 'allow_request_redirect', 'setup_url'];
1029+
$knownParams = ['redirect', 'allow_request_redirect'];
10381030

10391031
$method = 'DELETE';
10401032
$action = route('statamic.users.two-factor.disable');
@@ -1043,10 +1035,6 @@ public function disableTwoFactorForm()
10431035
$params['redirect'] = $this->parseRedirect($redirect);
10441036
}
10451037

1046-
if ($setupUrl = $this->params->get('setup_url')) {
1047-
$params['setup_url'] = $setupUrl;
1048-
}
1049-
10501038
if (! $this->canParseContents()) {
10511039
return array_merge([
10521040
'attrs' => $this->formAttrs($action, $method, $knownParams),

src/Http/Controllers/CP/Auth/TwoFactorChallengeController.php

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,9 @@
33
namespace Statamic\Http\Controllers\CP\Auth;
44

55
use Illuminate\Http\Request;
6-
use Illuminate\Support\Facades\Auth;
7-
use Inertia\Inertia;
8-
use Statamic\Events\TwoFactorAuthenticationFailed;
9-
use Statamic\Events\ValidTwoFactorAuthenticationCodeProvided;
106
use Statamic\Http\Controllers\TwoFactorChallengeController as Controller;
117
use Statamic\Http\Middleware\CP\HandleInertiaRequests;
128
use Statamic\Http\Middleware\CP\RedirectIfAuthorized;
13-
use Statamic\Http\Requests\TwoFactorChallengeRequest;
149
use Statamic\Support\Str;
1510

1611
class TwoFactorChallengeController extends Controller
@@ -22,37 +17,6 @@ public function __construct()
2217
$this->middleware(RedirectIfAuthorized::class);
2318
}
2419

25-
public function store(TwoFactorChallengeRequest $request)
26-
{
27-
$user = $request->challengedUser();
28-
29-
if ($code = $request->validRecoveryCode()) {
30-
$user->replaceTwoFactorRecoveryCode($code);
31-
} elseif (! $request->hasValidCode()) {
32-
TwoFactorAuthenticationFailed::dispatch($user);
33-
34-
return $this->sendFailedResponse($request);
35-
}
36-
37-
ValidTwoFactorAuthenticationCodeProvided::dispatch($user);
38-
39-
Auth::guard()->login($user, $request->remember());
40-
41-
$this->clearTwoFactorSession($request);
42-
43-
$request->session()->elevate();
44-
45-
$request->session()->regenerate();
46-
47-
if ($request->inertia() || $request->expectsJson()) {
48-
return $request->inertia()
49-
? Inertia::location($this->redirectPath($request))
50-
: response('Authenticated');
51-
}
52-
53-
return redirect()->intended($this->redirectPath($request));
54-
}
55-
5620
protected function formAction()
5721
{
5822
return cp_route('two-factor-challenge');

src/Http/Controllers/Concerns/HandlesLogins.php

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Statamic\Http\Controllers\Concerns;
44

55
use Illuminate\Contracts\Auth\Authenticatable;
6-
use Illuminate\Contracts\Encryption\DecryptException;
76
use Illuminate\Http\Request;
87
use Illuminate\Support\Facades\Auth;
98
use Illuminate\Validation\ValidationException;
@@ -66,16 +65,6 @@ protected function twoFactorChallengeResponse(Request $request, User $user)
6665
'login.remember' => $request->boolean('remember'),
6766
];
6867

69-
if ($challengeUrl = $request->input('_two_factor_challenge_url')) {
70-
if (! URL::isExternalToApplication($challengeUrl)) {
71-
$session['login.two_factor_challenge_url'] = $challengeUrl;
72-
}
73-
}
74-
75-
if ($setupUrl = $this->decryptSetupUrl($request->input('_two_factor_setup_url'))) {
76-
$session['login.two_factor_setup_url'] = $setupUrl;
77-
}
78-
7968
if ($redirect = $request->input('_redirect')) {
8069
if (! URL::isExternalToApplication($redirect)) {
8170
$session['login.redirect'] = $redirect;
@@ -86,14 +75,9 @@ protected function twoFactorChallengeResponse(Request $request, User $user)
8675

8776
TwoFactorAuthenticationChallenged::dispatch($user);
8877

89-
$challengeRedirect = ($challengeUrl = $request->input('_two_factor_challenge_url'))
90-
&& ! URL::isExternalToApplication($challengeUrl)
91-
? $challengeUrl
92-
: $this->twoFactorChallengeRedirect();
93-
9478
return $request->wantsJson()
9579
? response()->json(['two_factor' => true])
96-
: redirect($challengeRedirect);
80+
: redirect($this->twoFactorChallengeRedirect());
9781
}
9882

9983
abstract protected function twoFactorChallengeRedirect(): string;
@@ -106,19 +90,4 @@ protected function authenticate(Request $request, Authenticatable $user): void
10690

10791
$request->session()->regenerate();
10892
}
109-
110-
private function decryptSetupUrl(?string $value): ?string
111-
{
112-
if (! $value) {
113-
return null;
114-
}
115-
116-
try {
117-
$url = decrypt($value);
118-
} catch (DecryptException $e) {
119-
return null;
120-
}
121-
122-
return URL::isExternalToApplication($url) ? null : $url;
123-
}
12493
}

src/Http/Controllers/TwoFactorChallengeController.php

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ public function store(TwoFactorChallengeRequest $request)
5252

5353
Auth::guard()->login($user, $request->remember());
5454

55-
$this->clearTwoFactorSession($request);
56-
5755
$request->session()->elevate();
5856

5957
$request->session()->regenerate();
@@ -75,21 +73,9 @@ protected function sendFailedResponse(TwoFactorChallengeRequest $request)
7573
}
7674
}
7775

78-
if ($challengeUrl = $request->session()->get('login.two_factor_challenge_url')) {
79-
return $request->sendFailedTwoFactorChallengeResponse($challengeUrl);
80-
}
81-
8276
return $request->sendFailedTwoFactorChallengeResponse($this->failedRedirectPath());
8377
}
8478

85-
protected function clearTwoFactorSession(Request $request): void
86-
{
87-
$request->session()->forget([
88-
'login.two_factor_challenge_url',
89-
'login.two_factor_setup_url',
90-
]);
91-
}
92-
9379
protected function formAction()
9480
{
9581
return route('statamic.two-factor-challenge');
@@ -114,6 +100,6 @@ protected function redirectPath(Request $request)
114100

115101
protected function failedRedirectPath()
116102
{
117-
return route('statamic.two-factor-challenge');
103+
return config('statamic.users.two_factor_challenge_url') ?? route('statamic.two-factor-challenge');
118104
}
119105
}

src/Http/Controllers/User/LoginController.php

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,17 @@ public function login(UserLoginRequest $request)
2929
return $this->twoFactorChallengeResponse($request, $user);
3030
}
3131

32-
if ($setupUrl = $this->decryptSetupUrl($request->input('_two_factor_setup_url'))) {
33-
$request->session()->put('login.two_factor_setup_url', $setupUrl);
34-
}
32+
$redirect = $request->input('_redirect');
33+
$redirect = $redirect && ! URL::isExternalToApplication($redirect) ? $redirect : null;
3534

36-
if ($redirect = $request->input('_redirect')) {
37-
if (! URL::isExternalToApplication($redirect)) {
38-
$request->session()->put('login.redirect', $redirect);
39-
}
35+
// If 2FA setup is required, stash the redirect so the setup flow can use it after completion.
36+
if (TwoFactor::enabled() && $user->isTwoFactorAuthenticationRequired() && ! $user->hasEnabledTwoFactorAuthentication() && $redirect) {
37+
$request->session()->put('login.redirect', $redirect);
4038
}
4139

4240
$this->authenticate($request, $user);
4341

44-
$redirect = $request->input('_redirect');
45-
46-
$url = $redirect && ! URL::isExternalToApplication($redirect)
47-
? $redirect
48-
: route('statamic.site');
49-
50-
return redirect($url)->withSuccess(__('Login successful.'));
42+
return redirect($redirect ?? route('statamic.site'))->withSuccess(__('Login successful.'));
5143
}
5244

5345
private function checkPasskeyEnforcement(Request $request)
@@ -77,7 +69,7 @@ private function checkPasskeyEnforcement(Request $request)
7769

7870
protected function twoFactorChallengeRedirect(): string
7971
{
80-
return route('statamic.two-factor-challenge');
72+
return config('statamic.users.two_factor_challenge_url') ?? route('statamic.two-factor-challenge');
8173
}
8274

8375
/**

src/Http/Controllers/User/TwoFactorAuthenticationController.php

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function disable(Request $request, DisableTwoFactorAuthentication $disabl
5959

6060
$disable($user);
6161

62-
if (! $request->has('_redirect') && ! $request->has('_setup_url')) {
62+
if (! $request->has('_redirect')) {
6363
if ($user->isTwoFactorAuthenticationRequired()) {
6464
return ['redirect' => $this->setupUrlRedirect()];
6565
}
@@ -68,35 +68,13 @@ public function disable(Request $request, DisableTwoFactorAuthentication $disabl
6868
}
6969

7070
if ($user->isTwoFactorAuthenticationRequired()) {
71-
$this->storeSetupUrlInSession($request);
72-
73-
return redirect($this->getSetupUrl($request))
71+
return redirect($this->setupUrlRedirect())
7472
->with('success', __('Two-factor authentication disabled.'));
7573
}
7674

7775
return $this->formSuccessRedirect($request, __('Two-factor authentication disabled.'));
7876
}
7977

80-
private function getSetupUrl(Request $request): string
81-
{
82-
$setupUrl = $request->input('_setup_url');
83-
84-
if ($setupUrl && ! URL::isExternalToApplication($setupUrl)) {
85-
return $setupUrl;
86-
}
87-
88-
return $this->setupUrlRedirect();
89-
}
90-
91-
private function storeSetupUrlInSession(Request $request): void
92-
{
93-
$setupUrl = $request->input('_setup_url');
94-
95-
if ($setupUrl && ! URL::isExternalToApplication($setupUrl)) {
96-
$request->session()->put('login.two_factor_setup_url', $setupUrl);
97-
}
98-
}
99-
10078
private function handleFormValidationError(Request $request, ValidationException $e)
10179
{
10280
$errorRedirect = $request->input('_error_redirect');
@@ -132,6 +110,6 @@ protected function confirmUrl()
132110

133111
protected function setupUrlRedirect()
134112
{
135-
return route('statamic.two-factor-setup');
113+
return config('statamic.users.two_factor_setup_url') ?? route('statamic.two-factor-setup');
136114
}
137115
}

src/Http/Middleware/RedirectIfTwoFactorSetupIncomplete.php

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Closure;
66
use Illuminate\Http\Request;
77
use Statamic\Facades\TwoFactor;
8-
use Statamic\Facades\URL;
98
use Statamic\Facades\User;
109

1110
class RedirectIfTwoFactorSetupIncomplete
@@ -27,28 +26,20 @@ public function handle(Request $request, Closure $next)
2726

2827
protected function isSetupUrl(Request $request): bool
2928
{
30-
$currentPath = '/'.ltrim($request->path(), '/');
31-
32-
// Check if we're on the custom setup URL from session.
33-
if ($request->hasSession() && ($customUrl = $request->session()->get('login.two_factor_setup_url'))) {
34-
if (! URL::isExternalToApplication($customUrl)) {
35-
$customPath = '/'.ltrim(parse_url($customUrl, PHP_URL_PATH), '/');
36-
37-
if ($currentPath === $customPath) {
38-
return true;
39-
}
40-
}
29+
if (! $customUrl = config('statamic.users.two_factor_setup_url')) {
30+
return false;
4131
}
4232

43-
return false;
33+
$currentPath = '/'.ltrim($request->path(), '/');
34+
$customPath = '/'.ltrim(parse_url($customUrl, PHP_URL_PATH) ?? '', '/');
35+
36+
return $currentPath === $customPath;
4437
}
4538

4639
protected function redirectUrl(Request $request): string
4740
{
48-
if ($request->hasSession() && ($url = $request->session()->get('login.two_factor_setup_url'))) {
49-
if (! URL::isExternalToApplication($url)) {
50-
return $url;
51-
}
41+
if ($url = config('statamic.users.two_factor_setup_url')) {
42+
return $url;
5243
}
5344

5445
return route($this->redirectRoute(), [

0 commit comments

Comments
 (0)