Skip to content

[6.x] Add guard param to user tags - #15382

Draft
duncanmcclean wants to merge 5 commits into
6.xfrom
user-tags-guard-param
Draft

[6.x] Add guard param to user tags#15382
duncanmcclean wants to merge 5 commits into
6.xfrom
user-tags-guard-param

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request implements a guard parameter on the user tags, so a template can read a user from a guard other than the one configured in statamic.users.guards.web.

Sites running two user populations (say, website customers on Laravel's web guard and Statamic users on a statamic guard) can't express that with the config alone, because the front-end guard config sets one default guard for the whole request. An admin toolbar rendered on the front-end for the logged-in CP user therefore had no way of checking permissions or building a logout link for that user.

The parameter is accepted by the tags that read the current user: {{ user }}, {{ user:can }}, {{ user:cant }}, {{ user:is }}, {{ user:isnt }}, {{ user:in }}, {{ user:not_in }}, {{ user:logout }} and {{ user:logout_url }}. When it's omitted, the tags behave exactly as before.

{{ user:can guard="statamic" do="edit pages entries" }}
    <a href="{{ user:logout_url guard="statamic" redirect="{url}" }}">Log out</a>
{{ /user:can }}

This PR also lets the logout route accept a guard query parameter, which is what {{ user:logout_url }} appends. Unknown guards return a 404 rather than being passed through to the auth manager.

The form tags (login, register, profile, password and so on) are unchanged. They post to controllers that rely on the configured guard, and nobody has asked for those to be guard-aware.

Closes #10490

duncanmcclean and others added 2 commits September 7, 2026 18:21
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p6KWCwGqfnEv1ntSxnYUD
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p6KWCwGqfnEv1ntSxnYUD

@jasonvarga jasonvarga left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Warning — ?guard= also applies to the CP logout route (src/Http/Controllers/User/LoginController.php:109)
LoginController@logout backs two routes:

routes/web.php:48 → statamic.logout (the front-end route this PR targets)
routes/cp.php:145 → statamic.cp.logout
The new query param is read unconditionally, so /cp/logout?guard= now logs out that other guard and redirects to route('statamic.site') as though the CP logout succeeded — while the CP session stays alive. On the multi-guard setups this PR is aimed at, a crafted logout link leaves a user believing they're signed out of the CP when they aren't. Nothing in the PR's stated scope calls for the CP route to be guard-aware.

Suggested fix: only honour the param on the front-end route — e.g. split the CP logout into its own controller method, or gate on request()->routeIs('statamic.logout').

Warning — guard validation is too loose and has three unhandled-input paths (src/Http/Controllers/User/LoginController.php:111-114)

if ($guard = request()->get('guard')) {
    abort_unless(array_key_exists($guard, config('auth.guards')), 404);
}

Auth::guard($guard)->logout();

The check confirms a guard is configured, not that it can be logged out of, and the truthiness guard lets input past it:

Non-stateful guards. ?guard=api (or sanctum/any token-driver guard) passes array_key_exists, but TokenGuard has no logout() → BadMethodCallException → 500.
Array input. ?guard[]=x is truthy, so array_key_exists(['x'], …) throws TypeError: Illegal offset type → 500.
?guard=0. The string "0" is falsy, so the abort_unless never runs; Auth::guard('0') then throws InvalidArgumentException → 500.
All three are 500s reachable from unauthenticated query input on a public route. Suggested fix: read a string (request()->string('guard')->toString()), use !== '' rather than truthiness, and require the guard to resolve to a StatefulGuard rather than merely existing in config.

Warning — no test coverage for the invalid-guard edges or the CP route
LogoutTest covers the happy path and one unknown-guard 404. Missing: the CP logout route with a guard param, a non-session guard, and array/"0" input — i.e. exactly the paths above.

duncanmcclean and others added 3 commits September 7, 2026 19:10
@duncanmcclean

Copy link
Copy Markdown
Member Author

Thanks for the review! Addressed:

  • Validation: the guard param is now only honoured when it's a string naming a configured guard that resolves to a StatefulGuard. Anything else (?guard[]=x, token/sanctum guards, unknown guards) is a 404. ?guard=0 is treated as no guard.
  • Tests: added coverage for unknown guards, non-session guards, array input, and the CP logout route.
  • CP logout route: the CP route doesn't actually go through this controller. routes/cp.php points at Statamic\Http\Controllers\CP\Auth\LoginController::logout, which never reads the param. I've added a test proving /cp/logout?guard=... ignores it.

@jasonvarga

Copy link
Copy Markdown
Member

Might be introducing a security issue. Marking as a draft for a moment so we don't merge prematurely.

@jasonvarga
jasonvarga marked this pull request as draft September 8, 2026 17:59
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.

User tags don't work when you have multiple guards

2 participants