I'm new in symfony and I'm following the login form tutorial(https://symfony.com/doc/2.8/cookbook/security/form_login_setup.html) but I get the following error:
Uncaught PHP Exception LogicException: "The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?" at /var/www/html/app/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php line 157
Context: { "exception": "Object(LogicException)" }
I use the same SecurityController:
class SecurityController extends Controller{
* #Route("/admin", name="login")
public function loginAction(Request $request)
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'security/login.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
}
/**
* #Route("/login_check", name="login_check")
*/
public function loginCheckAction()
{
// this controller will not be executed,
// as the route is handled by the Security system
}
security.yml
# To get started with security, check out the documentation:
http://symfony.com/doc/current/book/security.html
security:
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
app_users:
ldap:
service: app.ldap
base_dn: DC=corp, DC=int
search_dn: null
search_password: null
filter: (uid={username})
default_roles: ROLE_USER
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
# activate different ways to authenticate
# http_basic: ~
# http://symfony.com/doc/current/book/security.html#a-configuring-how-your-users-will-authenticate
# form_login: ~
# http://symfony.com/doc/current/cookbook/security/form_login_setup.html
secure:
provider: app_users
form_login_ldap:
service: app.ldap
dn_string: "uid={username},ou=Users,dc=corp,dc=int"
check_path: login_check
login_path: admin
logout: true
In your security.yml, try to replace
check_path: login_check
by
check_path: /login_check
And make your method like this :
/**
* #Route("/login_check", name="login_check")
*/
public function loginCheckAction()
{
throw new \RuntimeException('You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.');
}
Related
If I call / and am not logged in, I get the error ERR_TOO_MANY_REDIRECTS.
Actually, I should be redirected to the login page. Where’s the mistake?
security.yaml:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
logout:
path: app_logout
form_login:
login_path: app_login
check_path: app_login
access_control:
- { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
role_hierarchy:
ROLE_USER: ROLE_USER
ROLE_ADMIN: [ROLE_USER, ROLE_ALLOWED_TO_SWITCH]
Controller:
/**
* #Route("/{id}",defaults={"id" = null} , name="app_dashboard")
*/
public function index(PositiveTimeRepository $positiveTimeRepository, $id): Response
{
return $this->render('dashboard/index.html.twig', [
'positiveData' => $positiveTimeRepository->findBy([
'user' => $this->getUser()]),
'issetGetID' => $id
]);
}
SecurityController:
/**
* #Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('login/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error
]);
}
I updated my controllers and security.yaml
You don’t have to check for the roles in your controller yourself the security package does that with the firewall you defined, but you would have to define your login url:
firewalls:
main:
# ...
form_login:
# "login" is the name of the route created previously
login_path: login
check_path: login
I want members to log in from the frontend and I've defined my authentication handler below and added it as a service which gives me a json response as expected.
<?php
namespace AppBundle\Handler;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
protected $router;
//protected $security;
protected $userManager;
protected $service_container;
public function __construct(RouterInterface $router, $userManager, $service_container)
{
$this->router = $router;
//$this->security = $security;
$this->userManager = $userManager;
$this->service_container = $service_container;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token) {
if ($request->isXmlHttpRequest()) {
$result = array('success' => true);
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
else {
// Create a flash message with the authentication error message
$request->getSession()->set(SecurityContext::AUTHENTICATION_ERROR, $exception);
$url = $this->router->generate('fos_user_security_login');
return new RedirectResponse($url);
}
return new RedirectResponse($this->router->generate('anag_new'));
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {
if ($request->isXmlHttpRequest()) {
$result = array('success' => false, 'message' => $exception->getMessage());
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
return new Response();
}
}
However, I am getting same results regardless of whether a user is registered or not. Here is the response
{"success":false,"message":"Bad credentials."}
Here is my security.yml
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: /admin(.*)
context: user
form_login:
provider: fos_userbundle
login_path: /admin/login
use_forward: false
check_path: /admin/login_check
failure_path: null
logout:
path: /admin/logout
target: /admin/login
anonymous: true
main:
pattern: .*
context: user
form_login:
provider: fos_userbundle
login_path: /login
use_forward: false
check_path: fos_user_security_check
failure_path: null
success_handler: authentication_handler
failure_handler: authentication_handler
logout: true
anonymous: true
routing.yml
fos_user_security_check:
path: /login_check
defaults:
_controller: FOSUserBundle:Security:check
fos_user_security_logout:
path: /logout
defaults:
_controller: FOSUserBundle:Security:logout
The esiest way to implement API authentication for me is to implement the brand new Guard Authentication Interface
http://symfony.com/doc/current/cookbook/security/guard-authentication.html
This simple class allows you to define the process, which instantiate, handles and post-processes authentication.
Enabling the service is as easy as
# app/config/security.yml
security:
# ...
firewalls:
# ...
main:
anonymous: ~
logout: ~
guard:
authenticators:
- app.my_authenticator
# if you want, disable storing the user in the session
# stateless: true
# maybe other things, like form_login, remember_me, etc
# ...
You also need a user provide for this
http://symfony.com/doc/current/cookbook/security/custom_provider.html
Using the Guard you can handle any type of custom authentication (bearer, forms, cookies, GET tokens etc)
I'm trying to do functional test for the routes that are behind the firewall. I'm not sure what I'm doing wrong but the test for the route admin/dashboard fails. Any ideas?
<?php
namespace AppBundle\Tests;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
class ApplicationAvailabilityFunctionalTest extends WebTestCase
{
private $client;
public function setUp()
{
$this->client = self::createClient();
}
/**
* #dataProvider urlProvider
*/
public function testPageIsSuccessful($url)
{
$this->client->request('GET', $url);
$this->assertTrue($this->client->getResponse()->isSuccessful());
}
public function urlProvider()
{
$this->logIn();
return array(
array('/'),
array('/admin/login'),
array('/admin/dashboard'),
);
}
public function logIn()
{
$this->client = self::createClient();
$session = $this->client->getContainer()->get('session');
$firewall = 'our_db_provider';
$token = new UsernamePasswordToken('admin', 'admin', $firewall, array('ROLE_ADMIN'));
$session->set('_security_'.$firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
}
//UPDATE
Here's the error I get
1) AppBundle\Tests\ApplicationAvailabilityFunctionalTest::testPageIsSuccessful with data set #2 ('/admin/dashboard')
Failed asserting that false is true.
/Users/me/Projects/cms/src/AppBundle/Tests/ApplicationAvailabilityFunctionalTest.php:27
//UPDATE 2
Here's the dump of $token variable
Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken {#488
-credentials: null
-providerKey: "security"
-user: "admin"
-roles: array:1 [
0 => Symfony\Component\Security\Core\Role\Role {#487
-role: "ROLE_ADMIN"
}
]
-authenticated: true
-attributes: []
}
//UPDATE 3
`security:
encoders:
AppBundle\Entity\Admin\User:
algorithm: bcrypt
providers:
our_db_provider:
entity:
class: AppBundle\Entity\Admin\User
property: username
access_control:
- { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, roles: ROLE_ADMIN }
firewalls:
default:
anonymous: ~
http_basic: ~
form_login:
login_path: /admin/login
check_path: /admin/login_check
csrf_provider: security.csrf.token_manager
logout:
path: /admin/logout
target: /admin/login
provider: our_db_provider
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~`
The route is not public
The failing test is on the /admin/dashboard route that probably is protected by authentication so the server response is not successfully (200 OK) but (403 access denied or 302 redirect)
So you must test your route differently: the route is protected so check for 403 or that redirect to login page
Check the doc about How to Simulate Authentication with a Token in a Functional Test
And test that an authenticated user see correctly the page
I made the Login-Action Tutorial from the symfony site: http://symfony.com/doc/current/cookbook/security/form_login_setup.html
When I do my Login in the form, I get the following error:
The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?
Here is my security.yml
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_USER: ROLE_USER
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
in_memory:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }
admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
access_denied_url: no_access
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login
security: false
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
default_target_path: home
logout:
path: /logout
target: /login
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
- { path: ^/, roles: ROLE_USER }
My Security Controller:
class SecurityController extends Controller
{
/**
* #Route("/login", name="login_route")
* #Template()
*/
public function loginAction()
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'TripAdminBundle:Main:login.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
}
/**
* #Route("/login_check", name="login_check")
*/
public function loginCheckAction()
{
// this controller will not be executed,
// as the route is handled by the Security system
}
}
The redirect after Click on the submit Button is to login_check but there is no code in it because symfony says: the route is handled by the Security system
Bute I get this error. Can someone help me with this please?
return array directly , don't use render method and twig file name .
return array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
I have a Symfony 2.7.6 project with custom Simple Form authentication provider and support for remember me functionality as well as impersonalization feature. Everything works as expected.
However, I want to introduce another authentication provider that will allow requests regardless of session state using two HTTP headers for authentication (e.g. API-Client-Id and API-Client-Token) for third-party applications.
I've created a Simple Pre-Auth authentication provider that validates these header fields and creates authentication token with empty User instance on success.
However, it looks like Symfony is trying to remember those API authentications using session, so I'm getting the following error on the second request: "You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.".
I can set stateless: true flag in my firewall configuration to disable session support, but it will disable it for both auth providers.
SO, how do I preserve existing functionality with my Simple Form authenticator and yet create another layer of authentication to be used for single stateless API requests?
I'm not sure if my approach is conceptually correct. I will gladly accept any suggestions and will provide any relevant information on first request.
Here's my security.yml config:
security:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: ~
form_login:
login_path: app.login
check_path: app.session.sign_in
username_parameter: username
password_parameter: password
success_handler: app.security.login_handler
failure_handler: app.security.login_handler
require_previous_session: false
logout:
path: app.session.sign_out
invalidate_session: false
success_handler: app.security.logout_success_handler
# Simple form auth provider
simple_form:
authenticator: app.security.authenticator.out_service
# Token provider
simple_preauth:
authenticator: app.security.authenticator.api_client
remember_me:
name: "%app.session.remember_me.name%"
key: "%secret%"
lifetime: 1209600 # 14 days
path: /
domain: ~
always_remember_me: true
switch_user: { role: ROLE_ADMIN }
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/recover-password, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /, roles: IS_AUTHENTICATED_REMEMBERED }
providers:
main:
entity:
class: App\AppBundle\Model\User
property: id
encoders:
App\AppBundle\Model\User: plaintext
role_hierarchy:
ROLE_ADMIN: [ROLE_USER, ROLE_ACTIVE]
ROLE_API_CLIENT: ~
ROLE_USER: ~
ROLE_ACTIVE: ~
ApiClientAuthenticator.php:
<?php
namespace App\AppBundle\Security;
use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use App\AppBundle\Model\User;
class ApiClientAuthenticator implements SimplePreAuthenticatorInterface
{
/** #var LoggerInterface */
protected $logger;
/** #var array */
protected $clients;
/**
* #param array $clients
*/
public function __construct(array $clients)
{
$this->clients = $clients;
}
public function createToken(Request $request, $providerKey)
{
$clientId = $request->headers->get('Api-Client-Id');
$clientSecret = $request->headers->get('Api-Client-Secret');
if (!$clientId || !$clientSecret) {
return null;
}
return new PreAuthenticatedToken(
'anon.',
[$clientId, $clientSecret],
$providerKey
);
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
list ($clientId, $clientSecret) = $token->getCredentials();
$foundClient = null;
foreach ($this->clients as $client) {
if ($client['id'] == $clientId) {
if ($client['secret'] == $clientSecret) {
$foundClient = $client;
break;
}
}
}
if (!$foundClient) {
throw new AuthenticationException;
}
$user = new User;
$user->setApiClient(true);
return new PreAuthenticatedToken(
$user,
$foundClient,
$providerKey,
['ROLE_API_CLIENT']
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return ($token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey);
}
}