Why doesn't LogonUser(...) work for domain accounts? - windows

I've been trying to use LogonUser(...) to get an access token for a user account, as in this MSDN sample.
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(userName, domainName, Console.ReadLine(),
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
When I run the sample (with Administrator privileges) it works fine when given a domain of . and a local user account name and password, but no matter what I do I get error code 1326 (Logon failure: unknown user name or bad password) if I try to use a domain account. I get the same result if I enter garbage for the domain, which makes me wonder if it's actually contacting the DC at all.
What could be stopping this from working?

In my case the issue, similar to the question asker, was that the account I was trying to authenticate to was in a domain that my current machine did not belong to. Unlike the original poster, my machine should not and could not be part of this other domain. I wanted the login to perform action on a resource on this domain though.
The answer was the following
bool success = LogonUser(
userName,
domain,
password,
(int)LOGON32_LOGON_NEW_CREDENTIALS, //9
(int)LOGON32_PROVIDER_DEFAULT, //0
out userToken);
with the following constants defined:
public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
public const int LOGON32_PROVIDER_DEFAULT = 0;
Hopefully this will help others who are lost in a similar situation.
Edit: As mentioned in the comments below, this logon type allows the caller to clone its current token and specify new credentials for outbound connections. The new logon session has the same local identifier but uses different credentials for other network connections. As a result of that fact, "success" will return true even if the password is bad. You will need an additional check beyond "success" to confirm that the credentials are actually good.
This was not a concern in my initial use case as we used the current network user's credential in another function to pull the plaintext password from secure storage. So it would have never been wrong unless there was an inconsistency between that system and active directory in which case we had bigger problems.

In my case it was the fact that, although I was logged in to my computer as a domain user, my computer was not itself part of the domain. Once added to the domain the sample started to work.

Use DOMAIN\LOGIN with an empty domainname for that case...

Related

Does the credential manager have a limit for the number of credentials stored?

I'm using CredWriteW to store some credentials and persisting through the user session. As we will have to store the credentials for lots of different accounts, I'm wondering: is there some kind of limit as to how many credentials can be stored on the credential manager?
I found this doc Credential limit per app | Microsoft Learn , but it's not clear whether it applies only to RDP, or to Credential Manager in general, or to something else. I've tried searching for this limit, but everything seems to point back to that same doc.
This is the code for reference:
CREDENTIAL credential = {0};
credential.Type = CRED_TYPE_DOMAIN_PASSWORD;
credential.TargetName = account;
credential.CredentialBlobSize = credentialBlobSize;
credential.CredentialBlob = (LPBYTE)password;
credential.Persist = CRED_PERSIST_SESSION;
credential.UserName = (LPWSTR)userName;
// Write the credential in the user space
if (!CredWriteW(&credential, 0))
{
// ...
}
This API indirectly mentioned/used in your RDP/Remote Deskop link is called "Vault", it's not the same API that the one used by CredWrite.
Vault is an undocumented API. See here for example on SO: Reverse engineering the function arguments of VaultRemoveItem or here on github's mimikatz
CredWrite is not documented to have any reasonable limit, here are 100 credentials I've just created with it:

How to prevent multiple login to the same account in ASP.NET Core

I'm using ASP.NET Core 6 Identity for login system. I want to prevent multiple login to the same account.
My Identity settings are:
//For custom Identity
string connection = configuration.GetConnectionString("DefaultConnection");
builder.Services.AddIdentity<AppUser, AppRole>(options =>
{
options.User.RequireUniqueEmail = false;
options.Password.RequireDigit = true;
options.Password.RequireUppercase = false;
options.Password.RequiredUniqueChars = 0;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 6;
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultProvider;
}).AddEntityFrameworkStores<IdentityAppContext>().AddDefaultTokenProviders();
I searched some similar questions but none of them could help me to implement this feature in ASP.NET Core 6.
Please guide me.
What is the Use Case for denying access while logged in?
"When someone logged in, no body can log in to that account until he closes the browser or logs out manually"
That would require logic like:
On Login, throw error if tokenHasBeenIssued, by querying the server db.
On Login, if no server token for user, createToken.
On Logout, a clean db, removeUserToken
but, when someone closes their browser there is no message sent to the server, so you'd never clear the token, so you'd get one login granted and then they would be logged out forever.
Maybe this scenario is fixable with a hack of a 'Timed cron job to clear all old tokens'?
I would suggest implement two factor authentication or even delegate your auth needs to third party provider, eg Auth0 or Azure AD, etc.
If you mean to stay signed in, you need to implement a token(for example, JWT Token) and use User ID or username directly without logging in again.

Stop Auto Logon after failure - Custom Credential Provider Windows

I've taken up Widows Samples on Credential Providers and have built one using them as a reference. I'm able to log in seamlessly, wither by giving username and password manually. I've set
CustomCredential::SetSelected (__out BOOL* pbAutoLogon) {
*pbAutoLogon = TRUE; // FALSE;
return S_OK;
}
Now, AutoLogon, when the Tile is selected, is happening seamlessly.
As a test case, I changed the password, and as expected the Login fails. After failure, an error message appears and when I click OK ( this is the only option ), the credentials are resubmitted for a retry. How do can we stop this behavior? Which method gets called after the authentication failure?
I've handled ReportResult() but that did not help.
Thanks in advance.
It's been a while - but I believe GetSerialization() is called to return serialized credentials to LogonUI. You need to implement this as well. The credential provider samples I think have working code for KERB_INTERACTIVE_UNLOCK_LOGON.
You can also change *pbAutoLogon conditionally in SetSelected() - I do this in my credential provider depending on certain results.
ReportResult() would be called after GetSerialization returns its result to LogonUI. Inside ReportResult() you could do things like clear the password box (which is done in the samples code.)
If you're not returning serialized credentials in GetSerialization then I think you might get the kind of error you listed in your original post. In the credential samples KerbInteractiveUnlockLogonPack() is called in GetSerialization() and this is what 'logs' the user in effectively.
The error seems specific - 'the user has not been granted the requested logon type' so maybe it has something to do with the rights of the user you're testing with.
If you are using remote desktop, make sure your users are members of the right groups to be able to login (https://support.jumpdesktop.com/hc/en-us/articles/216424183-General-RDP-You-must-be-granted-the-Allow-log-on-through-the-Terminal-or-Remote-Desktop-Services-Right-) or if it is a regular user that it is allowed interactive login.
Also - SetSelected() Gets called when your credential provider is clicked on - I'm not sure if it gets called after every logon attempt or not (my guess is that it doesn't.) In my credential provider I am using a custom logon dialog that I show using SetSelected().

Is there a way to revoke another user's access tokens and end their session in Identity Server 4?

Is there a recommended way to revoke another user's access in Identity Server 4? The use case I'm looking at is an Administrator revoking system access for a currently logged in user.
I've read the documentation for the Revocation Endpoint and can see how that can be used by a user to revoke their own access. But how can this be done when the Administrator wouldn't know what a particular user's access token is?
Same goes for the End Session Endpoint I suppose, how would the Admin know their ID Token?
What I've tried so far is implementing an IProfileService and checking the user's account is valid in the IsActiveAsync method. In our customer db I can deactivate their account and this has the desired effect of redirecting them to to the Login page. But the tokens and session are still 'alive'. Would this be a good place to end session and revoke access token?
Or is persisting user tokens to the database an option?
Update
Based on the answer from #Mashton below I found an example of how to implement persistence in the Identity Server docs here.
Creating the data migrations described there will persist tokens to [dbo].[PersistedGrants] in the Key column. I was confused at first since they didn't look anything like my reference access tokens but after a little digging I found that they are stored as a SHA-256 hash. Looking at the DefaultGrantStore implementation in Identity Server's GitHub the Hashed Key is calculated as follows ...
const string KeySeparator = ":";
protected string GetHashedKey(string value)
{
return (value + KeySeparator + _grantType).Sha256();
}
... where the value is the token and the _grantType is one of the following ...
public static class PersistedGrantTypes
{
public const string AuthorizationCode = "authorization_code";
public const string ReferenceToken = "reference_token";
public const string RefreshToken = "refresh_token";
public const string UserConsent = "user_consent";
}
Using persisted grants doesn't give me the original access token but it does allow me the ability to revoke access tokens since the [dbo].[PersistedGrants] table has the SubjectId.
Update 2 - Identity Server keeps creating tokens
I created an implicit mvc client and after successful login I'm dumpimg the claims on the screen. I delete the access token from the persisted grant db then use Postman to end the session in the End Session Endpoint (using the id token in the claims). When I refresh the browser I'd expect the user to get redirected to the login screen but instead they get a new access token and a new id token. The Client.IdentityTokenLifetime is only 30 seconds.
Any ideas of what I'm missing here?
You can only revoke Reference tokens not JWTs, and yes those need to be stored in a db. Have a look at the IPersistedGrantStore (of the top of my head, so may have got the name wrong), and you'll see the structure is pretty simple.
Once you've got them stored, you can obviously do anything you like admin-wise, such as change the expiry or just outright delete them.

What is the native equivalent of PrincipalContext.ValidateCredentials?

What is the native way to validate a set of user domain credentials (username, password, domain) against that domain?
In other words, i am looking for the native equivalent of:
Boolean ValidateCredentials(String username, String password, String domain)
{
// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain))
{
// validate the credentials
return pc.ValidateCredentials(username, password)
}
}
ValidateCredentials("iboyd", "Tr0ub4dor&3", "contoso");
Hasn't this been asked, and answered, to death?
No! This question is asked, a lot. Three of those times by me. But the more you dig into it, the more you realize the accepted answers are incorrect.
Microsoft managed to solve it in .NET with the PrincipalContext class added in .NET 3.5. And the PrincipalContext is nothing magical. Underneath it uses the flat C-style ldap API. But trying to reverse engineer the code from ILSpy is not working out. And referencesource notwithstanding, Microsoft still keeps large portions of the .NET Framework class library source code secret.
What have you tried?
Method 1: Just use LogonUser
I cannot use LogonUser. LogonUser works only when your machine is either on the domain you're validating (e.g. contoso) or your domain trusts the domain you're validating. In other words, if there is a domain controller out there for the contoso.test domain then:
LogonUser("iboyd", "contoso.test", "Tr0ub4dor&3",
LOGON32_LOGON_NETWORK, "Negotiate", ref token);
will fail with error:
1326 (Logon failure: unknown user name or bad password)
That's because this domain i specify is not my own domain, or a domain i trust.
C# PrincipalContext doesn't suffer from this problem.
Method 2: Just use the SSPI (Security Support Provider Interface)
The SSPI is what LogonUser uses internally. The short answer is that it fails for the same reason that LogonUser does: Windows will not trust credentials from an untrusted domain.
The code is pretty long to provide an example of. The psuedo-code jist is:
QuerySecurityPackageInfo("Negotiate");
// Prepare client message (negotiate)
AcquireCredentialsHandle(....); //for the client
InitializeSecurityContext(...); //on the returned client handle
CompleteAuthToken(...); //on the client context
// Prepare server message (challenge).
AcquireCredentialsHandle(...); //for the server
AcceptSecurityContext(...); //on the returned server handle
CompleteAuthToken(...); //on the server context
// Prepare client message (authenticate).
AcquireCredentialsHandle(....); //for the client
InitializeSecurityContext(...); //on the returned client handle
CompleteAuthToken(...); //on the client context
// Prepare server message (authentication).
AcquireCredentialsHandle(...); //for the server
AcceptSecurityContext(...); //on the returned server handle
CompleteAuthToken(...); //on the server context
This code works great if your machine is joined to the domain you're validating credentials. But as soon as you try to validate a set of domain credentails from a foreign domain: it fails.
C# PrincipalContext doesn't suffer from this problem.
Method 3: Just use LDAP's AdsGetObject
Some might suggest using AdsGetObject.
AdsGetObject("LDAP://CN=iboyd,DC=contoso,DC=test");
That's a red-herring, because AdsGetObject supports no way to pass username/password:
HRESULT ADsGetObject(
_In_ LPCWSTR lpszPathName,
_In_ REFIID riid,
_Out_ VOID **ppObject
);
Instead you will be simply asking about a user.
Perhaps you meant AdsOpenObject:
HRESULT ADsOpenObject(
_In_ LPCWSTR lpszPathName,
_In_ LPCWSTR lpszUserName,
_In_ LPCWSTR lpszPassword,
_In_ DWORD dwReserved,
_In_ REFIID riid,
_Out_ VOID **ppObject
);
where you can specify credentials to connect as.
C# PrincipalContext doesn't suffer from this problem.
Method 4: Just use AdsOpenObject
Some might suggest using AdsOpenObject:
String path = "LDAP://CN=iboyd,DC=contoso,DC=test"
AdsOpenObject(path, "iboyd", "Tr0ub4dor&3", 0, IADs, ref ads);
Setting aside the fact that the path i constructed is invalid, setting aside the fact that there is no way to construct a valid LDAP path when you only know:
{username}
{password}
{domain}
that is because
LDAP://CN={username},DC={domain}
is not a valid LDAP path for any user.
Notwithstanding the LDAP path conundrum, the fundamental issue is that trying to query LDAP. That is wrong.
We want to validate a user's AD credentials.
Any time we attempt to query an LDAP server, we will fail if the user does not have permission to query LDAP - even though their credentials are valid.
When you pass credentials to AdsOpenObject they are used to specify who you want to connect as. Once you connect, you will then perform a query against LDAP. When you don't have permission to query LDAP, the AdsOpenObject will fail.
What's even more maddening is that even if you do have permission to query for users in LDAP, you're still needlessly performing a query of LDAP - an expensive operation.
C# PrincipalContext doesn't suffer from this problem.
Method 5: Use ADO with the ADsDSOObject provider
There are many who simply use the ADsDSOObject OLEDB provider with ADO to query LDAP. This solves the issue of having to come up with the correct LDAP path - you don't have to know the LDAP path for the user
String sql =
'SELECT userAccountControl FROM "LDAP://DC=contoso,DC=test"
'WHERE objectClass="user"
'and sAMAccountName = "iboyd"';
String connectionString = "Provider=ADsDSOObject;Password=Tr0ub4dor&3;
User ID=iboyd;Encrypt Password=True;Mode=Read;
Bind Flags=0;ADSI Flag=-2147483648";
Connection conn = new ADODB.Connection();
conn.ConnectionString = connectionString;
conn.Open();
IRecordset rs = conn.Execute(sql);
That works; it solves the problem of not knowing a user's LDAP path. But it doesn't solve the issue of if you don't have permission to query AD, then it fails.
Plus there's the issue that it is querying Active Directory, when it should be validating credentials.
C# PrincipalContext doesn't suffer from this problem.
Method 6: Just use PrincipalContext.ValidateCredentials
The .NET 3.5 class PrincipalContext has that lets you validate credentials knowing only:
Username
Password
Domain Name
You don't need to know the name or IP of the AD server. You don't need to construct any LDAP paths. And most importantly, you don't need permission to query Active Directory - it just works.
I tried digging down into the source code using ILSpy, but it gets harry fast:
ValidateCredentials
CredentialValidator.Validate
BindLdap
new LdapDirectoryIdentifier
new LdapConnection
ldapConnection.SessionOptions.FastConcurrentBind();
lockedLdapBind
Bind
With a lot of presumably important code around it. There's a lot of ups, and downs, with dependency injection, and functions too little - all the normal difficulties you get with overly complicated code structure. The complexity is on par with programming the SSPI. Nobody understands SSPI code, and i already wrote code that calls it!
Note: This question doesn't ask how to validate local credentials, as opposed to local credentials. Nor does it ask how to do both. In this case i'm simply asking how to do what is already available in the .NET world but in the native world.
Unfortunately:
System.Security.DirectoryServices.AccountManagement.PrincipalContext
was not exposed though a COM-callable wrapper:
And now that i've spent two and a half hours typing in this question: it is time to go home. Lets see if i get closed between now and tomorrow morning.
What about the LogonUser functions from advapi.dll, e.g. LogonUserA:
BOOL LogonUserA(
[in] LPCSTR lpszUsername,
[in, optional] LPCSTR lpszDomain,
[in, optional] LPCSTR lpszPassword,
[in] DWORD dwLogonType,
[in] DWORD dwLogonProvider,
[out] PHANDLE phToken
);
LogonUser(L"LocalService", L"NT AUTHORITY", NULL, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &hToken)
This logs in against the AD on the local machine.

Resources