Form-less authentication using standard Symfony2 security - php

I have the following scenario: I have a secured area of my domain under the pattern "/register", for which I have associated a fixed user called "registrant", with the unique role USER_REGISTRANT. The relevant security.yml sections are:
providers:
in_memory:
memory:
users:
registrant: { password: registrant, roles: 'REGISTERING_USER' }
firewalls:
register:
pattern: ^/register/.*
anonymous: false
form_login:
login_path: /register/initiate_registration
check_path: /register/start_registration
My goal is the following: whenever the user tries to enter the "/register" security context, she should be automatically authenticated as the user "registrant", without any form interaction or other user-side authentication steps.
I want to achieve this using the standard form-login mechanisms in Symfony2, i.e. when the user is sent to the login_path, the system should simply generate the necessary token/form data and pass it to check_path, just as would be done if the user had filled in a form and submitted it.
The general outline of the logic should go something like this:
/**
* #Route("/register/initiate_registration", name="initiate_registration")
*/
public function startAction() {
// TODO: Generate form data etc here
return $this->redirect($this->generateUrl('start_registration'));
}
What steps should be taken in the login_path controller in order to get the functionality desired above?

Is this docs can be usefull for you security?

Related

Symfony add csrf token check for other pages

Official documentation (https://symfony.com/doc/current/security.html) of symfony states, to user CSRF protection I should add
# config/packages/security.yaml
security:
# ...
firewalls:
secured_area:
# ...
form_login:
# ...
enable_csrf: true
It works only for login form. This article (https://symfony.com/doc/current/security/csrf.html#csrf-protection-in-symfony-forms) says, that I can user method isCsrfTokenValid in the controller to check the token. I have another page, not, login page, where I want to check csrf token. Can you configure it somehow in the security.yaml or isCsrfTokenValid is the only way?

How to retrieve username after authentication is successful?

I am using Gitlab authentication in my current symfony project. I am using following two packages.
omines/oauth2-gitlab
knpuniversity/oauth2-client-bundle
Inside my security.yaml file
security:
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
app_user_provider:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
guard:
authenticators:
- App\Security\GitlabAuthenticator
access_control:
- { path: ^/connect, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
This is working fine and it is storing cookie PHPSESSID. I need to retrieve the username after successful login.
It is very very simple. Just use $this->getUser().
public function index()
{
/** #var \App\Entity\User $user */
$user = $this->getUser();
return new Response('Well hi there '.$user->getFirstName());
}
UserProvider (that's used in login process) returns a UserInterface, which represent a User. From it you can access the username.
Now, how to retrieve that user?
Depending on Symfony version, and depending where you want to access it, you have several possibilities:
Symfony <3.2
In controller -> you can use $this->getUser()->getUsername();
Symfony >=3.2
In controller -> Inject UserInterface directly as a dependency (read more)
For both cases, if you need the user not in controller, you can inject (based on your symfony version) the TokenStorage or the Security service (can't find which version supports which, for sure Security component is supported in sf5).
If you choose (or are forced) to go with TokenStorage (or its interface), you need to perform the following steps:
getToken
check if the token is not null
retrieve the username from the token (or optionally retrieve the whole user and then retrieve the username from there)

How to authenticate an anonymous visitor (user) using a hash in Symfony 4?

We have a site where people can login using a hash. We don't have distinct users. Everyone with a valid hash can login and it's not important to distinguish who is logged in.
I already created a form to get the hash in the controller. Here's the simplified code:
public function index(Request $request) {
if ($request->isMethod('post')) {
$token = $request->request->get('token');
$hash = $request->request->get('hash');
if ($this->isCsrfTokenValid('login', $token) && $this->isHashValid($hash)) {
// redirect
}
// fail
}
return $this->render('login.html.twig', []);
}
Now in $this->isHashValid() I can already say whether a hash is valid. But I'm not sure, how to authenticate the visitor manually:
isHashValid($hash) {
// pseudo-check for the question
if (in_array($hash, $hashes) {
// How to authenticate the user?
return true;
}
return false;
}
I also updated the security.yaml to redirect unauthenticated visitors to the startpage, which works already:
security:
providers:
in_memory: { memory: ~ }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
form_login:
login_path: '/'
access_control:
- { path: ^/secured, allow_if: "is_authenticated()" }
So my question is, how can I authenticate and later log out a visitor programmatically in Symfoy 4?
Do I still need to create a User-class, even though we don't have "real users" in a classical sense?
You will have to create a User implementing the UserInterface, but it can be a barebones class that does not require you to set any values. Just make sure that getRoles() returns at least ["ROLE_USER"] and everything else should be ok to just return dummy data or null values.
The authentification can be solved multiple ways.
GuardAuthenticator seems like a good solution: https://symfony.com/doc/current/security/guard_authentication.html#step-2-create-the-authenticator-class
FormLoginAuthenticator is pretty similar, but the some methods are automatically dealt with based on using a login form, so it's a bit easier to implement: https://symfony.com/doc/current/security/form_login_setup.html
In both cases you could basically do it like this: getCredentials you extract the data from the request, in getUser you return your dummy user object and in checkCredentials you call your isHashValid method the rest should be self explanatory.

Symfony authentication providers

I'm using fr3d/ldap-bundle. It logs me in and imports users from AD if they're not in db. That's fine.
Despite AD users I also have local users, which are in my db. There is special column authType which says how user should be authenticated - via LDAP or natively ( FOS ). I've created my own user provider:
public function chooseProviderForUsername($username)
{
if($user->getAuthType() == User::LOGIN_LDAP) {
$this->properProvider = $this->ldapUserProvider;
} elseif($user->getAuthType() == User::LOGIN_NATIVE) {
$this->properProvider = $this->fosUserProvider;
} else {
throw new InvalidArgumentException('Error');
}
}
public function loadUserByUsername($username)
{
return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}
PROBLEM: Chain provider isn't an option - it allows user to login with his LDAP password AND with his local password! That's a big security issue.
Is there a way to login user via different authentication providers, depending on the db field?
EDIT:
My security.yml:
providers:
fos_userbundle:
id: fos_user.user_provider.username
appbundle_user_provider:
id: appbundle.user_provider
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: ^/admin.*
context: user
fr3d_ldap: ~
form_login:
provider: appbundle_user_provider
csrf_provider: security.csrf.token_manager
always_use_default_target_path: true
default_target_path: admin_main
login_path: /admin/login
check_path: /admin/login_check
logout:
path: /admin/logout
target: /admin/login
anonymous: true
Here is security.yml. This line fr3d_ldap: ~ enables the ldap bundle, which authorize ldap users and saves them into my db. Without it I cannot authorize them, probably I would have to write custom AuthenticationProvider.
I am not very familiar with ldap but I would suggest try doing a completely manual login
$token = new UsernamePasswordToken($user, null, "firewallname", $user->getRoles());
$securityContext = $this->container->get('security.context');
$securityContext->setToken($token);
Then you can manually do the checks yourself, and depending on the result of the check decide how you want to verify the user before authenticating. For example, run a query by username and password before executing this login code or whatever, depending on the db field you want.
Your approach seems fine but you should check logic of your methods.
First of all this one:
public function chooseProviderForUsername($username)
{
if($user->getAuthType() == User::LOGIN_LDAP) {
$this->properProvider = $this->ldapUserProvider;
} elseif($user->getAuthType() == User::LOGIN_NATIVE) {
$this->properProvider = $this->fosUserProvider;
} else {
throw new InvalidArgumentException('Error');
}
}
You pass $username to this method as an argument, but then use $user object, which seems to be undefined in current context.
Secondly:
public function loadUserByUsername($username)
{
return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}
So as chooseProviderForUsername method actually does not return any value you are not able to chain it this way.
I hope refactoring these issues should make your provider work properly.
Ok, so very brief answer, but I think at the moment Symfony is searching for the user amongst any old User Provider rather than the one you want it to for that particular user (which explains the whole logging in with two passwords thing). A solution should be to make AppBundleUserProvider implement UserProviderInterface, remove the other User Providers from security.yml and then to ensure that the first thing AppBundleUserProvider does it to find out which User Provider is required for that user then mimic it for every method in the UserProviderInterface. You could set $this->realUP based on Username, then set every method to just return $this->realUP->someMethod().
The cleanest way I can think of is to create your own ChainProvider class that only allows login with one provider and use the Dependency Injection Container to use yours.
You just need to override the security.user.provider.chain.clas parameter definition in your bundle's config file.

Symfony 2 after login do some extra job

In this case, after a successful login, I need to update the user's login time into the underlying table.
The User entity currently implements UserInterface and is doing fine. Just want to add some extra code to log the login date time of the user.
Searched the site and seemed to me to use an EventListener etc is a bit heavy. Other lighter alternatives?
You can implement a Success Hander.
Write a class that implement the AuthenticationSuccessHandlerInterface:
Define it as a service (you can inject other services like doctrine
or the security context, in your case the Session)
Add the handler service in the security config like:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: login
check_path: login_check
success_handler: some.service.id
logout:
path: logout
target: /
Check this for a complete example and the reference doc for all the symfony2 security configuration options (you can configure the failure_handler also).
In my working solutions I implements the method onSecurityInteractiveLogin in my listener as:
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
$user->setLastLogin(new \Datetime());
// Entity manager injected by container in the service definition
$this->em->persist($user);
$this->em->flush();
}
Hope this help

Categories