Using ABP 8.3.3. Have not yet upgraded to the latest 10 version due to breaking changes. Currently on ABP Enterprise subscription.
Provide us with the following info:
🧐 Hint: If you are using the ABP Studio, you can see all the information about your solution from the configuration window, which opens when you right-click on the solution and click on the Solution Configuration button.
- Where can we intercept the call to ActiveDirectory in order to check login and also determine if user is authenticated? Note: setting the AD server in settings only partly gets you there. It still does not work, nor does it create an account when a usser registers. Tried this in ABP dotnetcore 10 and dotnet core 8 versions. We had to write code to authenticate the user in AD, but where to after this?
7 Answer(s)
-
0
- In ABP 8.3.3, there isn’t a built-in “intercept the call to ActiveDirectory” hook in the login pipeline for on-prem Windows AD / LDAP the same way there is for OIDC/OAuth external providers. The “AD server in settings” you mentioned is not enough by itself to make ABP automatically authenticate against AD and auto-provision users.
- The supported/ABP-native place to plug in “authenticate elsewhere, then create/sign-in an ABP user” is the External Login pipeline (i.e., treat AD as an external identity provider). In practice for on-prem AD, you typically implement your own authentication handler/provider (LDAP bind / Kerberos / etc.), then sign in using ABP Identity by creating/finding an
IdentityUserand issuing the normal ABP auth cookie/token.
What to do (ABP 8.3.3, MVC, tiered)
Do AD authentication in the Auth Server (or IdentityServer/OpenIddict host)
- In a tiered solution, the “login happens” in the AuthServer/IdentityServer app (depending on your template/version).
- Put your AD credential validation in the login flow there (e.g., custom login page model / custom action that validates username+password against AD).
After AD says “OK”, create or find the ABP user and sign in
- The “where to after this?” part is: create/find
IdentityUserand sign in via ASP.NET Core Identity (so ABP can do permissions/auditing/tenancy/etc.). - Conceptually:
- Look up user in ABP by username/email.
- If not found, create it (and set email/name fields from AD if you have them).
- Optionally add an external login record (provider name like
"LDAP"and provider key like AD’s GUID/UPN) if you want it tracked like an external provider. - Then call the normal sign-in manager to issue the auth cookie / token.
- The “where to after this?” part is: create/find
Do NOT expect “Register” to create AD accounts
- ABP’s register page creates users in the ABP database/Identity store, not in AD.
- If you need “registration” to mean “create in AD”, that is a separate provisioning process (and usually not allowed in many enterprises). You’d have to customize registration to call your AD provisioning logic.
If you want automatic user creation on first login
- Use the same logic in the login callback (after AD validation) to create the ABP
IdentityUserrecord the first time that AD user signs in.
- Use the same logic in the login callback (after AD validation) to create the ABP
Verified references (closest applicable ABP guidance)
- ABP account module overview (login/registration are ABP Identity-based): https://abp.io/docs/latest/modules/account
- ABP’s general statement about authentication capabilities (mentions AD/LDAP as an integration scenario, but not a built-in “hook point” API): https://abp.io/docs/latest/others/why-abp-platform
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Where in code do we start?
If you want automatic user creation on first login
Use the same logic in the login callback (after AD validation) to create the ABP IdentityUser record the first time that AD user signs in.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Another issue is it seems the AD being looked for is some generic AD, not windows AD. Many code changes for AD had to be made to finally authenticate. Even then, it seems something is missing. Shouldn't be this much work.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi, Thanks for the follow-up. If you are using the built-in LDAP login in Identity Pro, the place to start is not the register page. The username/password login flow already calls the LDAP provider automatically:
- The login page posts the username and password.
- ABP calls
SignInManager.PasswordSignInAsync(...). AbpSignInManagerchecks the registered external login providers.LdapExternalLoginProvider.TryAuthenticateAsync(...)performs the LDAP bind.- If LDAP authentication succeeds, ABP creates or updates the
IdentityUserand signs the user in. So, for your scenario, the main hook points are:
LdapExternalLoginProvider: customize how the username is normalized before authentication.OpenLdapManager: customize how ABP searches the directory and reads user information. This is important because the built-in LDAP implementation is OpenLDAP-oriented by default (uid=...,cn=..., and(&(uid=...))). For Windows AD, you usually need a Windows AD-specific format such asDOMAIN\\user,user@domain, or a search based onsAMAccountName/userPrincipalName. So changing only the LDAP server settings is usually not enough. Also, the register page is not the correct place for LDAP provisioning:- normal registration creates a local ABP user
- it does not create an AD account
- for LDAP, ABP user creation happens after a successful LDAP login, or by using the Identity Pro external user import feature So the smallest practical next step is:
- Keep the login page as is.
- Replace
OpenLdapManagerfor your Windows AD bind/search behavior. - Replace
LdapExternalLoginProviderso the username format matches your AD format. - Make sure the AD user has a readable email value, because ABP needs that while creating the
IdentityUser. If you mean true integrated Windows Authentication (IIS/Negotiate) rather than LDAP username/password, that is a different flow and goes through the external login callback path instead of the LDAP provider. If login succeeds but the ABP user is still not created, please share these details and we can narrow it down quickly:
- the exact username format you are submitting (
jsmith,DOMAIN\\jsmith, orjsmith@domain) - whether the AD user has the
mailattribute populated - your custom username normalization / LDAP search filter changes
- the exact exception or log entry right after the successful bind Best regards, ABP Support Team
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Thank you for the quick reply. NO: Not using IIS\WINDOWS authentication.
- I modified LdapExternalLoginProvider NormalizeUserNameAsync method:
2. protected virtual async Task<string> NormalizeUserNameAsync(string userName) { string usertoLogin=$"mydomain\\{userName}, {await LdapSettingProvider.GetBaseDcAsync()}"; return usertoLogin; // not used: return $"uid={userName}, {await LdapSettingProvider.GetBaseDcAsync()}"; } When I login with the built-in admin account: OpenLdapManager calls ConnectAsync schema var gets set to LDAP Next,`ldapConnection.Connect(await LdapSettingProvider.GetServerHostAsync(), await LdapSettingProvider.GetServerPortAsync(), schema);` is called. I expect this to fail, of course. I get an invalid username and password. The login does not fall back to a local ABP account. Can't login at all. Next. trying a domain account in format mydomain\myusername and I get further: In Method DetermineProviderCultureResult(HttpContext httpContext) and I get to this point: `string returnUrl = request.Query[ReturnUrl]; // this is empty. if (returnUrl.IsNullOrWhiteSpace()) { return await NullProviderCultureResult; }` action returns and same message invalid username and password. At some point the code keeps moving along and I get the authservermodule`.AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, options => { //Personal Microsoft accounts as an example. options.AuthorizationEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize"; options.TokenEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; options.ClaimActions.MapCustomJson("picture", _ => "https://graph.microsoft.com/v1.0/me/photo/$value"); options.SaveTokens = true; })`Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
And ultimately never logs in.
After coding and modifying a console app as provided in one of the examples, I can authenticate to On-Prem Windows Server AD. Here's the working code for the console app, slightly modified. Note using static LdapForNet... was necessary and all LdapForNet.Native was required.using System; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using LdapForNet; using LdapForNet.Native; using static LdapForNet.Native.Native; /********************** cmd to find logon DC systeminfo | find /i "logon server" distinguished name (DN) = CN=Doe\, Michael,OU=DEVELOPERS,OU=MarketSt,OU=MYOU,DC=mydomain,DC=local (DN ldap entry for Michael Doe) Filter example: ds.Filter = "(&(objectCategory=User)(objectClass=person)(name=" + userName + "*))"; ********************/ namespace LDAPTester { public class Program { static async Task Main(string[] args) { var server = "10.10.10.11"; var serverPort = 389; var baseDc = "OU=users,DC=mydomain,DC=local"; using (var cn = new LdapConnection()) { try { cn.Connect("ldap://10.10.10.11:389"); // bind using userdn and password cn.Bind(LdapAuthMechanism.SIMPLE, "mydomain\\michaeldoe", "Password111444"); var entries = cn.Search("DC=mydomain,DC=local", "(&(objectCategory=user)(objectClass=person) (sAMAccountName=michaeldoe))").Take(10); foreach (var entry in entries) { var x = entry; // key/value pairs of attributes directoryAttributes are the LDAP //field category names, givenName, surName etc. var de = x.ToDirectoryEntry(); Console.WriteLine(entry); //Debug.WriteLine(x.Dn["name"][0].ToString()); } string s = ""; } catch (Exception exception) { string msg = exception.Message; bool stopHere = true; } } ```Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Thanks for the extra details. We re-checked this in a fresh ABP 8.3.3 commercial MVC/tiered solution and validated that the following customization shape compiles against the generated template.
There are two separate points here:
- The local
adminfallback should still work. - The Windows AD username/search format still needs to be aligned with your directory.
For the first point: in ABP 8.3.3, the login flow first tries the LDAP external provider, and if that returns
false, it falls back to the normal localPasswordSignInAsync. So a failed LDAP bind should not block the built-in localadminuser.Because of that, if
adminalso fails, please verify these two items first:- local login is still enabled
- the local admin password is still valid
As a quick confirmation step, temporarily disable LDAP login and test the built-in admin user alone.
For the LDAP customization itself, the important correction is this: the bind username should not be
mydomain\\user, <BaseDc>. For Windows AD simple bind it should generally be only one of these formats:mydomain\\userNameuserName@mydomain.local
So
LdapExternalLoginProvider.NormalizeUserNameAsyncshould return only the bind name and should not appendBaseDc.For this scenario, the register page is not the correct customization point. In ABP 8.3.3 the relevant flow is:
LoginModel.OnPostAsync(...)AbpSignInManager.PasswordSignInAsync(...)LdapExternalLoginProvider.TryAuthenticateAsync(...)- after successful LDAP authentication, ABP creates or updates the
IdentityUser
So for Windows AD you will usually need to customize both:
LdapExternalLoginProviderOpenLdapManager
Below is the exact code shape we validated. You should adapt the namespace and class names to your solution.
WindowsAdLdapExternalLoginProvider.csusing System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Features; using Volo.Abp.Guids; using Volo.Abp.Identity; using Volo.Abp.Identity.ExternalLoginProviders.Ldap; using Volo.Abp.Ldap; using Volo.Abp.MultiTenancy; using Volo.Abp.Settings; namespace YourProject.Identity; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(WindowsAdLdapExternalLoginProvider), typeof(LdapExternalLoginProvider))] public class WindowsAdLdapExternalLoginProvider : LdapExternalLoginProvider { protected WindowsAdLdapManager WindowsAdLdapManager { get; } public WindowsAdLdapExternalLoginProvider( IGuidGenerator guidGenerator, ICurrentTenant currentTenant, IdentityUserManager userManager, IIdentityUserRepository identityUserRepository, OpenLdapManager ldapManager, ILdapSettingProvider ldapSettingProvider, IFeatureChecker featureChecker, ISettingProvider settingProvider, IOptions<IdentityOptions> identityOptions, WindowsAdLdapManager windowsAdLdapManager) : base( guidGenerator, currentTenant, userManager, identityUserRepository, ldapManager, ldapSettingProvider, featureChecker, settingProvider, identityOptions) { WindowsAdLdapManager = windowsAdLdapManager; } protected override Task<string> NormalizeUserNameAsync(string userName) { return WindowsAdLdapManager.NormalizeForActiveDirectoryAsync(userName); } }WindowsAdLdapManager.csusing System.Text; using System.Threading.Tasks; using LdapForNet; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity.ExternalLoginProviders.Ldap; using Volo.Abp.Ldap; namespace YourProject.Identity; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(WindowsAdLdapManager), typeof(OpenLdapManager), typeof(ILdapManager), typeof(LdapManager))] public class WindowsAdLdapManager : OpenLdapManager { public WindowsAdLdapManager(ILdapSettingProvider ldapSettingProvider) : base(ldapSettingProvider) { } protected override async Task<string> NormalizeUserNameAsync(string userName) { return await NormalizeForActiveDirectoryAsync(userName); } protected override Task<string> GetUserEmailAsync(LdapEntry ldapEntry) { var directoryEntry = ldapEntry.ToDirectoryEntry(); return Task.FromResult( directoryEntry.GetAttribute("mail")?.GetValue<string>() ?? directoryEntry.GetAttribute("userPrincipalName")?.GetValue<string>() ?? string.Empty ); } protected override Task<string> GetUserFilterAsync(string userName) { return Task.FromResult($"(&(objectClass=user)(sAMAccountName={EscapeFilterValue(userName)}))"); } public virtual async Task<string> NormalizeForActiveDirectoryAsync(string userName) { if (userName.Contains("@") || userName.Contains("\\")) { return userName; } var domain = await LdapSettingProvider.GetDomainAsync(); if (string.IsNullOrWhiteSpace(domain)) { return userName; } return $"{userName}@{domain}"; } protected virtual string EscapeFilterValue(string value) { var builder = new StringBuilder(value.Length); foreach (var character in value) { builder.Append(character switch { '\\' => "\\5c", '*' => "\\2a", '(' => "\\28", ')' => "\\29", '\0' => "\\00", _ => character }); } return builder.ToString(); } }Then register the custom provider in your Domain module:
Configure<AbpIdentityOptions>(options => { options.ExternalLoginProviders.Add<WindowsAdLdapExternalLoginProvider>(LdapExternalLoginProvider.Name); });Notes:
- if your LDAP settings
UserNameis already stored asmydomain\\serviceUserorserviceuser@mydomain.local, this code keeps it as-is and does not prefix it again GetUserFilterAsync(...)usessAMAccountNameinstead ofuidGetUserEmailAsync(...)falls back frommailtouserPrincipalNameDetermineProviderCultureResult(...)with an emptyReturnUrlis not the root cause hereAddMicrosoftAccount(...)is unrelated to the LDAP username/password flow
One more thing to verify: after LDAP bind succeeds, ABP still needs to read the user's email to create the
IdentityUser. If the ADmailattribute is empty, or your directory uses a different attribute, then adjustGetUserEmailAsync(...)accordingly.If you share your current
OpenLdapManageroverride and the exact format stored in the LDAP settingsUserNamefield, we can narrow down the last missing part.Best regards,
ABP Support Team
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) - The local