Symfony2 hold behavior when extending DefaultAuthenticationSuccessHandler - authentication

I want to be able to login in my app via the usual login form and using AJAX directly sending the user/password to the login_check path.
The idea is when user access via AJAX return Response with code 200 or 400 depending if the login success or fails.
Looking at other threads I finally decide to extend the default handlers DefaultAuthenticationSuccessHandler and DefaultAuthenticationFailureHandler to achieve this, export as services and specify in the success_handler property in my secure area.
File service.yml
services:
authentication.success_handler:
class: %mycustom.authentication.success_handler.class%
arguments: ['#security.http_utils', {} ]
public: false
tags:
- { name: 'monolog.logger', channel: 'security' }
authentication.failure_handler:
class: %mycustom.authentication.failure_handler.class%
arguments: ['#http_kernel', '#security.http_utils', {}, '#logger' ]
public: false
tags:
- { name: 'monolog.logger', channel: 'security' }
File security.yml
secured_meems_area:
pattern: ^/somrurl/
form_login:
login_path: /somrurl/login
check_path: /somrurl/api/login_check
success_handler: authentication.success_handler
failure_handler: authentication.failure_handler
require_previous_session: false
All this seems to work, except the behaviour of my extendend handler isn't like the original one. In the default implementation used by Symfony if you access a page/A and you are not logged on, Symfony redirects to the login page and after it you are redirected again to page/A. But this not occurs with my extended handler.
To solve it, I can specify a default_target_path when registering the handler as a service, but I want to understand why it doesn't follow the "normal" behavior.
Any ideas out there.
Thanks in advance.
SOLUTION !!!
After looking and test I found a solution here Symfony2 extending DefaultAuthenticationSuccessHandler
The idea is override the default symfony success/failure handlers instead of define my own and apply in the security.yml file.
File security.yml
secured_meems_area:
pattern: ^/somrurl/
form_login:
login_path: /somrurl/login
check_path: /somrurl/api/login_check
#
# DON'T USE !!!
#
# success_handler: authentication.success_handler
# failure_handler: authentication.failure_handler
#
require_previous_session: false
File service.yml. (NOTE the security. in the service name)
services:
security.authentication.success_handler:
class: %mycustom.authentication.success_handler.class%
arguments: ['#security.http_utils', {} ]
public: false
tags:
- { name: 'monolog.logger', channel: 'security' }
security.authentication.failure_handler:
class: %mycustom.authentication.failure_handler.class%
arguments: ['#http_kernel', '#security.http_utils', {}, '#logger' ]
public: false
tags:
- { name: 'monolog.logger', channel: 'security' }
This way we are overriden the default implementation with our own and don't need to specify the handlers in the security area.

Related

Symfony 6.2 does not reach token handler on token authentication

Symfony 6.2 does not reach custom token handler while executing a request if token_extractors equals to header in the security.yml.
Here is the security.yml
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
access_token_provider:
entity:
class: App\Entity\AccessToken
property: secret
firewalls:
main:
lazy: true
provider: access_token_provider
stateless: true
pattern: ^/
access_token:
token_extractors: header
token_handler: App\Security\AccessTokenHandler
access_control:
- { path: ^/, roles: ROLE_ADMIN }
Here is the custom token handler, as you can see it shoud dump the token and die, and id does that if the token provided in query string.
namespace App\Security;
use App\Repository\AccessTokenRepository;
use Doctrine\ORM\NonUniqueResultException;
use SensitiveParameter;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
readonly class AccessTokenHandler implements AccessTokenHandlerInterface
{
public function __construct(private AccessTokenRepository $accessTokenRepository)
{
}
/**
* #throws NonUniqueResultException
*/
public function getUserBadgeFrom(#[SensitiveParameter] string $accessToken): UserBadge
{
var_dump($accessToken);
die;
$accessToken = $this->accessTokenRepository->getOneByToken($accessToken);
if (!$accessToken || !$accessToken->isValid()) {
throw new BadCredentialsException('Invalid credentials.');
}
return new UserBadge($accessToken->getId());
}
}
Authorization header generated by API client looks like this:
Authorization: Bearer 00000000-0000-0000-0000-000000000000
What's wrong in my implementation?
I tried to change token_extractors to query_string and provide token in GET parameters, and it does reach.
Query string looks like this:
localhost:8001/users?access_token=00000000-0000-0000-0000-000000000000
The problem was caused by Apache misconfiguration.
Apache didn't pass Authorization token to PHP due to default configuration and security reasons.
Adding CGIPassAuth On to a directory context solved this.
<Directory /var/www/html>
CGIPassAuth On
</Directory>
https://httpd.apache.org/docs/trunk/mod/core.html#cgipassauth

Token not found when redirect user based on Roles

I have this security.yml file:
...
security:
encoders:
Trainme\RestBundle\Document\User:
id: security.encoder.blowfish
role_hierarchy:
ROLE_TRAINER: ROLE_USER
ROLE_ADMIN: ROLE_TRAINER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
trainme_admin_provider:
id: trainme_admin.user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_admin:
pattern: ^/admin
form_login:
check_path: trainme_security_check
login_path: trainme_login
default_target_path: trainme_dashboard
logout:
path: trainme_logout
target: trainme_redirect_route
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_TRAINER }
And when user's role is ROLE_TRAINER they will be redirected to /profile, if ROLE_ADMIN then /admin. I do this using the following solution How to redirect to different url based on roles in symfony 2.
The problem is:
When a look at debug toolbar in /profile, it says that i'm not authenticated. Why i'm not authenticated? I already login using login form. But when I logged in as ROLE_ADMIN and i'm in /admin, it says that I'm authenticated.
The authentication process is only triggered if the requested url is behind a firewall. Your firewall is only in place for the pattern ^/admin. If you want the firewall to be active across the whole site you should set your pattern to just ^/ and use the access controls section to define the specific roles for different areas of the site (as you have done). Alternatively you can set up a second firewall if you wish but typically one firewall with appropriate access controls is sufficient.

Symfony2: Access anonymously path

I use the FOSRestbundle with mopa/wsse-authentication-bundle (WSSE authentication).
Everything works fine (Authentication and RESTful api).
But i want to make 1 specific path public (without authentication).
Path i want to make public: ^/api/users
I tried following settings but i still get a 403 Forbidden status for the api/users path.
(security.yml)
jms_security_extra:
secure_all_services: false
expressions: true
security:
encoders:
***\UserBundle\Entity\User: plaintext
role_hierarchy:
ROLE_USER: [ROLE_API_USER]
providers:
***_users:
entity: { class: UserBundle:User }
firewalls:
wsse_secured:
pattern: ^/api
anonymous: true
stateless: true
wsse:
nonce_dir: null
lifetime: 300
provider: ***_users
access_control:
- { path: ^/api/users, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/(css|js), roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/(_wdt|_profiler)
If you want to make api/users/ page public that is accessible to everybody without any sort of authentication, you can think of keeping out of WSSE authentication. To do that, you can specify the url pattern for which WSSE authentication will not be applied. e.g. in below example I have specified a pattern to skip URLs having the form as "api/users/".
in short I am suggesting to skip WSSE authentication for the "users" page using the "pattern"
wsse_secured:
pattern: ^/api/[^users].*
wsse:
nonce_dir: null
lifetime: 300
provider: ***_users

Symfony2: login does not work on first try after clearing cookies

When trying to log in, Symfony2 tells me that I provided the wrong credentials. Second try works. Any ideas why this could happen?
To reproduce the behaviour, I have to logout, clear cookies, go to the login page again and log in again.
I am using FOSUserBundle.
config.yml:
framework:
#esi: ~
secret: asdfsadfasdf
#translator: { fallback: en }
charset: UTF-8
router: { resource: "%kernel.root_dir%/config/routing.yml" }
form: true
csrf_protection: true
validation: { enable_annotations: true }
templating: { engines: ['twig'], assets_version: v1.2 } #assets_version: SomeVersionScheme
translator: { fallback: de }
session:
default_locale: de
auto_start: false
lifetime: 1000000
...
security.yml:
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
fos_userbundle:
id: fos_user.user_manager
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login$
security: false
public:
pattern: ^/.*
form_login:
provider: fos_userbundle
check_path: /login_check
remember_me: true
remember_me:
key: aaasfasdfasdfsadfsadf
lifetime: 1296000 #15 days in second
path: /
anonymous: true
logout: true
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY}
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY}
#- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
- { path: ^/events/create, roles: ROLE_USER }
#...
acl:
connection: default
routing.yml:
_imagine:
resource: .
type: imagine
_index:
resource: "#AjadoEventHubBundle/Controller/IndexController.php"
type: annotation
fos_comment_api:
type: rest
resource: "#FOSCommentBundle/Resources/config/routing.yml"
prefix: /api
fos_user_security:
resource: "#FOSUserBundle/Resources/config/routing/security.xml"
...
#FOSUserBundle/Resources/config/routing/security.xml:
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="fos_user_security_login" pattern="/login">
<default key="_controller">FOSUserBundle:Security:login</default>
</route>
<route id="fos_user_security_check" pattern="/login_check">
<default key="_controller">FOSUserBundle:Security:check</default>
</route>
<route id="fos_user_security_logout" pattern="/logout">
<default key="_controller">FOSUserBundle:Security:logout</default>
</route>
</routes>
On my opinion, this is expected behaviour as you enabled anonymous authentication :
You request your app url, without being logged => a session cookie is created with your session ID
An anonymous token is created
You clear cookie => no more session id to identify you
Next request, no token is attached to your login request...
I'm not familiar with symfony, however, I have experienced the same problem when the authentication check looked for a valid cookie, but the cookie was being created after the check--thus causing it to pass the second time, never the first.
By default Symfony require that a session must be exist before the submitting of the form
from the docs
# by default, a session must exist before submitting an authentication request
# if false, then Request::hasPreviousSession is not called during authentication
# new in Symfony 2.3
In order to over come this you could set "require_previous_session" (which is by default true) to false in the "security.yml" as under "form_login" like this:
require_previous_session: false
You could read more about it in Symfony docs in the following link
SecurityBundle Configuration ("security")
I had this problem and I solved it following the answer here Symfony 2 “Your session has timed out or you have disabled cookies”.
#AlterPHP was right, you have to login twice because the first time you are getting an error like this:
Authentication request failed. (...) Your session has timed out, or you have disabled cookies.
As you don't have a session started, with this request a new session is created. Next time you try to login, as the session was created, you can login.
You had to set the option require_previous_session: false in your app/config/security.yml file to avoid looking for a previous session:
security:
firewalls:
main:
form_login:
require_previous_session: false

Symfony2: Unable to find the controller for path "/en/login_check"

I am working on creating a login form in Symfony, following the example provided in the Security chapter of the Symfony book.
I have created the necessary configurations in security.yml, routing.yml and also implemented my ControllerAction and Twig template.
Here is what the security and routing files look like:-
security.yml
Firewalls:
# defaut login area for standard users
main:
switch_user: true
context: user
pattern: .*
form_login:
provider: fos_userbundle
login_path: /login
use_forward: false
check_path: /login_check
failure_path: null
logout: true
anonymous: true
routing.yml
login:
pattern: /{_locale}/login
defaults: { _controller: XYZSiteBundle:SiteUser:login }
login_check:
pattern: /{_locale}/login_check
As you can see, the login_path and check_path are both under the same firewall.
I am still getting the following error:-
Unable to find the controller for path "/en/login_check". Maybe you forgot to add the matching route in your routing configuration?
What am I doing incorrectly?
Your configuration setting is almost OK.Your problem is related to routing. You can solve this issue by just change your routing.yaml to
In routing.yaml
login:
pattern: /login defaults: { _controller:XYZSiteBundle:SiteUser:login }
login_check:
pattern: /login_check
because you \login and \logincheck pattern must match with security.yaml pattern. You need to remove /{_locale}/login and /{_locale}/login_check.
Hope this helps you.
The check_path value of your form_login section will not match /en/login_check.
You have to modify it to match /.*/login_check.
Your login_check route is missing controller default, should be:
login_check:
pattern: /{_locale}/login_check
defaults: { _controller: XYZSiteBundle:SiteUser:loginCheck }