Skip to content

IsGranted Attribute not working on LiveComponents #2521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Mauriceu opened this issue Jan 22, 2025 · 9 comments
Closed

IsGranted Attribute not working on LiveComponents #2521

Mauriceu opened this issue Jan 22, 2025 · 9 comments
Labels
Status: Needs Review Needs to be reviewed

Comments

@Mauriceu
Copy link

Mauriceu commented Jan 22, 2025

As per the docs LiveComponents are like normal Symfony controllers, as that you can also use the Attributes you usually use.

However, when a LiveAction is invoked the currently logged in User is null, probably due to some priority settings.

This in turn leads to the #[isGranted] attribute failing - also every other (manual) security check fails, because Security::getUser() always returns null.

Example Code:

#[LiveAction]
#[IsGranted("SOME_ROLE")]
public function save(): Response 
{ 
    /** AuthenticationEntrypoint logic will be invoked, implying no logged in User is found */ 
}
#[LiveAction]
public function save(): Response 
{
    $user = $this->security->getUser();
    // User is null
}

My current firewall configuration:

        user:
            pattern: ^/
            lazy: false

            # Repository implements UserLoaderInterface 
            # retrieves the User by comparing an identifier 
            # to either email or username.
            provider: user

            custom_authenticators:
                - App\Security\Authenticator\UserAuthenticator

            entry_point: App\Security\Authenticator\AuthenticationEntryPoint
            user_checker: App\Security\IsDisabledChecker
            login_throttling:
                limiter: "app.user_rate_limiter"

            remember_me:
                secret: '%kernel.secret%'
                lifetime: 604800

            login_link:
                check_route: user_login_link
                signature_properties: [ 'id' ]
                success_handler: App\Security\AutoLoginSuccessHandler
@Mauriceu Mauriceu added the Bug Bug Fix label Jan 22, 2025
@carsonbot carsonbot added the Status: Needs Review Needs to be reviewed label Jan 22, 2025
@smnandre
Copy link
Member

Do you have the same problems with default security settings / tools ?

@Mauriceu
Copy link
Author

Mauriceu commented Jan 22, 2025

Do you have the same problems with default security settings / tools ?

No, applying #[IsGranted] on a normal Controller works as expected.
I assume that is what you meant...?

@smnandre
Copy link
Member

Sorry no, i meant the opposite. Do the IsGranted attribute work on your component, with a more traditional / basic securty settings (firewall / authenticator / etc) ?

@Mauriceu
Copy link
Author

Mauriceu commented Jan 23, 2025

I've pretty much removed everything from the security config and it still is not working.

Some additional info:

We use keycloak to authenticate our users and create a customized UserInterface DTO. This DTO only contains a few additional properties for conditional rendering. The AuthenticationToken containing that DTO is correctly retrieved from the TokenStorage when invoking a normal controller, IsGranted works and everything else does too. Rendering a LiveComponent within a normal Controllers request context - e.g. GET request to a controller which renders a twig template that includes a LiveComponent - the AuthenticationToken is also correctly retrieved.

However, when invoking a LiveAction the TokenStorage::getToken() function always returns NULL.
I've confirmed that by tracing the Token within the AuthorizationChecker, the LiveComponents constructor, the LiveComponents LiveAction, and the LiveComponents Post/Pre-Mount function.

I'll try and setup a minimal reproducer in a few days time.

@smnandre
Copy link
Member

Do you use custom events / event listeners in your Authenticator ?

Could you check the priorities:

php bin/console debug:event-dispatcher kernel

@Mauriceu
Copy link
Author

Mauriceu commented Jan 27, 2025

No custom events in the authenticator. The authenticator just retrieves some query-params from the URL and creates a SelfValidatingPassport using these query-params. The provider retrieves the user data from the keycloak endpoint by using whatever value was used to create the Passport as identifier. The workflow is pretty similar to any in-built symfony authentication/provider system.

"kernel.controller" event

  1. Sentry\SentryBundle\EventListener\RequestListener::handleKernelControllerEvent() 10
  2. Symfony\UX\LiveComponent\EventListener\LiveComponentSubscriber::onKernelController() 10
  3. Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector::onKernelController() 0
  4. Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::onKernelController() 0

"kernel.controller_arguments" event

  1. Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener::onKernelControllerArguments() 20
  2. Symfony\Component\HttpKernel\EventListener\CacheAttributeListener::onKernelControllerArguments() 10
  3. ContainerQxMnOBF\RequestPayloadValueResolverGhost3590451::onKernelControllerArguments() 0
  4. Symfony\Component\HttpKernel\EventListener\ErrorListener::onControllerArguments() 0

"kernel.exception" event

  1. BabDev\PagerfantaBundle\EventListener\ConvertNotValidCurrentPageToNotFoundListener::onKernelException() 512
  2. BabDev\PagerfantaBundle\EventListener\ConvertNotValidMaxPerPageToNotFoundListener::onKernelException() 512
  3. Sentry\SentryBundle\EventListener\ErrorListener::handleExceptionEvent() 128
  4. Symfony\UX\LiveComponent\EventListener\LiveComponentSubscriber::onKernelException() 20
  5. Symfony\Component\HttpKernel\EventListener\ErrorListener::logKernelException() 0
  6. Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelException() 0
  7. App\EventListener\RouteNotFoundListener::__invoke() -1
  8. App\EventListener\ExceptionListener::__invoke() -2
  9. Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelException() -64
  10. Symfony\Component\HttpKernel\EventListener\ErrorListener::onKernelException() -128

"kernel.finish_request" event

  1. Sentry\SentryBundle\EventListener\TracingSubRequestListener::handleKernelFinishRequestEvent() 10
  2. Sentry\SentryBundle\EventListener\SubRequestListener::handleKernelFinishRequestEvent() 5
  3. Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelFinishRequest() 0
  4. Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelFinishRequest() 0
  5. Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::onKernelFinishRequest() 0
  6. Symfony\Component\HttpKernel\EventListener\LocaleAwareListener::onKernelFinishRequest() -15

"kernel.request" event

  1. Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure() 2048
  2. Nelmio\SecurityBundle\EventListener\ContentSecurityPolicyListener::onKernelRequest() 512
  3. Symfony\Component\HttpKernel\EventListener\ValidateRequestListener::onKernelRequest() 256
  4. Symfony\Component\HttpKernel\EventListener\SessionListener::onKernelRequest() 128
  5. Symfony\Component\HttpKernel\EventListener\LocaleListener::setDefaultLocale() 100
  6. Symfony\Component\AssetMapper\AssetMapperDevServerSubscriber::onKernelRequest() 35
  7. Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest() 32
  8. Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelRequest() 16
  9. Symfony\Component\HttpKernel\EventListener\LocaleAwareListener::onKernelRequest() 15
  10. Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::configureLogoutUrlGenerator() 8
  11. Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::onKernelRequest() 8
  12. Sentry\SentryBundle\EventListener\RequestListener::handleKernelRequestEvent() 5
  13. Sentry\SentryBundle\EventListener\TracingRequestListener::handleKernelRequestEvent() 4
  14. Sentry\SentryBundle\EventListener\SubRequestListener::handleKernelRequestEvent() 3
  15. Sentry\SentryBundle\EventListener\TracingSubRequestListener::handleKernelRequestEvent() 2
  16. Sentry\SentryBundle\EventListener\LoginListener::handleKernelRequestEvent() 0
  17. Knp\Bundle\PaginatorBundle\Subscriber\SlidingPaginationSubscriber::onKernelRequest() 0
  18. Symfony\UX\LiveComponent\EventListener\LiveComponentSubscriber::onKernelRequest() 0

"kernel.response" event

  1. Symfony\Component\AssetMapper\AssetMapperDevServerSubscriber::onKernelResponse() 2048
  2. Sentry\SentryBundle\EventListener\TracingRequestListener::handleKernelResponseEvent() 15
  3. Sentry\SentryBundle\EventListener\TracingSubRequestListener::handleKernelResponseEvent() 15
  4. Symfony\Component\Security\Http\Firewall\ContextListener::onKernelResponse() 0
  5. Symfony\Component\Security\Http\Firewall\ContextListener::onKernelResponse() 0
  6. Nelmio\SecurityBundle\EventListener\ContentTypeListener::onKernelResponse() 0
  7. Nelmio\SecurityBundle\EventListener\ReferrerPolicyListener::onKernelResponse() 0
  8. Symfony\Component\HttpKernel\EventListener\ResponseListener::onKernelResponse() 0
  9. Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::onKernelResponse() 0
  10. Symfony\Component\Security\Http\RememberMe\ResponseListener::onKernelResponse() 0
  11. Symfony\UX\LiveComponent\EventListener\LiveComponentSubscriber::onKernelResponse() 0
  12. Nelmio\SecurityBundle\EventListener\ClickjackingListener::onKernelResponse() 0
  13. Nelmio\SecurityBundle\EventListener\ContentSecurityPolicyListener::onKernelResponse() 0
  14. Nelmio\SecurityBundle\EventListener\XssProtectionListener::onKernelResponse() 0
  15. Symfony\Component\HttpKernel\EventListener\CacheAttributeListener::onKernelResponse() -10
  16. Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelResponse() -100
  17. Symfony\Component\HttpKernel\EventListener\ErrorListener::removeCspHeader() -128
  18. Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener::onKernelResponse() -128
  19. Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener::onResponse() -255
  20. Symfony\Component\HttpKernel\EventListener\SessionListener::onKernelResponse() -1000

"kernel.terminate" event

  1. Sentry\SentryBundle\EventListener\TracingRequestListener::handleKernelTerminateEvent() 5
  2. Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelTerminate() -1024

"kernel.view" event

  1. Symfony\UX\LiveComponent\EventListener\LiveComponentSubscriber::onKernelView() 0
  2. Symfony\Bridge\Twig\EventListener\TemplateAttributeListener::onKernelView() -128

@Mauriceu
Copy link
Author

I setup a repo in hopes of reproducing the issue. However, isGranted seems to be working here. The reproducer uses the in-built form-login and in_memory provider....

@Mauriceu
Copy link
Author

Problem solved. The security config includes not only one but two firewalls and the security context did not match the correct one, which led to the "sessionKey" property in the ContextListener being set incorrectly. Guess I should've posted the full security.yaml - hindsight 20/20, will do better next time 🗡

We use 2 firewalls, one for authenticating Users and another for authenticating Admins. The admin firewall pattern is "^/admin" the User firewall has a pattern of "^/". Guess you see where this is going...
The admin firewall did not match our "/_components/Admin:SomeComponent:..." URLs which would be used when executing a LiveAction, thus always falling back to the User firewall.

Solved the issue by changing the admin firewall pattern to: "^/(admin|_components/Admin:)"

@smnandre
Copy link
Member

Well, i would not have checked that first, to be honest. Happy if everything is good for you!

@smnandre smnandre removed the Bug Bug Fix label Jan 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Needs Review Needs to be reviewed
Projects
None yet
Development

No branches or pull requests

3 participants