PrincipalContext ValidateCredentials .NET framework - active-directory

I try to validate AD credentials using the next method call on a PrincipalContext object and it fails with the error:
The server cannot handle directory requests
Code:
// default ContextOptions per MSDN are Negotiate+Signing+Sealing
principalContext.ValidateCredentials(userName, password)
Then I tried to call the method where I put ContextOptions explicitly and it works:
principalContext.ValidateCredentials(userName, password, ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing)
According to MSDN the default ContextOptions for the first call are exactly the same as I provided in the second call. So, my expectations are that they either should both work or both fail.
Any idea why it behaves differently?
UPDATE:
Context is defined as the following for both cases:
var principalContext = new PrincipalContext(ContextType.Domain, "MyDomain");

Related

LDAP Error: The user has insufficient access rights. : LdapErr: DSID-0C09099D, comment: Error processing control,

I want to get incremental changes from Active Directory using C# and for that I am trying to build a solution as mentioned in the following article (using DirSync Control).
https://learn.microsoft.com/en-us/windows/win32/ad/polling-for-changes-using-the-dirsync-control
However, I am facing following problems:
When using following code, I am getting exception that The user has insufficient access rights. The user is part of administrators group.
What more permission needs to be given to that account? And how?
LdapConnection connection = new LdapConnection("adfs.fed.zzz.com");
connection.SessionOptions.ProtocolVersion = 3;
connection.Credential = new System.Net.NetworkCredential("adfsfed\\username", "password");
connection.AuthType = AuthType.Basic;
connection.Bind();
var filter = "(&(objectClass=*))";
var searchRequest = new SearchRequest("", filter, SearchScope.Subtree, properties);
DirSyncRequestControl dirSyncRC = new DirSyncRequestControl(null, DirectorySynchronizationOptions.None);
searchRequest.Controls.Add(dirSyncRC);
var response = connection.SendRequest(searchRequest) as SearchResponse;
If I am using below code, then I am not getting any exception but getting empty result in cookie.
String[] properties = { "objectGUID", "sAMAccountName", "displayName", "mail", "member" };
String filter = "(|(objectClass=group)(objectClass=user))";
DirectorySearcher directorySearcher = new DirectorySearcher(myLdapConnection, filter, properties);
var dSynch = new DirectorySynchronization(System.DirectoryServices.DirectorySynchronizationOptions.None);
directorySearcher.DirectorySynchronization = dSynch;
directorySearcher.SearchScope = System.DirectoryServices.SearchScope.Subtree;
var results = directorySearcher.FindAll();
var cookie = dSynch.GetDirectorySynchronizationCookie();
Considerations:
I have only one Domain Controller
I am system admin. So, I can assign appropriate permissions to the user.
Please help.
• Your user ID will need the "Replicating Directory Changes" permission and should be a member of ‘Domain Administrators’ group to use the DirSync LDAP control extension. But please note that it pretty much can read anything in the directory partition, regardless of standard permissions. Though they cannot change anything.
However - you may have some attributes that are sensitive in your directory. Please refer the powershell script in the below link and execute it with the user ID after giving appropriate permissions using C#. It is a dirsync code that will retrieve even attributes like ‘userAccountControl, userparameters, msexchuseraccountcontrol, pwdlastset, unicodePwd (BLANK, So no hashed domain password is returned), lockouttime, accountexpires, unixuserpassword(Its Hash is returned).
http://dloder.blogspot.com/2012/01/powershell-dirsync-sample.html
Based on the response given by #KartikBhiwapurkar-MT, I figured out the bug.
The error The user has insufficient access rights is completely misleading (User had already Replicating Directory Changes rights and was part of Domain Administrators group). The error was happening in System.DirectoryServices.Protocols is that I was passing out "" as first parameter (distinguishedName)
new SearchRequest("", filter, SearchScope.Subtree, properties);
but it should have been passed as
new SearchRequest("DC=adfs,DC=fed,DC=zzz,DC=com", filter, SearchScope.Subtree, properties);
I was getting empty cookie in System.DirectoryServices because of bug in latest nuget package (6.0.0). At the time of writing this answer, the bug is still open.
Reference to bug

Implement one general Authorization Service which should be called when I put Authorize attribute on it in multiple applications/APIs

Has anyone an idear what to use as a general Authorization Service and have an working code example or good implementation steps how to implement such of thing.
It takes a lot of time to look what I am after, but didn't found any satisfied solution yet.
IdentityServer is not an option, while my permissions can not be stored as claims, because of the size of the token. It comes with about 200 persmissions, so it should be done in a dbcontext or something.
I looked at the PolicyServer, but it wasn't working as I expected. When I installed it at the IS4 application, it works on the IS4 controllers, but when the Authorize is called from an external application, it doesn't call the Authorize override at all were it should check the permissions.
And it seems that the permissions aren't set in the external application either in the User.Claims or what so ever. I'm missing some settings I think.
What I want to accomplish is that I have one permissions store (table) (which for example contains a bunch of index, add, edit or delete button or what so ever). The should be given to the autheniticated user which is logged in. But this single persmission-store should be available at all applications or APIs I run, so that the Authorize attribute can do his job.
I think it shouldn't be so hard to do, so I'm missing a good working example how to implement something like this and what is working.
Who can help me with this to get this done?
I wrote some code to get the permissions by API call and use that in the IsInRole override. But when I declare it with the Authorize attr, it will not get in the method:
[ApiController]
1) [Authorize]
public class AuthController : ControllerBase
{
private readonly IdentityContext _context;
public AuthController(IdentityContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
[HttpGet()]
[Route("api/auth/isinrole")]
public bool IsInRole(string role)
{
2) if (User.FindFirst("sub")?.Value != null)
{
var userID = Guid.Parse(User.FindFirst("sub")?.Value);
if([This is the code that checks if user has role])
return true;
}
return false;
This is the IsInRole override (ClaimsPrincipal.IsInRole override):
public override bool IsInRole(string role)
{
var httpClient = _httpClientFactory.CreateClient("AuthClient");
3) var accessToken = _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken).Result;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var request = new HttpRequestMessage(HttpMethod.Get, "/api/auth/isinrole/?id=" + role);
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
etc...
This isn't working while it is not sending the access_token in the request
The 'sub' isn't send
Is always null
The open source version of the PolicyServer is a local implementation. All it does is read the permissions from a store (in the sample a config file) and transform them into claims using middleware.
In order to use the permissions you'll have to add this middleware in all projects where you want to use the permissions.
Having local permissions, you can't have conflicts with other resources. E.g. being an admin in api1 doesn't mean you are admin in api2 as well.
But decentralized permissions may be hard to maintain. That's why you probably want a central server for permissions, where the store actually calls the policy server rather than read the permissions from a local config file.
For that you'll need to add a discriminator in order to distinguish between resources. I use scopes, because that's the one thing that both the client and the resource share.
It also keeps the response small, you only have to request the permissions for a certain scope instead of all permissions.
The alternative is to use IdentityServer as-is. But instead of JWT tokens use reference tokens.
The random string is a lot shorter, but requires the client and / or resource to request the permissions by sending the reference token to the IdentityServer. This may be close to how the PolicyServer works, but with less control on the response.
There is an alternative to your solution and that is to use a referense token instead of a JWT-token. A reference token is just an opaque identifier and when a client receives this token, he has go to and look up the real token and details via the backend. The reference token does not contain any information. Its just a lookup identifier that the client can use against IdentiyServer
By using this your tokens will be very small.
Using reference token is just one option available to you.
see the documentation about Reference Tokens

Identity Server 4 Extension Grant without a Subject

I created a delegation extension grant the way they did in the docs. (https://identityserver4.readthedocs.io/en/latest/topics/extension_grants.html)
In the example, they get the user's identity from the claims and return the grant validation result like so:
var sub = result.Claims.FirstOrDefault(c => c.Type == "sub").Value;
context.Result = new GrantValidationResult(sub, GrantType);
My issue is that I don't always have a subject aka user identity when I need to utilize the delegation grant. In my scenario, I have an application listening to messages. When the app gets a message, it calls an API using client_credentials. That API then calls a sub API using the delegation grant type. Since the app is using client_credentials, there is no "sub" in the claims.
I tried checking if the "sub" claim exists and if not, set the subject of the GrantValidationResult to a "magical" guid which the IUserStore's FindByIdAsync would look for and either return null or a newed up empty TUser. In both cases, this causes Microsoft.AspNetCore.Identity to bomb futher down the pipeline.
How can I return a GrantValidationResult with the current claims, but not the subject when it doesn't exist?
I found this override for the GrantValidationResult.
// Summary:
// Initializes a new instance of the IdentityServer4.Validation.GrantValidationResult
// class with no subject. Warning: the resulting access token will only contain
// the client identity.
public GrantValidationResult(Dictionary<string, object> customResponse = null);
Since I don't have any custom responses, if "sub" is null, then I do this:
context.Result = new GrantValidationResult(new Dictionary<string, object>());
Doing it this way still populates the claims with the requested/validated scopes.

Identity Server 4 GetSchemeSupportsSignOutAsync returns incorrect response

I've setup an open id connect provider, Google in this case, using the AddOpenIdConnect extension method in dotnet core. From the discovery document:
https://accounts.google.com/.well-known/openid-configuration
it does not seem that google supports federated sign-out because there is no end_session endpoint. However, in Identity Server 4, the call:
var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
returns true. So during Logout it tries to sign out of google using:
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
which throws an exception:
InvalidOperationException: Cannot redirect to the end session endpoint, the configuration may be missing or invalid.
Is this a bug in Identity Server 4 or is there a configuration property that needs to be set when setting up the Oidc provider so that this extension method will pickup that the provider does not support signout?
Doesn't appear to be a bug in Identity Server 4. The code behind this extension calls out to get the underlying authentication scheme handler.
public static async Task<bool> GetSchemeSupportsSignOutAsync(this HttpContext context, string scheme)
{
var provider = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
var handler = await provider.GetHandlerAsync(context, scheme);
return (handler != null && handler is IAuthenticationSignOutHandler);
}
In this case, your handler will be OpenIdConnectHandler which appears to implement IAuthenticationSignOutHandler so that's why regardless of what is in the discovery document (end session endpoint supported or not), if you use the AddOpenIdConnect(...), it will always register a handler which seemingly supports sign out, but as you have pointed out, does not actually enforce the actual idp validation for that kind of functionality support (link to handler source).
And lastly, worthwhile to mention, that Identity Server 4 check is rightful here as according to Microsoft docs, the IAuthenticationSignOutHandler is indeed basically a marker interface used to determine if a handler supports SignOut.
So I guess you just simply can't use the generic AddOpenIdConnect(...), instead perhaps you should use AddGoogle(...) which does not implement IAuthenticationSignOutHandler so will work as expected with Identity Server 4 (link to source).
As Vidmantas Blazevicius mentioned, using .AddOpenIdConnect will make the extension method default to true because of the interface. I have changed my code to explicity check for the support of an end_session_endpoint by doing:
var discoveryClient = new IdentityModel.Client.DiscoveryClient("https://accounts.google.com/.well-known/openid-configuration")
{
Policy = new IdentityModel.Client.DiscoveryPolicy
{
ValidateEndpoints = false, //this is needed for google, if set to true then will result in error response
ValidateIssuerName = false //this is needed for Microsoft, if set to true then will result in error response
}
};
var discoveryResult = await discoveryClient.GetAsync();
if (!discoveryResult.IsError)
{
if (!String.IsNullOrWhiteSpace(discoveryResult.EndSessionEndpoint))
supportsFederatedSignOut = true;
}
I then save an additional property on the model "SupportsFederatedSignOut" and then use this to determine whether external identity provider signout (SignOut) should be called.

ValidateCredentials LDAP server unavailable

Currently I am using PrincipalContext class to call the ValidateCredentials method to check for user credentials. It was working fine with our 2 environment until a rather 'strange' error occurred.
var configuration = ConfigurationManager.GetSection("PrincipalContextConfiguration") as PrincipalContextConfigurationSection;
var principalContext = new PrincipalContext(configuration.ContextType, configuration.Name, configuration.Container);
principalContext.ValidateCredentials(userName, password);
configuration.ContextType = "Domain"
configuration.Name = "example.local"
configuration.Container = "CN=Users,DC=example,DC=local"
Above are the sample of our current code, simplified for easier viewing. As above, everytime we need to validate credential, we will create a new PrincipalContext.
The error that we have is this validate credential works fine if we provide a valid username and password. But for a specific machine, that throws this error, whenever invalid username and password is supplied, exception is thrown with a message "LDAP server is unavailable".
Could anyone point me to where I should start to find the root cause of this. It is strange to us that the method is only throwing that exception when username and password is incorrect. we verify this by using PowerShell to call the method.
And I am not that expert in AD. Thanks.

Resources