I have 2 firewalls, one main one for the web/front end of the application. One for the api. They share a user entity, but i want the provider to load users based on a different property for each firewall.
For the main firewall users can log in with email only.
For the api firewall users can log in with username only.
Here is my setup so far.
My user provider with username as property:
app_user_provider:
entity:
class: App\Entity\User
property: userName
my firewall:
api:
pattern: ^/api
stateless: true
provider: app_user_provider
custom_authenticators:
- App\Security\AppLoginAuthenticator
- App\Security\AppKeyAuthenticator
main:
lazy: true
provider: app_user_provider
custom_authenticators:
- App\Security\LoginFormAuthenticator
form_login:
login_path: app_login
check_path: app_login
entry_point: form_login
logout:
path: app_logout
Create 2 providers with same entity and different property:
app_user_provider:
entity:
class: App\Entity\User
property: email
api_user_provider:
entity:
class: App\Entity\User
property: userName
Autowire your custom authenticator to use the correct one:
App\Security\AppLoginAuthenticator:
arguments:
$userProvider: "#security.user.provider.concrete.api_user_provider"
Related
I am installing JWTRefreshTokenBundle on a Symfony 6 and PHP 8.1 base
I followed the documentation and I get the following error:
Class "AppEntityRefreshToken" sub class of "Gesdinet\JWTRefreshTokenBundleEntityRefreshToken" is not a valid entity or mapped super class.
I continued to search and tried the following procedure:
https://github.com/markitosgv/JWTRefreshTokenBundle/issues/332
But the result is the same.
What is strange is that in the documentation we have to update our database with the new RefreshToken entity and absolutely nothing happens even when forcing the update
You will find below the different files.
If someone has an idea, I'm interested ! Thanks in advance
-- App\Entity\RefreshToken.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken as BaseRefreshToken;
/**
* #ORM\Entity
* #ORM\Table("refresh_tokens")
*/
class RefreshToken extends BaseRefreshToken
{
}
-- security.yaml
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ^/api/
stateless: true
entry_point: jwt
json_login:
check_path: /api/authentication_token
provider: app_user_provider
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
jwt: ~
refresh_jwt:
check_path: /api/authentication_refresh
provider: app_user_provider
main:
jwt: ~
-- routes.yaml
json_login:
path: /api/authentication_token
refresh_token:
path: /api/authentication_refresh
-- gesdinet_jwt_refresh_token.yaml
gesdinet_jwt_refresh_token:
refresh_token_class: App\Entity\RefreshToken
I've found a way to solve your issue.
You need to delete your App/Entity/RefreshToken file then you use the Symphony CLI and run
symfony console make:entity // or php bin/console ...
Name the entity RefreshToken and don't add any property
Then delete the repository class that has just been made and go inside the file App/Entity/RefreshToken to make it look like that :
<?php
namespace App\Entity;
use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken as BaseRefreshToken;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: "refresh_tokens")]
class RefreshToken extends BaseRefreshToken
{
}
You can now
symfony console make:migration
then
symfony console d:m:m
It should work like a charm when you ping your login route
EDIT:
Your security.yaml firewalls should look like that:
firewalls:
dev:
pattern: ^/_(profiler|wdt)
security: false
main:
pattern: ^/login
stateless: true
provider: app_user_provider
json_login:
provider: app_user_provider
check_path: /login
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
logout:
path: /logout
api:
pattern: ^/api
stateless: true
provider: app_user_provider
jwt:
provider: app_user_provider
authenticator: lexik_jwt_authentication.security.jwt_authenticator
api_token_refresh:
pattern: ^/token/refresh
stateless: true
refresh_jwt: ~
I'm trying to create REST API with JWT authentification in Symfony5. At first I've tried to do as written here https://h-benkachoud.medium.com/symfony-rest-api-without-fosrestbundle-using-jwt-authentication-part-2-be394d0924dd . Difficulties appeared when I've tried to make method getTokenUser:
/**
* #param JWTTokenManagerInterface $JWTManager
* #return JsonResponse
* #Route("/api/login_check", name="api_login_check", methods={"POST"})
*/
public function getTokenUser(UserInterface $user,JWTTokenManagerInterface $JWTManager)
{
return new JsonResponse(['token' => $JWTManager->create($user)]);
}
Symfony says that UserInterface is not service so it can't Autowire it.
Ok, then I've tried to find another articles about this problem. But surprisingly they just doesn't say how to write this method. For example, here https://digitalfortress.tech/php/jwt-authentication-with-symfony/ appears like route /api/login_check must work authomatically if it configured in security.yaml and in routes.yaml. But no, it doesn't work.
So how must I write controller?
My security.yaml is:
security:
encoders:
App\Entity\User:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
login:
pattern: ^/api/login
stateless: true
anonymous: true
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
provider: app_user_provider
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
it's true you do not need this method. You should refer to the documentation of the bundle you are using:
https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/index.rst
In fact the login URL is handled by the security component of Symfony and rely on the configuration you set under json_login key :) .
Once again, it's detailed in the documentation (section "Usage").
I'm using LexikJWTAuthenticationBundle with Api-Platform. I have multiple provider like this :
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
app_candidat_provider:
entity:
class: App\Entity\Candidat
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
candidat:
pattern: ^/candidat/authentication_token
anonymous: true
lazy: true
stateless: true
provider: app_candidat_provider
json_login:
check_path: /candidat/authentication_token
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
main:
anonymous: true
lazy: true
stateless: true
provider: app_user_provider
json_login:
check_path: /authentication_token
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
It's working when I'm getting the token with candidat firewall 'candidat/authentication_token' but when I'm using the token with authorization header, it's loading the user with main firewall instead of candidat.
How does the bundle know which firewall to use only with a token ?
How to deal with multiple providers ?
Thanks by advance for your help !
Symfony uses only one firewall per request and it's the first matched with the pattern. So in your case it's using candidat firewall for ^/candidat/authentication_token urls, and with others request does not matches candidat pattern, they will use "main".
When i add a new firewall for use api Authentication , i get this fail:
Invalid firewall "api": user provider "app_user_provider" not found.
how i can fix this fail?
security:
encoders:
App\Entity\User:
algorithm: bcrypt
cost: 4
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
in_memory: { memory: ~ }
proveedor:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
form_login:
login_path: login
check_path: login
provider: proveedor
default_target_path: tasks
logout:
path: /logout
target: /
api:
pattern: ^/api
anonymous: lazy
provider: app_user_provider
guard:
authenticators:
- App\Security\TokenAuthenticator
In your firewall named 'api', you specify 'app_user_provider' but in it is not present in the providers list.
In the providers list, you have: in_memory and proveedor.
Try to replace app_user_provider by proveedor.
If the provider allow you to get your user, it should work.
I would like to create my own LdapUserProvider to apply different role regarding the username.
Accutally my LDAP works really good with the default LdapUserProvider from symfony.
As the LDAP doc say :
The ldap user provider, using the LdapUserProvider class. Like all other user providers, it can be used with any authentication provider.
How can I do to use a customized User provider please?
security:
providers:
my_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: o=xxx
search_dn: cn=xxx Downloader,ou=ApplicationUsers,o=xxx
search_password: 'xxx'
default_roles: [ROLE_USER]
uid_key: uid
filter: "{uid_key}={username}"
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
# provider: App\Services\MyLdapUserProvider
logout:
csrf_token_id: logout
path: /logout
target: /login
form_login_ldap:
csrf_parameter: _csrf_token
login_path: login
check_path: login
service: Symfony\Component\Ldap\Ldap
dn_string: 'o=xxx'
query_string: 'uid={username}'
target_path_parameter: home
default_target_path: /home
access_control:
- { path: /index, role: IS_AUTHENTICATED_FULLY }
- { path: ^/admin, roles: ROLE_ADMIN }
Edit:
Thank you for your answer #Vyctorya
I think this is what I need!
In security.yaml this is how I call the service:
security:
providers:
myLdap:
id: App\Services\MyLdapUserProvider
and this is my service.yaml:
services:
App\Services\MyLdapUserProvider:
arguments:
$ldap: '#Symfony\Component\Ldap\LdapInterface'
$baseDn: 'o=xxx'
$searchDn: 'cn=xxx Downloader,ou=ApplicationUsers,o=xxx'
$searchPassword: 'xxx'
$defaultRoles:
'ROLE_USER'
$uidKey: 'uid'
$filter: '{uid_key}={username}'
Symfony\Component\Ldap\LdapInterface:
arguments: ['#Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
arguments:
- host: ....
I don't uderstand why baseDn is not defined...
Cannot autowire service "App\Services\MyLdapUserProvider": argument "$baseDn" of method "Symfony\Component\Security\Core\User\LdapUserProvider::__construct()" is type-hinted "string", you should configure its value explicitly.
Look at this Symfony Documentation.
You need to adjust your configuration:
security:
providers:
enter_you_custom_name_here:
id: AppBundle\Security\User\UserProvider
and create the UserProvider. Extend Symfony\Component\Security\Core\User\LdapUserProvider and override loadUserByUsername($username)