Symfony2 FOS, redirect when not logged in - symfony

I'd like to redirect user to login page when nobody is not logged in. I wrote lister to that, but all the time I'm getting ERR_TOO_MANY_REDIRECTS error.
Maybe is another method to achieve it globaly, and redirect user. Checking if user is logged in every controller is not a solution.
Listener\AccessListener.php
namespace Main\UserBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\SecurityContext;
class AccessListener{
private $security;
private $router;
public function __construct($security, $router){
$this->security = $security;
$this->router = $router;
}
public function onKernelRequest(GetResponseEvent $event){
if ($event->isMasterRequest()) {
if( $this->security->isGranted('IS_AUTHENTICATED_REMEMBERED') ){
$url = $this->router->generate('fos_user_security_login');
$event->setResponse(new RedirectResponse($url));
}
}
}
}
security.yml
security:
access_denied_url: /login
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
always_use_default_target_path: false
default_target_path: /
logout: true
anonymous: true
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
How do I correct it?

Your listener is checking whether a user is logged in and then, if they are not, they are being forwarded to the login page. At which point the listener checks whether the user is logged in... and so on.. and so on.
To stop this redirect loop you could check whether the current route being requested is the route that you are forwarding to, like so...
public function onKernelRequest(GetResponseEvent $event){
if ($event->isMasterRequest()) {
$loginRoute = 'fos_user_security_login';
$request = $event->getRequest();
// Return if current route and login route match
if ($request->get('_route') === $loginRoute) {
return;
}
if( $this->security->isGranted('IS_AUTHENTICATED_REMEMBERED') ){
$url = $this->router->generate($loginRoute);
$event->setResponse(new RedirectResponse($url));
}
}
}
However, a better way of doing this would be to add the root to the access control section needing a logged in user. This would allow you to have a better control over accessible and inaccessible paths.
access_control:
... current stuff ...
- { path: ^/, role: ROLE_USER }

You need to check whether the current security context holds (or in your case if it does not hold) a fully authenticated user session as follows:
if( false == $this->security->isGranted('IS_FULLY_AUTHENTICATED') ){

Related

HWIOAuthBundle custom user provider will not login user

I'm using HWIOAuthBundle with Symfony 4.1.
My custom user provider gets called and returns a valid user, however the user is not logged in after the redirect. The web profiler shows: logged in as anon; anonymous token; firewall main
I've simplified the provider class below for brevity and it's only considering twitter right now. The conditions around $source are so I can add more later.
I have used xdebug to make sure loadUserByOauthuserResponse() is being called and that users are both being created in the case of a new user, or the existing user is being returned when it exists.
Question: If loadUserbyOauthUserResponse() IS returning a valid user entity, then what could be preventing it from creating a valid session with this user?
<?php
namespace App\Security;
...
class OAuthProvider extends OAuthUserProvider
{
...
public function loadUserByOAuthUserResponse(UserResponseInterface $response): User
{
/** #var EntityManager $em */
$em = $this->container->get('doctrine.orm.entity_manager');
$repo = $em->getRepository('App:User');
$source = $response->getResourceOwner()->getName();
$email = null;
$data = [];
$newUser = false;
// Set email and socialUser.
if ($source === 'twitter') {
$data = $response->getData();
$email = $data['email'];
}
// Check if this user already exists in our app.
$user = $repo->findOneBy(['email' => $email]);
if ($user === null) {
$newUser = true;
$user = new User();
$user->setPassword($this->strand(32));
}
// Set session and user data based on source.
if ($source === 'twitter') {
$name = $data['name'];
if ($newUser) {
$user->setNickName($name);
$user->setEmail($email);
$em->persist($user);
$em->flush();
}
}
return $user;
}
}
hwi_oauth.yaml
hwi_oauth:
# list of names of the firewalls in which this bundle is active, this setting MUST be set
firewall_names: [main]
# https://github.com/hwi/HWIOAuthBundle/blob/master/Resources/doc/2-configuring_resource_owners.md
resource_owners:
twitter:
type: twitter
client_id: '%env(TWITTER_ID)%'
client_secret: '%env(TWITTER_SECRET)%'
options:
include_email: true
services.yaml:
app.oauth_aware.user_provider.service:
class: App\Security\OAuthProvider
arguments: ['#service_container']
security.yaml:
security:
# https://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
encoders:
App\Entity\User: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
app_users:
entity: { class: App\Entity\User, property: email }
hwi:
id: app.oauth_aware.user_provider.service
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
provider: app_users
anonymous: ~
remember_me:
secret: "%env(APP_SECRET)%"
lifetime: 2592000
path: /
guard:
authenticators:
- App\Security\LoginFormAuthenticator
entry_point: App\Security\LoginFormAuthenticator
logout:
path: /logout
target: /
oauth:
resource_owners:
twitter: "/login/check-twitter"
default_target_path: /
login_path: /
failure_path: /user-login
oauth_user_provider:
service: app.oauth_aware.user_provider.service
switch_user: ~
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/recover, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
I had the same problem until I found in logs "Token was deauthenticated after trying to refresh it."
And after that I found this solution.
Token was deauthenticated after trying to refresh it
I added isEqualTo and did not logged me out.

Redirect logged in users to home page when trying to access login page

I am trying to make my Symfony2 website redirect logged in users to another page when trying to access the login page.
This is my security.yml:
security:
encoders:
Cocoa\LoginBundle\Entity\User:
algorithm: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
providers:
administrators:
entity: { class: CocoaLoginBundle:User, property: username }
firewalls:
admin_area:
pattern: ^/administration
form_login:
login_path: login
check_path: login_check
logout:
path: /administration/logout
invalidate_session: true
target: /
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY}
- { path: ^/administration, roles: ROLE_ADMIN }
And this is my controller:
class LoginController extends Controller {
public function loginAction() {
if (false === $this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY')) {
return $this->redirect($this->generateUrl('public_homepage'));
}
$request = $this->getRequest();
$session = $request->getSession();
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render('CocoaLoginBundle:Login:login.html.twig', array(
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error));
}
public function loginCheckAction() {
}
}
If I access the login page as anonymous user I do see the login form. But after I log in and load the login page I get a 500 server error:
The security context contains no authentication token. One possible reason may be that there is no firewall configured for this URL.
A proposed fix I read in another topic is to put the login page behind a firewall. I did that but still error 500.
What am I doing wrong?
Thanks!
It's probably because you have only IS_AUTHENTICATED_ANONYMOUSLY role for path ^/login set in security.yml. So then there is no authentication token allowed to be passed to LoginController.
You should try either to add ROLE_ADMIN also for ^/login path in the access_control, or include ^/login under ^/administration so it will become ^/administration/login.
And as condition in LoginController you should try to use
if($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
return $this->redirect($this->generateUrl('public_homepage'));
}

How to set a login form for admins and another for other users using FOSUserBundle?

When having a backend for admin users, it is interesting to have a login form, and at the same time having a normal login form for normal users in the public area of our website.
Is that possible using FOSUserBundle? How can it be done "the Symfony2" way?
First we need to configure some special routes for the admin area:
admin_login:
pattern: /admin/login
defaults: { _controller: FOSUserBundle:Security:login }
admin_login_check:
pattern: /admin/login_check
defaults: { _controller: FOSUserBundle:Security:check }
admin_logout:
pattern: /admin/logout
defaults: { _controller: FOSUserBundle:Security:logout }
Next configure a special firewall for the admin area using these routes, and define them to be anonymously accessed:
firewalls:
...
admin:
pattern: /admin/(.*)
form_login:
provider: fos_userbundle
login_path: admin_login
check_path: admin_login_check
default_target_path: yourproject_admin_default_index
logout:
path: admin_logout
target: admin_login
anonymous: true
context: application
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
context: application
...
access_control:
...
- { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
Ok! We have just separated our login system in two parts: admin and main.
Let's override the SecurityController. For that we will need to create a custom bundle which parent is FOSUserBundle (check the doc for that). In this new bundle, create the controller:
<?php
namespace YourProject\UserBundle\Controller;
use FOS\UserBundle\Controller\SecurityController as BaseController;
/**
* {#inheritDoc}
*/
class SecurityController extends BaseController
{
/**
* {#inheritDoc}
*/
public function renderLogin(array $data)
{
$requestAttributes = $this->container->get('request')->attributes;
if ('admin_login' === $requestAttributes->get('_route')) {
$template = sprintf('AdminBundle:Security:login.html.twig');
} else {
$template = sprintf('FOSUserBundle:Security:login.html.twig');
}
return $this->container->get('templating')->renderResponse($template, $data);
}
}
That's it! Now you can write your AdminBundle:Security:login.html.twig :)
NOTE: Don't forget to use the admin routes in your admin area! (in the login form action, the logout link, etc)
Regarding to the approved answer, I made some adjustments in my Symfony 3.2.8 project in order to work correctly.
Instead of
$requestAttributes = $this->container->get('request')->attributes;
in the Security Controller, I used $requestAttributes = $this->container->get('request_stack')->getCurrentRequest();.

Symfony2 - Access for Login and Register page for Anonymous only not Users

I have this website with a login form and after I successfully logged in, I am redirected to the index. But when I click the back button, it lets me still view the login form which is not good. I want only the login form to be accessible by anonymous viewers only and not users who have logged in already. Is there a simple way to do this in symfony2? thanks
Here is my security.:
jms_security_extra:
secure_all_services: false
expressions: true
security:
encoders:
Mata\UserBundle\Entity\User:
algorithm: sha1
encode_as_base64: false
iterations: 1
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
user_db:
entity: { class: MataUserBundle:User, property: username }
firewalls:
secured_area:
pattern: ^/
anonymous: ~
form_login:
check_path: /login_check
login_path: /login
logout:
path: /logout
target: /
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
This may not be the best or proper way to do it, but its been the only way I could figure it out.
In my loginAction method I do this (see the $secured variable). If the user session is authenticated then I redirect them to the home/index page. I don't know of a way to do it via the firewall config because I don't believe the login page would ever have a firewall attached to it.
/**
* #Route("/login", name="login")
* #Template()
*/
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
// if the session is secured (user is logged in)
// then $secured will be an object with various user information.
$secured = unserialize($session->get('_security_secured'));
if ($secured) {
return $this->redirect($this->generateUrl('home'));
}
// get the login error if there is one
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
}
return array(
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
'embed' => $request->isXmlHttpRequest()
);
}
With symfony 4 you can solve it with allow_if :
access_control:
- { path: /login, allow_if: "!is_authenticated()" }
here's the list of expressions available within allow_if string
http://symfony.com/doc/current/security/expressions.html#security-expression-variables

Extend UserProvider for FOS UserBundle

I am building a site using Symfony2 and it will be a white-label type of site, where multiple domains map to the same server. So coolsite.customer1.com and aservice.customer2.com would map to the same site, but would need to appear different to the end user. I already solved for the domains, and loading the unique configurations as a service.
With the FOS UserBundle setup and running with a custom user (that has the domain_id stored in it), registration, login, etc works fine except that users from domain1 can login to domain2 also. This is expected in the FOS UserBundle. I need to make modifications to the bundle so that it only will authenticate users on the domain they are assigned to.
I have created a userProvider that extends the original userProvider in FOS and have overridden the loadUserByUsername method to also check the domain. See below:
use FOS\UserBundle\Security\UserProvider as FOSProvider;
use Symfony\Component\DependencyInjection\ContainerInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use Me\CoreBundle\Models\Core;
class UserProvider extends FOSProvider {
/**
*
* #var ContainerInterface
*/
protected $container;
public function __construct(UserManagerInterface $userManager, ContainerInterface $container) {
parent::__construct($userManager);
$this->container = $container;
}
/**
* {#inheritDoc}
*/
public function loadUserByUsername($username)
{
$core = $this->container->get('me_core');
/* #var $core Core */
$user = $this->findUserBy(array(
'username'=>$username,
'domain_id'=>$core->getDomainMap()->getId(),
));
if (!$user) {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
return $user;
}
public function findUserBy(array $criteria) {
return $this->userManager->findUserBy($criteria);
}
}
I have configured the service with the following.
services:
me.security.authentication.userprovider:
class: Me\UserBundle\Security\UserProvider
arguments:
- #fos_user.user_manager
- #service_container
My security.yml looks like this:
security:
providers:
me.security.authentication.userprovider:
id: fos_user.user_provider.username
encoders:
FOS\UserBundle\Model\UserInterface: sha512
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
logout: true
anonymous: true
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/_wdt, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/public, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
- { path: ^/, role: ROLE_USER }
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
What happens when I try to access the site is an exception. "ServiceNotFoundException: The service "security.authentication.manager" has a dependency on a non-existent service "security.user.provider.concrete.fos_userbundle"."
I based my modifications on This Cookbook Recipe
Any ideas? I am thoroughly stumped on this.
I was able to get it to work. Turns out I needed to make the "id" the same as the name of the service I was using. The commented lines are the originals that came with the bundle.
security:
providers:
me.security.authentication.userprovider:
id: me.security.authentication.userprovider
#fos_userbundle:
#id: fos_user.user_provider.username

Resources