Tag: ldap

LDAP Authentication: Python Flask

This is a quick python script showing how the flask-ldap3-login module can be used to authenticate and gather user attribute values

from flask_ldap3_login import LDAP3LoginManager
from ldap3 import Tls
import ssl

config = dict()

config['LDAP_HOST'] = 'ad.example.com'

# Use SSL unless you are debugging a problem. Clear text port is 389 and tls_ctx needs to be removed from add_server call
config['LDAP_USE_SSL'] = True
config['LDAP_PORT'] = 636

# Base DN
config['LDAP_BASE_DN'] = 'dc=example,dc=com'

# User Base DN, prepended to Base DN
config['LDAP_USER_DN'] = 'ou=UserDN'

# Groups Base DN, prepended to Base DN
config['LDAP_GROUP_DN'] = 'ou=SecurityGroupDN'

# Server will be manually added to establish SSL
config['LDAP_ADD_SERVER'] = False

# Domain component of userprincipal name
config['LDAP_BIND_DIRECT_SUFFIX'] = '@example.com'

# Search scope needs to be subtree

# Attributes to return
config['LDAP_GET_USER_ATTRIBUTES'] = ("mail", "givenName", "sn")

# Setup a LDAP3 Login Manager.
ldap_manager = LDAP3LoginManager()

# Init the mamager with the config since we aren't using an app

# TLS settings to establish trust without validating CA issuance chain. 
# Can use CERT_REQUIRED and ca_certs_file with path to cacerts that includes issuing chain
tls_ctx = Tls(


# Validate credentials
response = ldap_manager.authenticate_direct_credentials('e0012345', 'P@s5w0rdG03sH3re')

LDAP Authentication and Authorization: PHP


    #=== FUNCTION ==================================================================
    #      NAME: ldapAuthenticationAndAuthorizationWithAttributes
    #      PARAMETERS:
    #                    $strLDAPHost                   String  LDAP Server URI
    #                    $strUIDAttr                    String  Schema attribute for user ID search
    #                    $strSystemUser                 String  System credential username
    #                    $strSystemPassword             String  System credential password
    #                    $strUserBaseDN                 String  User search LDAP base DN
    #                    $strLogonUserID                String  Input user ID
    #                    $strLogonUserPassword          String  Input user password
    #					 $arrayAttrsToReturn			String	Attributes to be returned
    #                    $strGroupBaseDN                String  (optional) Group search LDAP base DN
    #                    $strGroupNamingAttribute       String  (optional) Schema attribute for group search
    #                    $strMembershipAttr             String  (optional) Schema attribute for group membership
    #                    $strAuthGroup                  String  (optional) Group name
    #     DESCRIPTION: Verify authentication and authorization against AD server.a
    #     RETURNS: array(BindReturnCode, Authorized, array(returnValues))
    #                        BindReturnCode:    -1 indicates LDAP connection failure, -2 indicates system account auth failed, -3 indicates user auth not attempted, >=0 is IANA-registered resultCode values (https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xml#ldap-parameters-6)
    #							NOTE: 0 is successful authentication in IANA-registered resultCode
    #                        Authorized:        0 authorization not attempted, -1 is not a member of the located group, 1 is member of the located group
    #						arrayUserAttributeValues	Array with values of $arrayAttrsToReturn
    #     USAGE: $arrayUserAuthorized = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", $strInputUserName, $strInputUserPassword, array('givenName', 'sn'), "ou=securitygroups,dc=example,dc=com","cn", "member", "LJRTestGroup")
    function ldapAuthenticationAndAuthorizationWithAttributes($strLDAPHost,$strUIDAttr, $strSystemUser, $strSystemPassword, $strUserBaseDN, $strLogonUserID, $strLogonUserPassword, $arrayAttrsToReturn, $strGroupBaseDN=null, $strGroupNamingAttribute=null, $strMembershipAttr=null, $strAuthGroup=null){
        $arrayAuthResults = array();
        $arrayUserAttributeValues = array();
        // Validate password is not null, otherwise directory servers implementing unauthenticated bind (https://tools.ietf.org/html/rfc4513#section-5.1.2) will return 0 on auth attempts with null password
        if( strlen($strLogonUserPassword) < 1){
            $arrayAuthResults['BindReturnCode'] = -3;
            $arrayAuthResults['Authorized'] = -1;
            // Connect to the LDAP directory for system ID queries
            $systemDS = ldap_connect($strLDAPHost);
            ldap_set_option($systemDS, LDAP_OPT_PROTOCOL_VERSION, 3);

            if ($systemDS) {
                // Bind with the system ID and find $strLogonUserID FQDN
                $systemBind = ldap_bind($systemDS, $strSystemUser, $strSystemPassword);

                if(ldap_errno($systemDS) == 0){
                    $result=ldap_search($systemDS,$strUserBaseDN,$strLDAPFilter, $arrayAttrsToReturn);

                    $entry = ldap_first_entry($systemDS, $result);

                    $strFoundUserFQDN= ldap_get_dn($systemDS, $entry);

                        $userDS = ldap_connect($strLDAPHost);
                        ldap_set_option($userDS, LDAP_OPT_PROTOCOL_VERSION, 3);

                        $userBind = ldap_bind($userDS, $strFoundUserFQDN, $strLogonUserPassword);
                        $arrayAuthResults['BindReturnCode'] = ldap_errno($userDS);


                        if($arrayAuthResults['BindReturnCode'] == 0){
                        	$objFoundUser = ldap_get_entries($systemDS, $result);
							for($arrayAttrsToReturn as $strAttributeName){
								$arrayUserAttributeValues[$strAttributeName] = $objFoundUser[0][$strAttributeName];

							$arrayAuthResults['AttributeValues'] = $arrayUserAttributeValues;
                            // If an auth group has been supplied, verify authorization
								// Escapes in DN need to be double-escaped or bad search filter error is encountered
                                $strGroupQuery = "(&amp;($strGroupNamingAttribute=$strAuthGroup)($strMembershipAttr=" . str_replace("\\","\\\\", $strFoundUserFQDN) . "))";

                                $groupResult = ldap_search($systemDS,$strGroupBaseDN, $strGroupQuery);
                                $authorisedState = ldap_count_entries($systemDS ,$groupResult);

                                // If a group matching the filter is found, the user is authorised
                                if($authorisedState == 1){
                                    $arrayAuthResults['Authorized'] = 1;
                                // Otherwise the user is not a member of the group and is not authorised
                                    $arrayAuthResults['Authorized'] = -1;
                                $arrayAuthResults['Authorized'] = 0;
                        // If the bind failed, the user has not logged in successfully so they cannot be authorized
                            $arrayAuthResults['Authorized'] = -1;

                    // User not found in directory
                        $arrayAuthResults['BindReturnCode'] = 32;
                        $arrayAuthResults['Authorized'] = -1;
                // system bind failed
                    $arrayAuthResults['BindReturnCode'] = -2;
                    $arrayAuthResults['Authorized'] = -1;
            // ldap connection failed
                $arrayAuthResults['BindReturnCode'] = -1;
                $arrayAuthResults['Authorized'] = -1;
        return $arrayAuthResults;

    print "User password not supplied:\n";
    $arrayNullPassword = array();
    $arrayNullPassword = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", '');

    print "Bad password:\n";
    $arrayBadPassword = array();
    $arrayBadPassword = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'N0tTh3P@s5w0rd',"ou=SecurityGroups,dc=example,dc=com","cn", "member");

    print "\nInvalid user:\n";
    $arrayUserNotInDirectory = array();
    $arrayUserNotInDirectory = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "xe0012345", 'xDoesN0tM@tt3r');

    print "\nGood password without authorization:\n";
    $arrayUserAuthenticated = array();
    $arrayUserAuthenticated = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re');

    print "\nGood password with authorized user:\n";
    $arrayUserAuthorized = array();
    $arrayUserAuthorized = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re',"ou=SecurityGroups,dc=example,dc=com","cn", "member", "cfyP_Unix_UnixUsers");

    print "\nGood password with unauthorized user:\n";
    $arrayUserNotAuthorized = array();
    $arrayUserNotAuthorized = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re',"ou=SecurityGroups,dc=example,dc=com","cn", "member", "WIN AM Team West");

    print "\nBad system account:\n";
    $arrayBadSystemCred = array();
    $arrayBadSystemCred = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "xSy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re');


LDAP Authentication: PHP and Active Directory

This is a very brief function that authenticates a user against Active Directory. Because you can authenticate using a fully qualified DN, sAMAccountName, or userPrincipalName … there’s no need to use a system credential or search for the user provided you’ve got a single domain in your forest (i.e. you know what to prepend to the sAMAccountName or postpend to userPrincipalName).

If you need to perform authorization as well as authentication, you’ll need the user’s FQDN so use the generic LDAP authentication and authorization function.

    #=== FUNCTION ==================================================================
    #      NAME: activeDirectoryLDAPAuthentication
    #      PARAMETERS: 
    #                    $strLDAPHost                   String  LDAP Server URI
    #                    $strLogonUserID                String  Input user ID
    #                    $strLogonUserPassword          String  Input user password
    #     DESCRIPTION: Verify authentication againt Active Directory server.
    #     RETURNS: int BindReturnCode:    -2 indicates LDAP connection failure, -3 indicates user auth not attempted, >=0 is IANA-registered resultCode values (https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xml#ldap-parameters-6)
    #							NOTE: 0 is successful authentication in IANA-registered resultCode
    #     USAGE: $iBindResult = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", $strInputUserName, $strInputUserPassword)
    function activeDirectoryLDAPAuthentication($strLDAPHost, $strLogonUserID, $strLogonUserPassword){
        $iBindReturnCode = null;
        // Validate password is not null, otherwise directory servers implementing unauthenticated bind (https://tools.ietf.org/html/rfc4513#section-5.1.2) will return 0 on auth attempts with null password
        if( strlen($strLogonUserPassword) < 1){
            $iBindReturnCode = -1;
            $userDS = ldap_connect($strLDAPHost);
                ldap_set_option($userDS, LDAP_OPT_PROTOCOL_VERSION, 3);

                $userBind = ldap_bind($userDS, $strLogonUserID . '@example.com', $strLogonUserPassword);
                $iBindReturnCode = ldap_errno($userDS);
            // ldap connection failed
                $iBindReturnCode = -2;              
        return $iBindReturnCode;

    $iBadUser = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", "xe0012345", 'N0tTh3P@s5w0rd');
    print "\nInvalid user: $iBadUser\n";

    $iUserAuthenticated = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", "e012345", 'Go0dP@s5w0rdH3r3');
    print "\nGood password: $iUserAuthenticated\n";

    $iBadPassword = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", "e0012345", 'N0tTh3P@s5w0rd');
    print "\nBad password: $iBadPassword\n";

    $iBadHost = activeDirectoryLDAPAuthentication("ldaps://abc.example.com", "e0012345", 'N0tTh3P@s5w0rd');
    print "\nBad host: $iBadHost\n";


Identifying System-Only AD Attributes

This information is specific to Active Directory. MSDN has documentation for each schema attribute — e.g. CN — which documents if the attribute is “system only” or not.

For an automated process, search at the base cn=schema,cn=configuration,dc=example,dc=com with the filter (&(ldapDisplayName=AttributeName))and return the value of systemOnly. E.G. this shows that operatingSystemServicePack is user writable.

ldap_search_s(ld, "cn=schema,cn=configuration,dc=example,dc=com", 2, "(&(ldapDisplayName=operatingSystemServicePack))", attrList,  0, &msg)
Getting 1 entries:
Dn: CN=Operating-System-Service-Pack,CN=Schema,CN=Configuration,dc=example,dc=com
systemOnly: FALSE; 

You can also list all of the system-only attributes by using the filter (&(systemOnly=TRUE)) and returning ldapDisplayName

ldap_search_s(ld, "cn=schema,cn=configuration,dc=example,dc=com", 2, "(&(systemOnly=TRUE))", attrList,  0, &msg)
Getting 189 entries:
Dn: CN=OM-Object-Class,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: oMObjectClass; 

Dn: CN=Canonical-Name,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: canonicalName; 

Dn: CN=Managed-Objects,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: managedObjects; 

Dn: CN=MAPI-ID,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: mAPIID; 

Dn: CN=Mastered-By,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: masteredBy; 

Dn: CN=Top,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: top; 

Dn: CN=NTDS-DSA-RO,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: nTDSDSARO; 

Dn: CN=Application-Process,CN=Schema,CN=Configuration,dc=example,dc=com
lDAPDisplayName: applicationProcess; 


Preventing Unauthenticated Binds in Active Directory

There is finally a Windows server-side solution to prevent “unauthenticated bind” requests (detailed in LDAP RFC 4513 section 5.1.2 with a note regarding the subsequent security considerations in section 6.3.1) from allowing unauthorized users to successfully authenticate.

It has always been possible to handle in code (i.e. verify that username and password are both non-null prior to communicating with the directory server) and is my personal preference as a developer cannot predict how individual directory services will be configured.

But for the third-party apps that don’t prevent unauthenticated binds, a setting to disallow unauthenticated bind operations to Active Directory was added in Windows 2019 — in your Configuration partition, open the properties of CN=Directory Service, CN=Windows NT, CN=Services, CN=Configuration — find the msDS-Other-Settings attribute, and add a new entry DenyUnauthenticatedBind=1

Using LDP To Browse Active Directory

One of the RSAT tools, ldp.exe, can be quite useful if you are trying to interact with Active Directory via LDAP but don’t know anything about the domain.

From “Connection”, chose “Connect”. Most domain controllers have A records registered for the domain name, so you can connect to the domain name.

Active Directory generally prohibits anonymous read, so you’ll need to bind to the directory. From “Connection”, chose “Bind”. If your computer is logged into the domain, you can select “Bind as currently logged on user”. If not, select “Bind with credentials” — in addition to the fully qualified DN of an account, AD allows you to bind with both userPrincipalName and sAMAccountName. userPrincipalName is userid@<domain.name> and sAMAccountName is domain\userid.

Now that you’ve logged into the domain, you can select “View” and “Tree”. If you leave the BaseDN blank, LDP will find the root of the directory partition.

Voila, you’ll see your domain. You can click around, or right-click the root of the domain and select “Search”. Look for something generic like “(&(objectClass=person))” to find user accounts. You’ll be able to see what attributes are used for what data.

Additionally, at the top of the window, you’ll see the hostname of the domain controller you are using and the root base DN for the domain.

LDAP Auth With Tokens

I’ve encountered a few web applications (including more than a handful of out-of-the-box, “I paid money for that”, applications) that perform LDAP authentication/authorization every single time the user changes pages. Or reloads the page. Or, seemingly, looks at the site. OK, not the later, but still. When the load balancer probe hits the service every second and your application’s connection count is an order of magnitude over the probe’s count … that’s not a good sign!

On the handful of sites I’ve developed at work, I have used cookies to “save” the authentication and authorization info. It works, but only if the user is accepting cookies. Unfortunately, the IT types who typically use my sites tend to have privacy concerns. And the technical knowledge to maintain their privacy. Which … I get, I block a lot of cookies too. So I’ve begun moving to a token-based scheme. Microsoft’s magic cloudy AD via Microsoft Graph is one approach. But that has external dependencies — lose Internet connectivity, and your app becomes unusable. I needed another option.

There are projects on GitHub to authenticate a user via LDAP and obtain a token to “save” that access has been granted. Clone the project, make an app.py that connects to your LDAP directory, and you’re ready.

from flask_ldap_auth import login_required, token
from flask import Flask
import sys

app = Flask(__name__)
app.config['SECRET_KEY'] = 'somethingsecret'
app.config['LDAP_AUTH_SERVER'] = 'ldap://ldap.forumsys.com'
app.config['LDAP_TOP_DN'] = 'dc=example,dc=com'
app.register_blueprint(token, url_prefix='/auth')

def hello():
return 'Hello, world'

if __name__ == '__main__':

The authentication process is two step — first obtain a token from the URL Assuming valid credentials are supplied, the URL returns JSON containing the token. Depending on how you are using the token, you may need to base64 encode it (the httpie example on the GitHub page handles this for you, but the example below includes the explicit encoding step).

You then use the token when accessing subsequent pages, for instance

import requests
import base64


tupleAuthValues = ("userIDToTest", "P@s5W0Rd2T35t")

tokenResponse = requests.post(url = API_ENDPOINT, auth=tupleAuthValues)

if(tokenResponse.status_code is 200):
jsonResponse = tokenResponse.json()
strToken = jsonResponse['token']
print("The token is %s" % strToken)

strB64Token = base64.b64encode(strToken)
print("The base64 encoded token is %s" % strB64Token)

strHeaders = {'Authorization': 'Basic {}'.format(strB64Token)}

responseSiteAccess = requests.get(SITE_URL, headers=strHeaders)
print("Error requesting token: %s" % tokenResponse.status_code)

Run and you get a token which provides access to the base URL.

[lisa@linux02 flask-ldap]# python authtest.py
The token is eyJhbGciOiJIUzI1NiIsImV4cCI6MTUzODE0NzU4NiwiaWF0IjoxNTM4MTQzOTg2fQ.eyJ1c2VybmFtZSI6ImdhdXNzIn0.FCJrECBlG1B6HQJKwt89XL3QrbLVjsGyc-NPbbxsS_U:
The base64 encoded token is ZXlKaGJHY2lPaUpJVXpJMU5pSXNJbVY0Y0NJNk1UVXpPREUwTnpVNE5pd2lhV0YwSWpveE5UTTRNVFF6T1RnMmZRLmV5SjFjMlZ5Ym1GdFpTSTZJbWRoZFhOekluMC5GQ0pyRUNCbEcxQjZIUUpLd3Q4OVhMM1FyYkxWanNHeWMtTlBiYnhzU19VOg==
Hello, world

A cool discovery I made during my testing — a test LDAP server that is publicly accessible. I’ve got dev servers at work, I’ve got an OpenLDAP instance on Docker … but none of that helps anyone else who wants to play around with LDAP auth. So if you don’t want to bother populating directory data within your own test OpenLDAP … some nice folks provide a quick LDAP auth source.

WebLogic LDAP Authentication

Configuring an LDAP Authentication provider in WebLogic (version 11g used in this documentation)

  • In configuring LDAP authentication, I add a new authentication provider but continue to use the local provider for the system account under which WebLogic is launched. Partially because I don’t really use WebLogic (there’s an Oracle app with its own management site that runs within WebLogic – very small number of users, so our configuration is in no way optimized), but partially because using a network-sourced system account can prevent your WebLogic instance from launching. If your config isn’t right, or if the network is down, or a firewall gets in the way, or the LDAP server is down …. Your WebLogic fails to launch because its system ID is not validated.

WebLogic Configuration

Lock & Edit the site so we can make changes. On the left-hand pane, scroll down & find Security Realms

Go into your realm, select the “providers” tab. Supply a name for the provider (I included “LDAP” in the name to ensure it was clear which provider this was – may even want to specify something like “CompanyXLDAPAuthProvider”)

Select type “LDAPAuthenticator” for generic LDAP (I was using Sun DSEE, and moved to Oracle OUD without changing the authenticator type). Click OK to create.

Change the control flag on your default authenticator. Click the hyperlink for the default provider. On the “Common” tab, change the “Control Flag” to “SUFFICIENT” and save.

Click the hyperlink for the newly created provider. On the “Common” tab, change the “Control Flag” to “SUFFICIENT” and save.

Select the “Provider specific” tab.


Host:     <your LDAP server>

Port:      636

Principal:             <Your system account, provided when you request access to the LDAP directory>

Credentials:        <Your system account password>

Confirm Credentials:       <same as credentials>

SSLEnabled:        Check this box (for testing purposes, i.e. if you are unable to connect with these instructions as provided, you can set the port to 389 and not check this box to help with troubleshooting the problem. But production authentication needs to be done over SSL)


User Base DN:    <get this from your LDAP admin. Ours is “ou=people,o=CompanyX”)

All User Filter:    (&(objectClass=inetOrgPerson))

For applications with a single group restricting valid users, you can use the filter: (&(objectClass=inetOrgPerson)(isMemberOf=cn=GroupNameHere,ou=groups,o=CompanyX))

Users from name filter:  (&(uid=%u)(objectClass=inetOrgPerson))

User Search Type:                           subtree (onelevel may be fine, but verify with your LDAP administrator)

User Name Attribute:                     uid

User Object Class:                           inetOrgPerson

Use Retrieved User Name as Principal – I didn’t select this, don’t really know what it does


Group Base DN:               <another one to get from your LDAP admin. Ours is “ou=groups,o=CompanyX”>

All Groups Filter:              (&(objectClass=groupOfUniqueNames))

If your group names all have the same prefix, you could limit “all” groups to just your groups with a filter like (&(objectClass=groupOfUniqueNames)(cn=MyApp*))

Group from name filter: (&(cn=%g)(objectclass=groupofuniquenames))

Group search scope:                      subtree (again, onelevel may be fine)

Group membership searching:    <We select ‘limited’ because there are no nested groups in the LDAP directories. If you need to resolve nested group memberships, this and the next value will be different>

Max group membership search level:      0

Ignore duplicate membership:     Doesn’t really matter as we don’t have duplicates. I left this unchecked.

Static groups

Static group Attribute name:       cn

Static group Object Class:             groupOfUniqueNames

Static Member DN Attribute:       uniqueMember

Static Group DNs from Member filter:     (&(uniquemember=%M)(objectclass=groupofuniquenames))

Dynamic Groups              this section is left blank/defaults as we don’t use dynamic groups


Connection Pool Size:     Ideal value dependent on your anticipated application load – default of 6 is a good place to start.

Connect timeout:             Default is 0. I don’t know if this is something particular to WebLogic, but I generally use a 15 or 30 second timeout. If the server hasn’t responded in that period, it is not going to respond and there’s no need to hang the thread waiting.

Connection Retry Limit: Default is 1, this should be sufficient but if you see a lot of connection errors, either increase the connect timeout or increase this retry limit

Parallel Connect Delay:  0 (default) is fine

Result time limit:              0 (default) is OK. On my the LDAP server, there is no time limit for searches. Since WebLogic is making very simple searches, you could put a limit in here to retry any search that takes abnormally long

Keep Alive Enabled:         Please do not enable keep alive unless you have a specific need for it. Bringing up a new session uses slightly more time/resources on your app server than re-using an existing connection but that keep alive is a LOT of extra “hey, I’m still here” pings against the LDAP servers

Follow Referrals:              Un-check this box unless your LDAP admin tells you referrals are in use and should be followed.

Bind Anonymously on referrals:  Leave unchecked if you are not following referrals. If referrals are used and followed – ask the LDAP admin how to bind

Propagate cause for logon exception:      I check this box because I *want* the ugly LDAP error code that explains why the logon failed (49 == bad user/password pair; 19 == account locked out). But no *need* to check the box

Cache Related Settings:  This is something that would require more knowledge of WebLogic than I have ?

If you enable caching, you may not see changes for whatever delta-time is the cache duration. So, the defaults of enabling cache & retaining it for 60 seconds wouldn’t really create a problem. If you set the cache duration to one day (a silly setting to make the problem cache can create clear) …. If I logged into your application at 2PM, did a whole bunch of work, went home, came back the next morning & saw my “your password is about to expire” warning … so go out to the password portal and change my password. Reboot, get logged back into my computer …. and try to access your application, I will get told my password is invalid. I could try again, even type what I *know* is my password into notepad & paste it into your app … still not able to log on. My old password, were I to try it, would work … but otherwise I’d have to wait until after 2PM before my new password would work.

Group membership changes could be a problem too – with the same 24 hour cache, if I am a valid user of your application who signs in at 2PM today, but my job function changes tomorrow morning & my access is revoked … I will still have application access until the cache expires. I am not sure if WebLogic does negative caching – basically if I am *not* a user, try to sign in and cannot because I lack the group membership & get an access request approved *really quickly* to become a group member, I may still be unable to access the application until the “Lisa is not a member of group XYZ” cache expires. If WebLogic does not do negative caching, then this scenario is not an issue.

So you might be able to lower utilization on your app server & my LDAP server by enabling cache (if your app, for instance, re-auths the object **each time the user changes pages** or something, then caching would be good). If you are just checking authentication and authorization on logon … probably not going to do much to lower utilization. But certainly keep the cache TTL low (like minutes, not days).

GUID Attribute:  nsUniqueID

Establishing The SSL Trust

For encryption to be negotiated with the LDAP servers, you need to have a keystore that includes the public keys from the CA used to sign the LDAP server cert. Obtain the base 64 encoded public keys either from the PKI admin or the LDAP admin. Place these file(s) on your server – I use the /tmp/ directory since they are no longer needed after import.

From the domain structure section, select: Environment=>Servers and select your server. On the “Configuration” tab, click the keystores sub-tab. If you are not already using a custom trust, you need to change they keystore type to use a custom trust (and specify a filename in a path to which the WebLogic account has access – keystore type is JKS and the password is whatever you are going to make the keystore password). If you *are* already using a custom trust, just record the file name of the custom trust keystore.

Use keytool to import the CA keys to the file specified in the custom trust. The following examples use a root and signing CA from my company, the CA chain which signs our LDAP SSL certs.

./keytool -import -v -trustcacerts -alias WIN-ROOT -file /tmp/WIN-ROOT-CA.b64 -keystore /path/to/the/TrustFile.jks -keypass YourKeystorePassword -storepass YourKeystorePassword

./keytool -import -v -trustcacerts -alias WIN-WEB -file /tmp/WIN-WEB-CA.b64 -keystore /path/to/the/TrustFile.jks -keypass YourKeystorePassword -storepass YourKeystorePassword

*** Under advanced, I had to check off “Use JSSE SSL” for SSL to work. Without that checked off, I got the following error in the log:

####<Feb 23, 2018 10:11:36 AM EST> <Notice> <Security> <server115.CompanyX.com> <AdminServer> <[ACTIVE] ExecuteThread: ’12’ for queue: ‘weblogic.kernel.Default (self-tuning)’> <<WLS Kernel>> <> <58b1979606d98df5:292a2ff6:161c336d0ba:-8000-0000000000000007> <1519398696289> <BEA-090898> <Ignoring the trusted CA certificate “CN=WIN-WEB-CA,DC=CompanyX,DC=com”. The loading of the trusted certificate list raised a certificate parsing exception PKIX: Unsupported OID in the AlgorithmIdentifier object: 1.2.840.113549.1.1.11.>

####<Feb 23, 2018 10:11:36 AM EST> <Notice> <Security> <server115.CompanyX.com> <AdminServer> <[ACTIVE] ExecuteThread: ’12’ for queue: ‘weblogic.kernel.Default (self-tuning)’> <<WLS Kernel>> <> <58b1979606d98df5:292a2ff6:161c336d0ba:-8000-0000000000000007> <1519398696289> <BEA-090898> <Ignoring the trusted CA certificate “CN=WIN-Root-CA”. The loading of the trusted certificate list raised a certificate parsing exception PKIX: Unsupported OID in the AlgorithmIdentifier object: 1.2.840.113549.1.1.11.>

An alternate solution would be to update your WebLogic instance – there are supposedly patches, but not sure which rev and it wasn’t worth trial-and-erroring WebLogic patches for my one WebLogic instance with a dozen users.

Whew, now save those changes. Activate changes & you will probably need to restart your WebLogic service to have the changes go into effect. You can go into the roles & add LDAP groups as — specifically, I added our LDAP group’s CN to the administrators WebLogic role.

Bulk LDAP Operations

LDIF – Directory Import and Export using LDIFDE.EXE and LDAPMODIFY


You can obtain ldifde.exe from any existing domain controller – copy \\dcname\c$\winnt\system32\ldifde.exe  to your SYSTEM32 folder. The ldapmodify command is part of the openldap-clients package on Linux. Windows builds of the openldap clients are available. The data being imported is essentially the same, just the command line to invoke the program differs.

Using LDIF files to update LDAP data is facilitated if you know the directory schema attributes, especially those associated to the user object class.  Active Directory schema is well documented on MSDN – base Active Directory schema can be found at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/adschema/adschema/active_directory_schema_site.asp and the extensions made by Exchange are documented at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wss/wss/wss_ldf_AD_Schema_intro.asp

LDIFDE is a command line program which runs with currently logged on user’s credentials – this means that your ID can write changes to AD using LDIFDE.  Please do not play with this program in the production Active Directory domain but rather test writing to a test domain.


LDIF Export

Exporting directory information is fairly straight forward:


ldifde –f filename.txt –d “ou=base,DC=windstream,DC=com” –p subtree –r “(&(attribute=value)(otherattribute=othervalue))” –s domaincontroller.windstream.com –l “attribute1, attribute2, attribute3 …”


-f File to contain exported data
-d Search base
-p Search scope
-r RFC-2254 compliant filter
-s Domain controller from which to obtain data
-l Attributes to be returned (eliding this command will return values for all attributes)


This will create a file named ljlexport.txt of all e####### users with email addresses whose accounts are located under WINDSTREAM.COM\WINDSTREAM\IT.  The file will contain, for each user, their logon ID (sAMAccountName), email address (mail), account status (userAccountControl), display name, and telephone number.

ldifde –f ljlexport.txt –d “ou=IT,ou=windstream,DC=windstream,DC=com” –r “(&(sAMAccountName=e*)(mail=*))” –s neohtwnnt836.windstream.com –l “sAMAccountName, mail, userAccountControl, displayName, telephoneNumber”


-r specifies the search filter and can become a rather complex query depending on what you are looking for — & is an AND filter, | is an OR filter.  ! can be used to find unmatched values and * works as a wildcard

“(&(mail=*)(!sAMAccountName=n99*))”        find all mail enabled accounts which are not N99’s for instance.

“(&(sAMAccountName=e0*)(!(employeeID=*)))”      Find all employee accounts with no employee ID specified

“(&(mail=*)(|(sAMAccountName=n99*)(sAMAccountName=g99*)))”            mail enabled accounts which are either n99’s or g99’s

“(&(objectClass=user)(objectCategory=person))”        Real user accounts, objectClass=user alone will return a lot of things you don’t believe are users J

“(&(objectClass=user)(objectCategory=person)(telephoneNumber=813*))”     Real user accounts with phone numbers in the 813 area code

“(&(objectClass=user)(objectCategory=person)(msExchHomeServerName=*SCARLITNT841))”       Real user with mailboxes on SCARLITNT841


-d specifies the search base (subtree search by default) – you can use “DC=windstream,DC=com” to get the entire directory or something like “ou=Central,ou=windstream,DC=windstream,DC=com” to just get users within the Central OU.


LDIF Import

Importing Directory Information is not so straight forward and again do not play with this program in the production Active Directory domain.  You need to create an ldif import file to make changes to objects.  A sample file content:

dn: cn=Landers\, Lisa,ou=GPOTest,ou=IT,ou=windstream,dc=windstreamtest,dc=com
changetype: modify
add: proxyAddresses
proxyAddresses: smtp:lisa@newtestdomain.windstream.com
replace: telephoneNumber
telephoneNumber: 501-905-4305
delete: mobile
mobile: 501-607-3750
delete: facsimileTelephoneNumber
dn: cn=Ahrend\, Sam,ou=IT,ou=windstream,dc=windstreamtest,dc=com
changetype: modify
replace: mDBUseDefaults
mDBUseDefaults: FALSE
replace: mDBStorageQuota
mDBStorageQuota: 190000
replace: mDBOverQuotaLimit
mDBOverQuotaLimit: 200000

Provided you have an import file, the syntax of the command is ldifde –i –v –k –y –f filename.txt  


-i LDIFDE import operation
-v Produce verbose output
-k Ignore constraint violations (and entry exists errors on add)
-y Lazy commit
-f File name to be imported



Changetype Add

A changetype of add is used when the entire object does not currently exist – this ldap operation creates a new object with the attributes specified in the stanza.


dn: cn=TestingGroup 10001,ou=testing,ou=it,DC=windstream,DC=com
changetype: add
cn: TestingGroup 10001
distinguishedName: cn= TestingGroup 10001,ou=testing,ou=it,DC=windstream,DC=com
name: TestingGroup10001
sAMAccountName: TestingGroup10001
objectClass: group
objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=windstream,DC=com
groupType: -2147483646
managedBy: cn=Landers\, Lisa,OU=GPOTest,ou=IT,OU=WINDSTREAM,DC=windstream,DC=com
member: cn=Landers\, Lisa,OU=GPOTest,ou=IT,OU=WINDSTREAM,DC=windstream,DC=com
member: cn=Ahrend\, Sam, ou=IT,OU=WINDSTREAM,DC=windstream,DC=com
legacyExchangeDN: /o=WINDSTREAMEXCH/ou=First Administrative Group/cn=Recipients/cn=TestingGroup10001
mailNickname: TestingGroup10001
reportToOriginator: TRUE


This example will create an e-mail enabled global security group named “TestingGroup 10001” under windstream.com – it – testing.  Both Sam and I will be listed as members of  the group and I will be listed as the group owner.  Add operations can be chained together with just a blank line between them should you need to add multiple objects in batch.


Any mandatory attributes for the schema classes need to be included (handled by ldifde works too) or the add operation will fail.  Attributes not valid for the object classes will cause the operation to fail as well.  If an object already exists, no change will be made even if some of the attributes specified differ from the values within AD.


Changetype Delete

Delete is used to delete the entire object – so be extra careful here.  The syntax is quite simple – the DN of the object to be removed and a line that says “changetype: delete”.  Again, multiple operations can be chained together with just a blank line.


dn: cn=TestingGroup 10002,ou=testing,ou=it,DC=windstream,DC=com
changetype: delete
dn: cn=TestingGroup 10003,ou=testing,ou=it,DC=windstream,DC=com
changetype: delete
dn: cn=TestingGroup 10004,ou=testing,ou=it,DC=windstream,DC=com
changetype: delete
dn: cn=TestingGroup 10005,ou=testing,ou=it,DC=windstream,DC=com
changetype: delete

Changetype Modify

Modify is used to change attributes on an existing object.  Modify can be used to add, replace, or delete an attribute.  The example above has two different stanza’s (separated by a blank line).  Within each stanza several operations are made:


First to add another email address to the secondary email addresses.  For a multi-value attribute (member, proxyAddresses …) changetype: modify\nadd: attribute adds another value to the attribute.  For single-valued attributes modify/add will fail if a value is present.

dn: cn=Landers\, Lisa,ou=GPOTest,ou=IT,ou=windstream,DC=windstream,DC=com
changetype: modify
add: proxyAddresses
proxyAddresses: smtp:lisa@newtestdomain.windstream.com

The next operation replaces the telephone number with the value specified – this will overwrite the existing value.  Be careful not to replace multi-value attributes

replace: telephoneNumber
telephoneNumber: 501-905-4305

The next operation deletes the mobile phone number with the value specified – if the value does not match, a change is not made.  This can be used as a failsafe, in this case only delete my mobile telephone number if the value is what I expect it to be, or to remove entries from multi-value attributes.  Delete the member of the group which is the specific member listed without changing the other group members, for instance.

delete: mobile
mobile: 501-607-3750

The next operation deletes the fax number – regardless of content the value is removed.

delete: facsimileTelephoneNumber

A blank line separates the two stanzas and a new object is specified.  Again the modify/replace option is used which will change the attributes to the values specified.

dn: cn=Ahrend\, Sam,ou=IT,ou=windstream,DC=windstream,DC=com
changetype: modify
replace: mDBUseDefaults
mDBUseDefaults: FALSE
replace: mDBStorageQuota
mDBStorageQuota: 190000
replace: mDBOverQuotaLimit
mDBOverQuotaLimit: 200000
dn: cn=Landers\, Lisa,ou=IT,ou=windstream,DC=windstream,DC=com
changetype: modify
replace: mDBUseDefaults
mDBUseDefaults: TRUE

Changetype ModDN
ModDN changes the object’s distinguishedName.  This is interesting as it can be used to move users – this example would move my account into the Central OU under ACI

dn: CN=Landers\, Lisa,OU=GPOTest,OU=IT,OU=WINDSTREAM,,DC=windstream,DC=com
changetype: moddn
newrdn: CN= Landers \, Lisa,OU=Central,OU=WINDSTREAM,DC=windstream,DC=com
deleteoldrdn: 1


ModDN can also be used to rename the object’s display in administrative listings:

dn: CN=Landers\, Lisa,OU=GPOTest,OU=IT,OU=WINDSTREAM,,DC=windstream,DC=com
changetype: moddn
newrdn: CN= Landers\, Jane, OU=GPOTest,OU=IT,OU=WINDSTREAM,,DC=windstream,DC=com
deleteoldrdn: 1

You would of course want to modify/replace at least givenName and displayName on the object dn to avoid confusion – otherwise my middle name would appear in active directory users and computers but my first name in Outlook.  I would modify the attributes first – if you modify the DN first, you need to remember to use the new DN for subsequent attribute value changes.

dn: CN=Landers\, Lisa,OU=GPOTest,OU=IT,OU=WINDSTREAM,,DC=windstream,DC=com
changetype: modify
replace: givenName
givenName: Jane
replace: displayName
displayName: Landers, Jane
dn: CN=Landers\, Lisa,OU=GPOTest,OU=IT,OU=WINDSTREAM,,DC=windstream,DC=com
changetype: moddn
newrdn: CN= Landers\, Jane, OU=GPOTest,OU=IT,OU=WINDSTREAM,,DC=windstream,DC=com
deleteoldrdn: 1

LDAP Directories


The International Telecommunication Union, ITU http://www.itu.int/home/index.html, is an organisation of the United Nations (sorry, Carra, now I guess all directories are on par with poodles) which submits recommendations for the coordination of global telecom and radio systems.  The Telecommunication Standardisation Sector, ITU-T, is the branch submits recommendations for telecommunication standards.  ITU-T was formerly known as Comité Consultatif International Téléphonique et Télégraphique, CCITT.

Published in volume eight of the 1988 CCITT Blue Book, X.500 is the recommendation for “The Directory”.  Additional recommendations X. 501, 509, 512, 518, 519, 520, 521, 525, and 530 farther defined ITU’s idea for directory service functionality.  Once ITU-T put forth the recommendation, several international standards bodies got involved, and eventually yielding the 9594-1 standard from ISO (from isos, name used by the International Organisation for Standardisation) and IEC (International Electro-technical Commission).   I’ve got the 1993 version (./9594-1-X.500.A4.ps) but never bothered to get the updates.

X.500 Components

Some of the terminology used within the X.500 recommendation is used in non-X.500 directories.  If you want to more about these or what they do, read the ISO/IEC standard

  • Directory Information Base (DIB) is used to store the directory data
  • Directory Information Tree (DIT) is a hierarchy within the DIB
  • Directory Service Agent (DSA) is a server which stores the DIB
  • Directory User Agent (DUA) is an application interface over which queries are made to the DSA
  • Directory Access Protocol (DAP) is the method used by the DUA to communicate with the DSA
  • Directory System Protocol (DSP) is used for communication between DSA’s
  • Directory Management Domain (DMD) contains DSA’s within an organisation
  • Directory Management Organization (DMO) is the actual organisation of the DMD
  • Directory Information Shadowing Protocol (DISP) is used to replicate information between DSA’s in a DMD
  • Directory Operational Binding Management Protocol (DOP) is policies which define the administrative information exchanged between DSA’s


X.500 directory access protocol in its actual form involves a lot of overhead we just don’t want to incur to keep a guy, his password, his address, and his phone number all available somewhere.  Lightweight Directory Access Protocol, LDAP, retains the directory structure of X.500 with a simplified TCP/IP based access protocol.  Referrals were simplified and the replication protocol generalized.

The LDAP Technical Specification Roadmap is maintained by the Internet Engineering Task Force, IETF, as RFC 4510 (http://tools.ietf.org/html/rfc4510).  The multiple RFC’s encompassed by LDAP are included in section 1, “The LDAP Technical Specification”.

Directory Components – Schema:

Every directory has a schema set which defines its components.  The schema defines what type of objects exist in the directory and what attributes are valid for those objects.  Schema “attributes” are types of values which can be on an object – anything from an asset ID number to a vendor ID.  Schema “classes” are categories of entries which can be made – and a group of attributes that category means the object can have.  An attribute can be “mandatory” within a class – for example a user account may need a cn value, an objectClass, and an objectCategory.  You cannot make a user account without these values.  The remaining values in a class are “optional” – you may or may not have facsimileTelephoneNumber on your account.

There are three types of classes: abstract, auxiliary, and structural.  Abstract classes are classes which an object cannot directly be – cannot be the object’s objectClass – but are used through subordinate classes to define valid and required attributes..  Auxiliary classes are somewhat like abstract classes in that an object cannot be an auxclass – auxclass’s are instead ‘attached’ to structural classes to create additional valid attributes for the class.  Structural classes are the objectClass “things” you can make within the directory – a user account, a print queue, or even a replication partnership.  The semantics may vary between directory services, but the idea remains.

If you wish to add additional attributes to your objects, you need to extend the directory schema.  I will note here that Novell seems to allow undefined schema extensions.  Normally an organisation is issued an OID number (Object Identifier).  A private enterprise would normally request an OID assignment from IANA (existing assignments are published to http://www.iana.org/assignments/enterprise-numbers) or ANSI.  ANSI’s cost money whereas IANA’s don’t.  Guess which we use! has been issued to Alltel, has been issued to Windstream.  Additional decimal number groups past the organisation’s enterprise number can be sub-allocated within the organisation.  .5 within Alltel is used within the AD schema extensions, and 5.1 is specific to the test domain where 5.0 is specific to the production domain.  The breakout an OID number within Alltel’s production AD is:

1                              iso
1.3                            org
1.3.6                          dod                        internet                      private                    enterprise              Alltel            Alltel Active Directory          Production AD      Production AD Classes      Production AD Attributes

So (cSOBESPolicy) is an attribute within the allteltest.com test domain – the seventh one at that – and (alltelPerson) is a class in the production alltel.com domain (the first and only one).  A different numbering scheme off the “” base should be used for other directories.

Once you have created new attributes and appended an aux-class to an existing class, you may (iPlanet, IBM LDAP) need to restart the LDAP service or you may (Active Directory) need to wait for the change to propagate.  Our customized attributes are associated to an aux-class of user called alltelPerson. 

Please do not randomly modify the schema – especially in Active Directory.  In AD you cannot remove the entries.  You can deactivate an attribute but it cannot be deleted.

Directory Components – Structure:

A directory structure will begin at its root – an “O”, organisation name, in iPlanet/NDS/IBM LDAP, or “DC”, domain component, in Active Directory.  OU’s, Organisational Units may be contained under the directory root to provide some organisation to the objects housed within the directory.  You may also see CN’s under the domain root –common name with an objectClass of container.  There may be several layers of OU’s or containers before you find the “leaf” objects – users, contacts, printers, servers, whatever.  The fully qualified LDAP syntax of an object begins at the object and ‘walks’ up the tree – “cn=e0082643,ou=core1,ou=lit,ou=ar,ou=sc,o=alltel” for my ID in the ALLTEL-TREE for example.  “Special” characters are encoded or escaped as required – “cn=Landers\, Lisa,ou=GPOTest,ou=IT,ou=ACI,dc=alltel,dc=com”

LDAP Operations

Connections to LDAP are normally made on port 389 for clear text and 636 for encrypted.  Ldap.alltel.com (iplanet), metatreeldap.alltel.com (CSO metatree), litexchldap.alltel.com (active directory ldap), and any NetWare 6.0+ server within the alltel-tree use these standard ports, but alternate ports can be assigned within most directory servers.

Once a connection is made, it may be possible to query the directory.  A limited set of data should be returned as any directory enumeration at this point is done under an anonymous credential.  To specify the user with which you wish to perform directory operations, a BIND must be made.

Search operations include a base and scope (where to begin the search and how deep to traverse the directory under the base), and usually a filter (what to search for) and attributes requested.  Ldapsearch.exe (Win32 available from \\neohtwnlx810.windstream.com\NDSSupport\softlib\misc\ldaputils\) can be used to search an LDAP compliant directory.  Ldapsearch options: -h hostname.alltel.com  -b “ou=search,ou=base,dc=alltel,dc=com” –s “scope” –D “cn=user,ou=location,dc=alltel,dc=com” –W “RFC-2254 compliant filter” attributes to return go here

The search scope can be base (just the object defined as the search base), one (the search base and one level under), or sub (everything subordinate to the search base).  Eg:

ldapsearch -h litexchldap.alltel.com -b “ou=GPOTest,ou=IT,ou=ACI,dc=alltel,dc=com” -s “one” -LLL -D “cn=Landers\, Lisa,ou=GPOTest,ou=IT,ou=ACI,dc=alltel,dc=com” -W “(&(objectClass=user)(mail=*))” displayName mail

The –W option prompts for a password, alternately -w “PasswordGoesHere” can be used.  Once a password has been supplied in this example, the display name and email address for users who have an email address will be returned:

dn: CN=ACI WWC LDS Undeliverables,OU=GPOTest,OU=IT,OU=ACI,DC=alltel,DC=com
displayName: ACI WWC LDS Undeliverables
mail: ACI.WWC.LDS.Undeliverables@alltel.com
dn: CN=bob,OU=GPOTest,OU=IT,OU=ACI,DC=alltel,DC=com
displayName: bob
mail: bob@alltel.com
dn: CN=Landers\, Lisa,OU=GPOTest,OU=IT,OU=ACI,DC=alltel,DC=com
displayName: Landers, Lisa
mail: Lisa.Landers@alltel.com

Additional operations to add objects, delete objects, modify objects, and modify the distinguished name of the object can be done.  More information on this will be provided later.