Symfony 5.3 LDAP new authentication is always not valid credentials - authentication

I'm trying a json_login_ldap with the new authenticator manager from Symfony 5.3, it seems it cannot resolve the user password and I get always a 'not valid credentials' error message and can't login.
If I try the comand line ldapsearch with my data it is working correctly. And debuggin in Symfony I can see the ldap user data. So it is failing on the authentication.
my security.yaml is
security:
enable_authenticator_manager: true
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
my_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: '%env(resolve:BASE_DN)%'
search_dn: '%env(resolve:SEARCH_DN)%'
search_password: '%env(resolve:LDAP_PASSWORD)%'
default_roles: ROLE_USER
uid_key: sAMAccountName
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
provider: my_ldap
stateless: true
json_login_ldap:
service: Symfony\Component\Ldap\Ldap
check_path: api_login
username_path: security.credentials.login
password_path: security.credentials.password
search_dn: '%env(resolve:SEARCH_DN)%'
search_password: '%env(resolve:LDAP_PASSWORD)%'
dn_string: '%env(resolve:BASE_DN)%'
query_string: 'sAMAccountName={username}'
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
the .env that declares the variables used in the security.yaml:
BASE_DN=OU=foo,OU=bar,OU=baz,OU=qux,DC=my,DC=example,DC=com
SEARCH_DN=cn=searchuser,ou=foo,ou=bar,ou=baz,ou=qux,dc=my,dc=example,dc=com
LDAP_PASSWORD=ldap_reader_password
and in the services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
Symfony\Component\Ldap\Ldap:
arguments: ['#Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
tags:
- ldap
Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
arguments:
- host: my-host # changed
port: 389
options:
protocol_version: 3
referrals: false
I'm sending the data with insomnia to test faster. I'm requesting a POST to http://localhost:8101/api/login with the data:
{
"security": {
"credentials": {
"login": "test.username",
"password": "my_password"
}
}
}
I followed the code to symfony/ldap/Security/LdapUserProvider.php and the loadUserByIdentifier at the end of the function $this->loadUser($identifier, $entry) actually returns a LdapUser object with the ldap user data. But the password is null instead the provided password. So maybe the password is lost somewhere before reaching this point.
In symfony\ldap\Security\LdapAuthenticator.php, the authenticate function returns the Passport object, this object has the PasswordCredentials with the plaintext correct user provided password and resolved: false.
I haven't found json_login_ldap examples in the documentation, but the project is almost empty, and I'm only trying to configure the ldap login, the configuration seems pretty basic. Why is not resolving the user password? Why I can't login? What I'm doing wrong?
Thank you for your help
Edit: The ldapsearch command line is
ldapsearch -x -b "dc=my,cn=example,cn=com" -H ldap://my.example.com -D "cn=searchuser,ou=foo,ou=bar,ou=baz,ou=qux,dc=my,dc=example,dc=com" -W
then asks for the password and answers with the list of ldap users.
With the ldapsearch command:
ldapsearch -x -b "dc=my,cn=example,cn=com" -H ldap://my.example.com -D "cn=searchuser,ou=foo,ou=bar,ou=baz,ou=qux,dc=my,dc=example,dc=com" -W "sAMAccountName=test.username"
and I get the following data:
# Test.Username, foo, bar, baz, qux, my.example.com
dn: CN=Test.Username,OU=foo,OU=bar,OU=baz,OU=qux,DC=my,DC=example,DC=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: test.username
sn: Username
c: EN
l: Randomcity
title: Assistant - departament
description: 8183-01234 | | EN | PT | Regular/Permanent
postalCode: 04035
physicalDeliveryOfficeName: Fake Street, Randomcity, 04035, EN
telephoneNumber: +34 (555) 123456
givenName: Test
distinguishedName: CN=Test Username,OU=foo,OU=bar,OU=baz,OU=qux,DC=my,DC=example,DC=com
instanceType: 4
whenCreated: 20230213120051.0Z
whenChanged: 20411115070912.0Z
displayName: Test Username
uSNCreated: 151386
memberOf: CN=pim,OU=pam,OU=pum - Groups,DC=my,DC=example,DC=com
uSNChanged: 330517879
co: bar
department: bar | departament
company: example bar U.C.
proxyAddresses: x500:/o=example/ou=Exchange Group (FYDF231234PDLT)/cn=Recipients/cn=Test.Username
streetAddress: Fake Street
employeeType: PT
name: Test Username
objectGUID:: mxAQq12Jjky/4ycKabEPS6==
userAccountControl: 512
badPwdCount: 0
codePage: 0
countryCode: 123
employeeID: EN00123
badPasswordTime: 123810906564118891
lastLogoff: 0
lastLogon: 123808333912908124
pwdLastSet: 123708200340431193
primaryGroupID: 123
objectSid:: AQUAAAAABCDEAAAxf4pEoe3zO6g0v/wO4kCAA==
accountExpires: 9123472012354775807
logonCount: 309
sAMAccountName: test.username
sAMAccountType: 123306368
sIDHistory:: AQUABCDEAAUVAAAAd4oemO2qe8MmF4j+ZRkBAA==
showInAddressBook: CN=Default Global Address List,CN=All Global Address Lists,CN=Address Lists Container,CN=pim,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=my,DC=example,DC=com
showInAddressBook: CN=All Users,CN=All Address Lists,CN=Address Lists Container,CN=pim,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=my,DC=example,DC=com
legacyExchangeDN: /o=pim/ou=External (FYDIBOHF25SPDLT)/cn=Recipients/cn=3f3dc637018b7653b68936ba11ed003d
userPrincipalName: enTUl01#my.example.com
lockoutTime: 0
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=my,DC=example,DC=com
dSCorePropagationData: 20212345670447.0Z
lastLogonTimestamp: 132814364123188798
msDS-SupportedEncryptionTypes: 0
textEncodedORAddress: X400:C=GB;A= ;P=example;O=London;S=Username;G=Test;
mail: Test.Username#example.com
manager: CN=Another Username,OU=foo,OU=bar,OU=baz,OU=qux,DC=my,DC=example,DC=com
msExchOmaAdminWirelessEnable: 4
msExchVersion: 44220981232016
msExchRemoteRecipientType: 4
mailNickname: TestUsername
msExchWhenMailboxCreated: 20130219124128.0Z
msExchMDBRulesQuota: 64
targetAddress: SMTP:TestUsername#fooAccess.mail.onmicrosoft.com
msExchSafeSendersHash:: fz/qCi123Qt86iU8888lolB1c6ddddUdL4Q77W9876rHu34
extensionAttribute1: EN
extensionAttribute11: 2015-May-08 12:05 by Workday
msExchArchiveQuota: 52123800
protocolSettings:: UE9QM8Klol123fCp8KnwqfCp8KytdfCp8Knwqc=
protocolSettings: 1
extensionAttribute12: Employee-PT
msExchMailboxGuid:: IPEaXbO1+0KPwAtZctyhBg==
msExchArchiveWarnQuota: 47112320
msExchPoliciesExcluded: {26491cfc-9e50-4857-861b-0cb8df22b5d7}
extensionAttribute4: bar | Finance
msExchRecipientDisplayType: -2112383642
extensionAttribute5: 255.255.255.255
msExchUMDtmfMap: reversedPhone:1234567890+
msExchUMDtmfMap: emailAddress:1234567890
msExchUMDtmfMap: lastNameFirstName:1234567890
msExchUMDtmfMap: firstNameLastName:1234567890
msExchTextMessagingState: 354021235
msExchTextMessagingState: 16123851
extensionAttribute6: EN001
msExchALObjectVersion: 119
extensionAttribute7: Active
msExchUserAccountControl: 0
msExchUMEnabledFlags2: -1
msExchRecipientTypeDetails: 2147123648
msExchELCMailboxFlags: 2
msExchDisabledArchiveGUID:: R/hxhWWlol123j1bfjZLNg==
When I asked for the ldap data they told me that the uid_key is sAMAccountName. And I think it could be the problem.
I tried to change the security.yaml ldap provider uid_key to mail (uid_key: mail), and the json_login_ldap dn_string to username (dn_string: {username}), and then tryed to login with the email test.username#example.com, but i see the same error.

First, ensure the ldap provider is properly configured. According to your ldapsearch example, it should be (with environment variables resolved) :
providers:
my_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: 'dc=my,cn=example,cn=com'
search_dn: 'cn=searchuser,ou=foo,ou=bar,ou=baz,ou=qux,dc=my,dc=example,dc=com'
search_password: '<searchuser_password>'
default_roles: ROLE_USER
uid_key: sAMAccountName
Then, the important thing to consider for the json login config is that dn_string is used as a user base dn when query_string is set (which is not obvious at all).
query_string:
When this option is used, query_string will search in the DN specified
by dn_string and the DN resulted of the query_string will be used to
authenticate the user with their password.
Also, the example in the documentation shows that search_dn and search_password are both set (again) in the login config.
Following these 2 points, this should work :
json_login_ldap:
service: Symfony\Component\Ldap\Ldap
check_path: api_login
username_path: security.credentials.login
password_path: security.credentials.password
search_dn: 'cn=searchuser,ou=foo,ou=bar,ou=baz,ou=qux,dc=my,dc=example,dc=com'
search_password: '<searchuser_password>'
dn_string: 'dc=my,cn=example,cn=com'
query_string: 'sAMAccountName={username}'

I think the issue is with this piece of configuration:
main:
provider: my_ldap
stateless: true
json_login_ldap:
service: Symfony\Component\Ldap\Ldap
check_path: api_login
username_path: security.credentials.login
password_path: security.credentials.password
dn_string: 'uid={username},%env(resolve:BASE_DN)%' <<<< ASSUMPTION
The assumption above is that all of your users are located in one flat list in a container. Looking at your ldapsearch example, you have an LDAP tree that is more hierarchical. It means that your BASE_DN value is not the direct parent of your users, but there are sublevels.
Looking at
uid_key: sAMAccountName
I assume that you are using Active Directory. In that case, try this configuration:
providers:
my_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: '%env(resolve:BASE_DN)%'
search_dn: '%env(resolve:SEARCH_DN)%'
search_password: '%env(resolve:LDAP_PASSWORD)%'
default_roles: ROLE_USER
uid_key: userPrincipalName
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
provider: my_ldap
stateless: true
json_login_ldap:
service: Symfony\Component\Ldap\Ldap
check_path: api_login
username_path: security.credentials.login
password_path: security.credentials.password
dn_string: '{username}'
(rest will stay the same).
Now you should be able to log in with the UPN of the users, of the format username#domain.
EDIT
You can also configure the LDAP module to search for the users by username, I had forgotten that part. That is the best solution IMHO.
Change the configuration to this:
main:
provider: my_ldap
stateless: true
json_login_ldap:
service: Symfony\Component\Ldap\Ldap
check_path: api_login
username_path: security.credentials.login
password_path: security.credentials.password
query_string: 'sAMAccountName={username}'
and try to log in with the username you tried in the first place.
See https://symfony.com/doc/current/security/ldap.html#query-string for the reference documentation.
EDIT
What if you use this as the main configuration, and try to log in with the same credentials as you showed in your JSON example?
main:
provider: my_ldap
stateless: true
json_login_ldap:
service: Symfony\Component\Ldap\Ldap
check_path: api_login
username_path: security.credentials.login
password_path: security.credentials.password
dn_string: '%env(resolve:BASE_DN)%'
query_string: 'sAMAccountName={username}'

Related

Using LexikJWTAuthenticationBundle with an LDAP Provider

I have a project that use Symfony API-Platform. I want to use LexikJWTAuthenticationBundle on my project but my users are stored in an Active Directory so I set an LDAP UserProvider. I tried to combine the setting of the two following documentation without success :
https://api-platform.com/docs/core/jwt/
https://symfony.com/doc/current/components/ldap.html
Here is what I have done
In security.yml :
security:
providers:
my_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: OU=organization_unit,DC=domain,DC=local
search_dn: "OU=organization_unit,DC=domain,DC=local"
search_password: '%env(resolve:LDAP_PASSWORD)%'
default_roles: ROLE_USER
uid_key: uid
firewalls:
login:
pattern: ^/api/login
stateless: true
anonymous: true
json_login:
provider: my_ldap
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
guard:
provider: my_ldap
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
In services.yml :
Symfony\Component\Ldap\Ldap:
arguments: ['#Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
arguments:
- host: '%env(resolve:LDAP_HOST)%'
port: 636
# encryption: tls
options:
protocol_version: 3
referrals: false
But when I try to send the request to my_domain/api/login_check with following content :
{
"username": "my_user",
"password": "my_password"
}
I received this error as response:
{"code":401,"message":"Invalid credentials."}
I have resolved my issue by using a custom action that checks if the user exist using LDAP and get its roles. Then, if the freshly retrieved user is authorized to use the application, I create a new token and return it in the response. And finally the token is used to authentify the user for each call he do to the API.
Here is my security.yml
providers:
# provider to retrieve user from LDAP
my_ldap:
id: App\Security\LdapService
# provider to retrieve user from user
jwt:
lexik_jwt:
class: App\Security\User
firewalls:
login:
pattern: ^/api/login
stateless: true
anonymous: true
api:
pattern: ^/api/
stateless: true
guard:
provider: jwt
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
The login action in controller :
/**
* #Route("/api/login", name="api_login", methods={"POST"})
*/
public function loginCheck(Request $request, LdapService $ldapService, AuthenticationSuccessHandler $authenticationSuccessHandler, AuthenticationFailureHandler $authenticationFailureHandler){
$content = json_decode($request->getContent());
$response= new JsonResponse();
try{
$user=$ldapService->authenticate($content->username, $content->password);
return $authenticationSuccessHandler->handleAuthenticationSuccess($user);
}catch(AuthenticationException $e){
return $authenticationFailureHandler->onAuthenticationFailure($request, $e);
}
return $response;
}

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.

Sitewide http auth interfering with Symfony2 app authentication

I have a site under development with the following structure:
public_html/
index.php
symfony_app/
other_app/
Currently I have the root of the site behind Basic HTTP authentication during the development testing phase. I couldn't figure out why my Symfony2 authentication for a valid user (myusername) was always redirecting to the Symfony login page. In the logs after successful Symfony login
security.INFO: User "myusername" has been authenticated successfully [] []
I found:
security.INFO: Basic Authentication Authorization header found for user "otherusername" [] []
..which is the user required by .htpasswd in the root of the site. So it seems that I have an issue with, for lack of a better term, nested http authentication.
Is it possible to have a Symfony app living behind http auth without the two clashing?
Security.yml
jms_security_extra:
secure_all_services: false
expressions: true
security:
encoders:
My\UserBundle\Entity\User:
algorithm: sha1
encode_as_base64: false
iterations: 1
role_hierarchy:
ROLE_ADMIN: ROLE_USER
providers:
administrators:
entity: { class: MyUserBundle:User }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login$
security: false
secured_area:
pattern: ^/
http_basic: ~
form_login:
login_path: login
check_path: login_check
always_use_default_target_path: true
logout:
path: /logout
switch_user: true
access_control:
- { path: ^/, roles: ROLE_USER, requires_channel: https }

Authenticate multiple symfony2 firewalls

I'm trying to authenticate users from in_memory provider for one section on the site and sso provider for all the other pages, so I tried the following:
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
sso:
id: sso.security.user.provider
fos_twitter:
id: fos_twitter.auth
in_memory:
users:
ryan: { password: ryanpass, roles: 'ROLE_CANVAS_ADMIN' }
admin: { password: kitten, roles: 'ROLE_CANVAS_ADMIN' }
firewalls:
main:
pattern: ^/canvas
provider: in_memory
secured_area:
pattern: ^/
form_login:
login_path: /signin
check_path: /login_check
anonymous: true
sso: true
fos_twitter: false
access_control:
- { path: ^/canvas, roles: [ROLE_CANVAS_ADMIN] }
This is not working, because it always enter to the /signin login path and this working using the sso provider.
I already tried the following implementations:
Authenticate multiple symfony2 firewalls with one login form,
http://symfony.com/doc/2.0/cookbook/security/custom_authentication_provider.html
and this: http://symfony.com/doc/current/book/security.html#using-multiple-user-providers
None of them work for me.
Can someone help with that?
Thanks..

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