1

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.

Alex
  • 1,230
  • 2
  • 24
  • 46
  • 1
    Can you share the ldapsearch commandline? – mvreijn Nov 16 '21 at 16:30
  • @mvreijn I added the command at the end of the question. – Alex Nov 16 '21 at 16:42
  • 1
    You set `uid_key: sAMAccountName` but then `dn_string: 'uid={username},%env(resolve:BASE_DN)%'`. sAMAccountName exists only in AD, what is the username identifier in the user entries ? Also in your command line example, the dn string used for authentication (option -D) does not match the dn_string `uid=foo,...` but `cn=foo,...`. It's probably the main issue (it could also be that the hashing mechanism is wrong or not necessary). sAMAccountName can be used in AD without being part of the dn, if on AD the dn_string is probably `cn=...` according to your ldapsearch cmd. – EricLavault Nov 16 '21 at 16:54

2 Answers2

2

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}'
EricLavault
  • 12,130
  • 3
  • 23
  • 45
  • this config returns a `not valid credentials` error message – Alex Nov 18 '21 at 14:33
  • @Alex Ok, just to be sure, you are trying to login with username "test.username", and you have already checked that this user can bind with the credentials you are passing in the POST request ? eg. using `ldapsearch -x -b "dc=my,cn=example,cn=com" -H ldap://my.example.com -D "CN=Test Username,OU=foo,OU=bar,OU=baz,OU=qux,DC=my,DC=example,DC=com" -s base -W` to return its own entry ? Please provide logs for search and bind operations, so that we can see how the dn is built. – EricLavault Nov 18 '21 at 15:02
  • good catch on the `dn_string` (why they don't use base_dn is a mystery). – mvreijn Nov 18 '21 at 15:02
  • @EricLavault the ldapsearch with `"CN=Test.Username,OU=foo,OU=bar,OU=baz,OU=qux,DC=my,DC=example,DC=com"` returned an "objectClass top" ldap object – Alex Nov 18 '21 at 15:30
  • But in the last lines of the response I see this: `# search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1`... is the zero success meaning that something went wrong? I didn't see any error message – Alex Nov 18 '21 at 15:36
  • I forgot the basedn, it should be the same as the bind dn : `ldapsearch -x -b "CN=Test Username,OU=foo,OU=bar,OU=baz,OU=qux,DC=my,DC=example,DC=com" -H ldap://my.example.com -D "CN=Test Username,OU=foo,OU=bar,OU=baz,OU=qux,DC=my,DC=example,DC=com" -s base -W`. WIth scope 'base', this should return the user entry. But I think it's ok because otherwise you would already had an invalid credentials error. – EricLavault Nov 18 '21 at 15:37
  • Now the ldapsearch returns me a ldap user object with the Test.Username data. Though I still see result: 0 Success at the end. And it still doesn't work in symfony... – Alex Nov 18 '21 at 15:43
  • For `ldapsearch`, Success is what it is: it worked. The issue is with the configuration of Symphony, it performs the wrong operation. Only we cannot exactly see what it is doing, which makes troubleshooting somewhat difficult. – mvreijn Nov 18 '21 at 15:47
  • I edited the security.yaml in the question above to fit with this answer. That is my actual configuration – Alex Nov 18 '21 at 15:50
  • @Alex Ok, (the 0 Success is ok, this was just to ensure this user can bind and read its own entry). Sorry I've no idea what's wrong now, either we all missed the point on the config, or maybe it comes from the post request.. are you passing `"login": "test.username"` along with its password, as for the ldapsearch (your post mention "my.user") ? I think you need to dig in the log to see how that post request is converted into an ldap bind. – EricLavault Nov 18 '21 at 16:58
  • well.... the difference between test.username and my.user it's because I'm changing the real data to fake data and somewhere I have changed my real user by my.user instead test.username. But everywhere I'm testing with the same user. Now I think that the problem is in the dn_string and the query_string, all the other config data seems to be right. I edited my question with all the latest changes and info. Thanks for your time and help. – Alex Nov 19 '21 at 07:59
  • Is it possible that dn_string and query string can have some problems when there is a space character inside a CN or an OU?? Actually there are some like this: `CN=London ABC ENGLAND,OU=London (Fake Street),OU=England,OU=ABCD,OU=ABC - Groups,DC=MY,DC=example,DC=com` Could it be that symfony can't handle space characters inside a DC or OU? – Alex Nov 19 '21 at 08:36
  • I don't think so, for the dn_string at least. Since special characters in a dn must be escaped with a `\`, a valid dn should already have such character escaped (eg. commas in rdn `cn=a,b,c` are escaped in dn `cn=a\,b\,c,dc=test,dc=com`). This means you must provide valid/escaped dn's. It's not the issue because otherwise the dn's used in your config (search_dn, base_dn, dn_string) would cause invalid syntax errors, and would also fail using ldapsearch. – EricLavault Nov 19 '21 at 13:44
  • That said, query_string must be escaped as well and I don't know if symfony does it when resolving {username}. Since it is a filter, there are some characters to escape (see this [post](https://stackoverflow.com/a/39805523/2529954)). In short, you should not worry about that as long as the username you are testing does not contain any of the following chars : `* ( ) \ NUL`. – EricLavault Nov 19 '21 at 13:49
  • Oh, by the way, did you try to remove password_hashers ? I'm thinking about this because the authentication process actually does ldap bind operations, there is no password comparison here so maybe... – EricLavault Nov 19 '21 at 13:53
1

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}'
mvreijn
  • 2,807
  • 28
  • 40
  • I tried both, but none of them worked. Thoug I see your point and I think you are right. The problem is the uid_key and the dn_string. I have edited the main question with the active directory response of the ldapsearch, maybe it's easier to help me with that new data. Thank you for your help – Alex Nov 18 '21 at 12:39
  • I have edited my answer, did you try the last suggestion? – mvreijn Nov 18 '21 at 15:50
  • I added the working data of the ldapsearch directly on the security.yaml file so I wrote the same on base_dn, search_dn and dn_string, and I wrote the Test.USername password directly on the search_password. Now the message is `missing credentials` :-/ – Alex Nov 18 '21 at 16:10
  • This is wrong, search_dn and search_password should be the ldap manager credentials (one who can reads all user entries). When authenticating a given user, first the manager binds with his credentials and search for that user using dn_string and query_string, then if the entry is found, the entry's dn is used along with the provided password to make another bind attempt, which is actually what performs the authentication. The ldapsearch I asked you to do was to check that the credentials you are passing in the post request are correct for that user. – EricLavault Nov 18 '21 at 16:56