I cant find any clear posts on how to use the annotation #Security of symfony.
What are the parameters i can use? And most important, how can i secure a controller from guests and only accessible for users?
Currently i have
/**
*
* #Route("/reseller/create/", name="app_reseller_create", methods={"POST", "GET"})
* #IsGranted("ROLE_ADMIN")
*/
public function create(Request $request): Response
{
}
#IsGranted
If you only want to check if a user is logged in, you can use a special attribute instead of a role. For the full controller you must set it over the class definition.
/**
* #IsGranted("IS_AUTHENTICATED_FULLY")
*/
class MyClass {
There are some special attributes that can use everywhere you can use ROLE_.
IS_AUTHENTICATED_FULLY is logged in.
IS_AUTHENTICATED_REMEMBERED is logged in or have an remembered cookie.
IS_AUTHENTICATED_ANONYMOUSLY any user have this
IS_ANONYMOUS only guests
IS_REMEMBERED only users with the rmembered cookie
IS_IMPERSONATOR only users that impersonating another user in the session.
The IS_ANONYMOUS, IS_REMEMBERED and IS_IMPERSONATOR attributes were introduced in Symfony 5.1.
#Security
The security annotation is more flexible as the IsGranted annotation and can use expressions.
Say you want a page, that only can be access if the user is an admin and have a specific token in his request.
with #IsGranted
/**
* #IsGranted("ROLE_ADMIN", statusCode=404)
*/
public function show(Request $request, Post $post)
{
if (!$request->request->has('privatetoken') || 'mytoken' !== $request->request->get('privatetoken')) {
return $this->createNotFoundException('not found');
}
// Show Post
}
with #Security
/**
* #Security("is_granted('ROLE_ADMIN') and request.get('privatetoken') == 'mytoken'", statusCode=404)
*/
public function show(Post $post)
{
// Show Post
}
With this, you must have an admin role and have an privatetoken parameter in your url like mydomain.com/post/show/?privatetoken=mytoken and you don't need the Request instance.
The expression has access to the following variables:
token: The current security token;
user: The current user object;
request: The request instance;
roles: The user roles;
and all request attributes.
There are too many possibilities to post this all. but i think it shows the difference to #IsGranted.
Symfony Docs
Related
I am trying to implement the LexikJwt package for my new API:
https://github.com/lexik/LexikJWTAuthenticationBundle
I just added my custom command to create a proper JWT token including my default roles for local testing. The next step would be to actually protect my routes. What i can't find however is exactly on how to do that. This is my current controller implementation using annotation routes
/**
* #Route(
* "/search",
* name="search",
* methods={"GET"},
* )
*/
class Search
{
/**
* #param AddressService $addressService
* #param Request $request
* #return JsonApiResponse
* #throws Exception
*/
public function __invoke(AddressService $addressService, Request $request)
{
$address = $addressService->createFromParams($request->query->all());
try {
$addressCollection = $addressService->search($address);
} catch (AddressNotFoundException $e) {
$addressCollection = [];
}
return new JsonApiResponse($addressCollection);
}
}
However the docs do not say anything about annotation routes and only on yml configs on security firewalls. The main thing i need is the token to be verified:
Is the token valid (matching the public key)
Is the token not expired
Does the route match the given roles
For example, i want the code above, which is an address service to match only if the token matches above and the token holds the role or scope: address:search.
Hope someone can help,
Pim
The issue was that the role should start with ROLE_. It is possible to override it though but this was the use case.
In security.yaml file we define the access control for various routes and the ROLES who can access that same route.
But how can we set the user, who is logged-in but can't revisit the /login page unless and untill it logs out and "ROLE_USER" changes to "anon".
I am new to Symfony 4.2.
Controller:
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
//use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class SecurityController extends AbstractController
{
/**
* #Route("/login", name="login")
*/
public function login(Request $request, AuthenticationUtils $utils, AuthorizationCheckerInterface $authChecker)
{
// to check whether user is looged-in
if ($authChecker->isGranted('IS_AUTHENTICATED_FULLY')) {
die('Logged in user cannot access this page');
}
// get the login error if there is one
$error = $utils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $utils->getLastUsername();
return $this->render('security/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error
]);
}
public function logout()
{
# code...
}
You cannot deny access for logged in user to the login page by editing security.yml. It is all users of a Symfony app, whether logged in or not, will have the base access privilege: IS_AUTHENTICATED_ANONYMOUSLY and Symfony does not have a exclusive role to not logged in user.
However, you can achieve the same thing by checking whether the user has logged in or not in your controller and perform a redirect or throw an AccessDeniedException:
public function login($name, AuthorizationCheckerInterface $authChecker)
{
if ($authChecker->isGranted('IS_AUTHENTICATED_FULLY')) {
throw new AccessDeniedException('Logged in user cannot access this page');
}
// ...
}
As I mentioned in the comments, in my opinion throwing an AccessDeniedException to an already logged in user isn't a good approach. What would your users think? If I have already logged in, why can't I access a page, that I can normally access even if I'm not logged in.
Therefore I'd strongly suggest to redirect logged in users, when accessing the /login path, to the start page of your application.
Just adapt the if-condition block in the method login of your SecurityController:
if ($authChecker->isGranted('IS_AUTHENTICATED_FULLY)) {
$this->redirectToRoute('name of the route - replace with an appropriate value');
}
You should take care that the route you're redirecting to, doesn't cause another redirect and thus puts you in an endless loop.
AdvancedUserInterface implement has isEnabled method for the User entity. But user properties storing in session. Disabling a user wont work until re-login.
So i need the clear specific user session by user id.
Or, i need the check database for refresh serialized user data.
What is the correct way and how can i do?
I had the same problem with FOSUserBundle, where I would disable a user but if they were logged in they could continue under the existing session. The disable only took effect when they tried to log in again.
To get around it I found a different way of doing this using a Security Voter. I created a custom voter that runs each time you call the "->isGranted" checker. I check for isGranted on every page for various ROLE levels. This voter then checks if the user isEnabled, if they aren't the voter votes to fail and disallows the user, returning them to the login screen.
My Custom Voter:
namespace AppBundle\Security;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* Purpose: A Voter that checks if the user is still enabled. Default FOSUserBundle behavior allows disabled users to
* continue under their existing session. This voter is designed to prevent that
* Created by PhpStorm.
* User: Matt Emerson
* Date: 2/17/2018
* Time: 1:24 PM
*/
class userVoter extends Voter
{
protected function supports($attribute, $subject)
{
//this Voter is always available
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();
if (!$user instanceof User) {
// the user must be logged in; if not, deny access
return false;
} elseif ($user->isEnabled() === false) {
// the user is not enabled; deny access
return false;
}
//otherwise return true
return true;
}
}
How to redirect the user to last page visited after login in Symfony with fosuserbundle?
In my controller, I check first if user is logged in. Then if he isn't, I redirect him to the login page. Here is the short code I used at the beginning of my controller.
$autenthicated = $this->checkAuth();
if($autenthicated==true){
return $this->render('MainBundle:Default:home.html.twig');
}else{
return $this->redirect($this->generateUrl('login_connect'));
}
The problem is that after the user has logged in, he is redirected to the main page instead the last page visited.
How should I redirect him to my custom login page? apparently this is not working:
return $this->redirect($this->generateUrl('login_connect'));
You don't have to handle the redirection to the login route by yourself.
If you throw an AccessDeniedException in your controller, the security component will redirect you to the login page (with the page you tried to access as parameter).
After login, the native LoginSuccessHandler will redirect you to the desired page.
You can also use the Controller shortcut :
$this->denyAccessUnlessGranted(['ROLE_USER']);// check your needed roles here
If you need to override the redirection logic after login, you can define a service and tell the security component to use this one to handle the redirection.
Here is a dummy example :
Your php service with redirection logic :
AppBundle/Security/LoginSuccessHandler.php
class LoginSuccessHandler implements AuthenticationSuccessHandlerInterface
{
// …
/**
* #param Request $request
* #param TokenInterface $token
* #return RedirectResponse
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
// your stuff
return new RedirectResponse($this->router->generate(‘custom_route’));
}
}
Service configuration :
# services.yml
app.login_success_handler:
public: false
class: AppBundle\Security\LoginSuccessHandler
arguments: ["#security.token_storage", "#router"]
Configure the security component to use your custom handler :
# security.yml
security:
firewalls:
main:
form_login:
success_handler: app.login_success_handler
I'm redesigning a website in symfony2, where users must be able to unsubscribe.
When they do, for database integrity reasons, we have to de-activate their account, and not completely delete it. We must also keep track of their personnal information, like email adress, for a certain time (legal obligation).
I'm using FOSUserBundle, and I initially thought a simple way to deactivate accounts of people who unsubscribed, would be to set the User property "enabled" to false.
But when a user is not enabled, if he tries to register again with the same email adress, he sees : "The email is already used". And I would like he could register again and create a new account (or reactivate the old one).
Is there a way to do this ?
Is there a best practice to handle unsubscriptions with FOSUserBundle ?
Thanks for your help.
You need to override the main registration process with, the easyextendbundle, you could have a look at the documentation at this url:
https://sonata-project.org/bundles/user/2-2/doc/reference/installation.html
Then in your extended controller, you have to create a new action to activate or deactivate your user, this action must be public in the security rules.
In this method, you could use the services to activate or deactivate a user:
fos:user:deactivate Deactivate a user
fos:user:activate Activate a user
You could inspire yourself from this earlier post : Symfony2: List Users with Option to Deactivate Each User
Another possibility is to update your User class with the property $subscribed as below:
/**
* #ORM\Column(name="subscribed", type="boolean")
*/
protected $subscribed;
public function setSubscribed($subscribed)
{
$this->subscribed = $subscribed;
}
public function isSubscribed($)
{
return $this->subscribed;
}
public function changeSubscribed()
{
$this->subscribed = !$this->subscribed;
}
This avoids e-mail address conflicts without adding another third-party bundle.
Edit (note also set method above)
in YourBundle\EventListener
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RegistrationListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
);
}
/**
* Persist organization on staff registration success.
*
* #param \FOS\UserBundle\Event\FormEvent $event
*/
public function onRegistrationSuccess(FormEvent $event)
{
/** #var $user \FOS\UserBundle\Model\UserInterface */
$user = $event->getForm()->getData();
$user->setSubscribed(true);
}
}
also, add to app/config/services.yml:
your_bundle.regisration.listener:
class: YourBundle\EventListener\RegistrationListener
tags:
- { name: kernel.event_subscriber }