Reputation: 96
I would like to implement a "share option" in a Symfony 6 project. Users can share certain resources with other people. The other people do not need to have an account in the application. But the sharing should still be protected with a password.
Unfortunately I haven't found anything about how to implement it using Symfony's methods.
I already tried to write a custom authenticator and check the password there. As far as I could find out, Symfony always needs a user object. This is exactly what I don't want. The people with the link should be authenticated only by a password and so call a subpage.
On the subpage there are different files available for download and the option to upload files. The "authentication" would have to serve for several requests, because the user should not always enter the password.
Each subpage can have a different password, which can be assigned by the user of the application. The password is stored in the database in the table of the resource. I am using the new Symfony Security Bundle with Passports. There I have tried the following:
public function authenticate(Request $request): Passport
{
$password = $request->request->get("_password");
$redirectPath = $request->getSession()->get("_security.upload.target_path");
$pathItems = explode("/", $redirectPath);
$hash = end($pathItems);
$party = $this->em->getRepository(Party::class)->findOneBy(["slug" => $hash]);
if (!$party) {
throw new CustomUserMessageAuthenticationException('no party');
}
if (!$party->getPassword()) {
throw new CustomUserMessageAuthenticationException("password wrong.");
}
return new Passport(
new UserBadge("guest_user"), new CustomCredentials(
function ($credentials, UserInterface $user) use ($party) {
return $credentials === $party->getPassword();
}, $password
)
);
}
In my opinion, however, this approach is very unclean.
Each resource has a UUID in the URL, which I can use to determine the resource. I first try to extract this from the URL and search for it in the database. The URL looks like this:
/upload/party/6c74d175-46ff-4004-a3b2-f9aa95badadc
My security configuration looks like this. I think I have some error here. Through dd() I tried to find out if the authenticator is called; unfortunately it is not. Each time the main firewall is called.
security:
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
guest_user:
entity:
class: App\Entity\GuestUser
role_hierarchy:
ROLE_USER: ~
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
upload:
pattern: ^/upload$
provider: guest_user
custom_authenticators:
- App\Security\PasswordAuthenticator
form_login:
login_path: check_password
check_path: check_password
main:
lazy: true
provider: app_user_provider
form_login:
login_path: login
check_path: login
logout:
path: app_logout
Has anyone had a challenge like this before?
Thanks a lot in advance!
Upvotes: 1
Views: 493
Reputation: 1646
You can create a NullUser
class and set an object of this class in the custom authenticator
<?php
declare(strict_types=1);
namespace App\Entity\User;
use Symfony\Component\Security\Core\User\UserInterface;
class NullUser implements UserInterface
{
public function getUserIdentifier(): string
{
return '';
}
public function getRoles(): array
{
return [];
}
public function eraseCredentials(): void
{
}
}
Or use a custom voter instead of authenticators
Upvotes: 1