I m trying to authenticate to a server with Active Directory using Nodejs. I read a lot a of answers of that but no body use the sAMAccountName as attribute to do that.
My code is :
let ActiveDirectory = require('activedirectory');
let config = {
url: 'ldap://XX.XX.XX.XXX:389',
baseDN: 'DC=domain,DC=com',
username: 'serverUserName',
password: 'serverPsw'
};
let ad = new ActiveDirectory(config);
let username = 'sAMAccountName=11111111111, OU=Usuarios, DC=domain,DC=com',
password = 'myPassword';
ad.authenticate(username, password, function (err, auth) {
if (err) {
console.log('ERROR: ' + JSON.stringify(err));
return;
}
if (auth) {
console.log('\nAuthenticated!', auth);
}
else {
console.log('\nAuthentication failed!');
}
});
This code only works if a change the username
'CN=myIdentifier,OU=Usuarios,DC=domain,DC=com'
and use the CN as attribute.
I really don't have idea what to do but it must work using the sAMAccountName.
Can anybody give me a help of how to make it work?
Thanks
You can authenticate against Active directory LDAP using sAMAccountName if you include the domain too - like company\logonid instead of just login ID. You can also use the full userPrincipalName like logonid#company.ccTLD
With either of those formats, you do not use any of the LDAP syntax components (something= or the ,ou=xyz,dc=abc). You use just the login ID and domain name (in the example above, the legacy style domain name is company and the AD style domain name is company.ccTLD)
Related
I gonna write a API which get a Username and password from the Front-End. This username and password get passed into my LDAP bind and should get checked if these informations are correct. Now I got the problem when the use types a wrong password I get the correct error code 49 for invalid credentials. But when the user just enter his username and nothing into password, then LDAP is automatically accepting it and passes through the authentication.
Can maybe someone help me or give me an advice whats wrong?
const ldap = require('ldapjs');
var client = ldap.createClient({
url: `ldap://${process.env.LDAP_HOST}:${process.env.LDAP_PORT}`
});
function ldapauth(dn, password, callback) {
var serverStatus;
//dn = entry.object.dn from another ldap query
client.bind(dn, password, function(err, res) {
if(err) {
console.log(['Error:',err.code, err.dn, err.message]);
serverStatus = err.code;
client.unbind();
return callback (serverStatus);
} else {
console.log('Auth Status: ' + res.status);
if(res.status == 0) {
serverStatus = res.status;
} else {
serverStatus = 500;
}
client.unbind();
return callback(serverStatus);
};
});
}
This is my output which I get when the password is empty
The activedirectory package solves this by returning an error if no password is supplied:
if ((! username) || (! password)) {
var err = {
'code': 0x31,
'errno': 'LDAP_INVALID_CREDENTIALS',
'description': 'The supplied credential is invalid'
};
return(callback(err, false));
}
Here is the code: https://github.com/gheeres/node-activedirectory/blob/master/lib/activedirectory.js#L1803
More info about the bind command: https://ldap.com/the-ldap-bind-operation/
This sounds like is the cause of your issue:
An anonymous simple bind can be performed by providing empty strings as the bind DN and password (technically, the LDAPv3 specification states that only the password must be empty, but this has been responsible for many security problems with LDAP clients in the past, and many servers require that if an empty password is provided then an empty DN must also be given).
There are two Active Directory (LDAP Servers). Following are the users which belongs to their servers respectively.
Server user password
1- abc.pk user_abc#abc.pk ********
2- xyz.com.pk user_xyz#xyz.com.pk ********
I am authenticating the user in NodeJS with library (ActiveDirectory). Below is my code where I am authenticating user_xyz#xyz.com.pk from its respective server.
const ActiveDirectory = require('activedirectory');
var ad = new ActiveDirectory({
"url": "ldap://xyz.com.pk",
"baseDN": "DC=xyz,DC=com,DC=pk"
});
ad.authenticate(username, password, function(err, auth) {
console.log('auth function called with username: '+username);
if (err) {
console.log('auth function called and with following err '+JSON.stringify(err));
return;
}
if (auth) {
console.log('Authenticated from Active directory!');
});
it works fine. Same works fine if I authenticate user_abc#abc.pk from server 1 by updating the url and baseDN.
var ad = new ActiveDirectory({
"url": "ldap://abc.pk",
"baseDN": "DC=abc,DC=pk"
});
Server abc.pk has Trust Relations with Server xyz.com.pk. Means I have to authenticate the user user_abc#abc.pk from the Server xyz.com.pk . using the following configurations.
var ad = new ActiveDirectory({
"url": "ldap://xyz.com.pk",
"baseDN": "DC=xyz,DC=com,DC=pk"
});
but now facing the error of invalid credentials. This is the exact error I am facing {"lde_message":"80090308: LdapErr: DSID-0C090453, comment: AcceptSecurityContext error, data 52e, v3839\u0000","lde_dn":null}
If I authenticate the user_abc#abc.pk from xyz.com.pk Server with Active Directory Explorer it works fine.
Active Directory Explorer image
It would be a great help if someone could give me a solution.
Thanks
I solved the problem by checking the following 2 things:
1.-The configuration must be separated in the baseDN part:
var config = {
url: 'ldap://aaa.bbb.ccc.ddd',
baseDN: 'DC=aaa,DC=bbb,DC=ccc,DC=ddd'
};
2.-It seems the problem is not from code https://community.arubanetworks.com/community-home/digestviewer/viewthread?MID=40296#:~:text=%22AcceptSecurityContext%20error%2C%20data%2052e%22,instead%20of%20just%20the%20username.
According to the post, sometimes the domain name server may be required for authentication. It would be necessary to verify if it works with "username" or "username#aaa.bbb.ccc.ddd" or "aaa.bbb.ccc.ddd\username" depending on how the user is registered.
I hope my experience can be of use. Cheers
The customer has created a key vault and store the credentials . To authenticate key vault , I have created the application in the node and using client id and client secret , I am able to read the secrets. But now the customer wants not to use the client id and client secret , instead use the username and password of the AZURE to access the keyvault in the program. Its one dedicated user for the keyvault access with no MFA.
I am not sure if we can access the keyvault with username and password from the node js. Kindly suggest.
Thanks
For this requirement, I also think that use username-password flow is unnecessary and client credential flow should be better (as juunas mentioned in comments). But if the customer still want to use username-password flow to implement, I can provide a sample as below for your reference:
1. You should register an app in AD with native platform but not web platform.
And please check if "Treat application as a public client" is enabled.
If your app is web platform, when you run the node js code it will show error message to ask you provide "client secret" even if you use username-password flow.
2. You need to add the azure key vault permission to your app.
And do not forget grant admin consent for it.
3. Then you can refer to the code below to get the secret value.
const KeyVault = require('azure-keyvault');
const { AuthenticationContext } = require('adal-node');
const clientId = '<clientId>';
const username = '<username>';
const password = '<password>';
var secretAuthenticator = function (challenge, callback) {
var context = new AuthenticationContext(challenge.authorization);
return context.acquireTokenWithUsernamePassword(challenge.resource, username, password, clientId, function(
err,
tokenResponse,
) {
if (err) throw err;
var authorizationValue = tokenResponse.tokenType + ' ' + tokenResponse.accessToken;
return callback(null, authorizationValue);
});
};
var credentials = new KeyVault.KeyVaultCredentials(secretAuthenticator);
var client = new KeyVault.KeyVaultClient(credentials);
client.getSecret("https://<keyvaultname>.vault.azure.net/", "<secret name>", "<secret version>", function (err, result) {
if (err) throw err;
console.log("secret value is: " + result.value);
});
I am trying to create a dropdown with all the users in my Office365 tenant. I created an app in Azure AD and gave it all the necessary permissions. I gave it all the permissions for Microsoft Graph actually, app and delegated. All of them.
Then I wrote up my script to query all users with https://graph.microsoft.com/v1.0/users.
I had my tenant admin go in and accept the permissions then output the list of users in the UI. Works fine for the admin
I'm not an admin but when I go to the page I get the following error:
This application requires application permissions to another
application. Consent for application permissions can only be performed
by an administrator. Sign out and sign in as an administrator or
contact one of your organization's administrators.
I need to know if this will work for users with even lower permissions. From what I understand the API request and the App is running under the permissions given to the application in Azure. So even if the user as Read Only, the request isn't running under the user, it's running under the Application I set up. So why would I get the error regarding permissions?
This is the code I'm using:
(function () {
"use strict";
// Some samples will use the tenant name here like "tenant.onmicrosoft.com"
// I prefer to user the subscription Id
var subscriptionId = "metenant.onmicrosoft.com";
// Copy the client ID of your AAD app here once you have registered one, configured the required permissions, and
// allowed implicit flow https://msdn.microsoft.com/en-us/office/office365/howto/get-started-with-office-365-unified-api
var clientId = "cccb1f2f-xxx-x-xxxxx-x-x-x-x-x-";
window.config = {
// subscriptionId: subscriptionId,
clientId: clientId,
postLogoutRedirectUri: window.location.origin,
endpoints: {
graphApiUri: 'https://graph.microsoft.com'
},
cacheLocation: 'localStorage' // enable this for IE, as sessionStorage does not work for localhost.
};
var authContext = new AuthenticationContext(config);
// Check For & Handle Redirect From AAD After Login
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
// If not logged in force login
var user = authContext.getCachedUser();
// NOTE: you may want to render the page for anonymous users and render
// a login button which runs the login function upon click.
if (!user) authContext.login();
// Acquire token for Files resource.
authContext.acquireToken(config.endpoints.graphApiUri, function (error, token) {
// Handle ADAL Errors.
if (error || !token) {
console.log('ADAL error occurred: ' + error);
return;
}
// Execute GET request to Files API.
var filesUri = config.endpoints.graphApiUri + "/v1.0/users";
$.ajax({
type: "GET",
url: filesUri,
headers: {
'Authorization': 'Bearer ' + token,
}
}).done(function (response) {
console.log('Successfully fetched from Graph.');
console.log(response);
var container = $(".container")
container.empty();
$.each(response.value, function(index, item) {
container.append($('<li>').text(item.displayName + " " + item.mail + " " + item.mobilePhone))
})
}).fail(function (response) {
var err = JSON.parse(response.responseText)
console.log('Failed:', err.error.message);
});
});
})();
There are two kinds of permission/scope for Microsoft Graph. One is that require administrator’s consent. The other is not required.
What’s the permission you were config for this app? To list the users without administrator’s consent, we can use the scope User.ReadBasic.All like figure below:
You can get more detail about the permission/scope from here.
Modify:
At present, the adal.js doesn’t provide the admin consent. If you want to use this feature, you can modify the code to add a prameter like below:
AuthenticationContext.prototype.login = function (prompt) {
// Token is not present and user needs to login
var expectedState = this._guid();
this.config.state = expectedState;
this._idTokenNonce = this._guid();
this._logstatus('Expected state: ' + expectedState + ' startPage:' + window.location);
this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, window.location);
this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, '');
this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, expectedState);
this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce);
this._saveItem(this.CONSTANTS.STORAGE.FAILED_RENEW, '');
this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
var urlNavigate = this._getNavigateUrl('id_token', null) + '&nonce=' + encodeURIComponent(this._idTokenNonce);
if (prompt && prompt === "admin_consent") {
urlNavigate = urlNavigate + "&prompt=admin_consent"
}
this.frameCallInProgress = false;
this._loginInProgress = true;
if (this.config.displayCall) {
// User defined way of handling the navigation
this.config.displayCall(urlNavigate);
} else {
this.promptUser(urlNavigate);
}
// callback from redirected page will receive fragment. It needs to call oauth2Callback
};
And if you were using Angular, we also need to modify the adal-angular.js:
this.$get = ['$rootScope', '$window', '$q', '$location', '$timeout', function ($rootScope, $window, $q, $location, $timeout) {
...
return {
// public methods will be here that are accessible from Controller
config: _adal.config,
login: function (prompt) {
_adal.login(prompt);
},
...
}
Then we can provide two button for users login in. One button is for the users sign-in with themselves. And the other is for admin to give the consent for the organization. Here is the code redirect to the login page for the admin consent in the control of Angular:
$scope.login = function () {
adalService.login("admin_consent");
};
I am using a library to authenticate LDAP Users, whose code is as follows:
public void authUser(String username, String pwd)
throws Exception
{
try
{
Properties env = getEnvironmentForContext();
env.put("java.naming.security.principal", "uid=" +
username + ",ou=users, dc=company"));
env.put("java.naming.security.credentials", pwd);
context = getContext(env);
System.out.println("Authentication Succeeded");
}
catch (Exception e)
{
System.out.println("Authentication Failed");
throw e;
}
}
Please note, i cannot modify the above Authentication Code. It comes from a external Library.
But, i want to deactivate some users (not delete them), so that Authentication Fails.
I am using LDAP (not Active Directory). Do not know what LDAP Software it is though, i can connect to it using 'LDAP Browser Client'.
The users exist under: dc=company, ou=users, uid=username
What attribute can i add/change on LDAP 'user' to de-activate a user.
Could i move the user to a different group like: dc=company, ou=deactivatedusers, uid=username? But this is not the preferred option, plus am not sure best way to do that.
EDIT: The LDAP being used is: Netscape/Sun/iPlanet
To answer your question per the Oracle iPlanet (Sun) documentation :
Setting the attribute nsAccountLock to true will disable a users account, and prevent them from binding to the directory.
However, in terms of the code you already have, I just don't see any way of accomplishing this... Is there something preventing you from writing your own implementation for iPlanet using the System.DirectoryServices.Protocols namespace in .Net?
Here is how I bind and authorize users against an iPlanet server :
//Build servername from variables
var BuildServerName = new StringBuilder();
BuildServerName.Append(ServerName);
BuildServerName.Append(":" + Convert.ToString(Port));
var ldapConnection = new LdapConnection(BuildServerName.ToString());
//Authenticate the Admin username and password, making sure it's a valid login
try
{
//Pass in the network (administrative) creds, and the domain.
var networkCredential = new NetworkCredential(Username, Password, config.LdapAuth.LdapDomain);
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
ldapConnection.AuthType = AuthType.Anonymous;;
ldapConnection.Bind(networkCredential);
//Lets find this person so we can use the correct DN syntax when we authorize them.
SearchRequest FindThem = new SearchRequest();
FindThem.Filter = config.LdapAuth.LdapFilter.Replace("{{Patron}}", Patron);
FindThem.DistinguishedName = config.LdapAuth.LdapDomain;
FindThem.Scope = System.DirectoryServices.Protocols.SearchScope.Subtree;
//We'll execute a search using the bound user
SearchResponse searchresults = (SearchResponse) ldapConnection.SendRequest(FindThem);
//Should only get on result back, if not throw an error
if(searchresults.Entries.Count == 1)
{
SearchResultEntryCollection entries = searchresults.Entries;
SearchResultEntry thispatron = entries[0];
PatronDN = thispatron.DistinguishedName;
}
}
If you wanted to move disabled users to a specific group, from this point you could write logic to check the DistinguishedName of that user, and throw a handled exception if their DistinguishedName contains the name of that group. Also, if the nsAccountLock attribute is available to your binding account as a readable attribute, you could just check the value of that attribute for true, and handle the user accordingly.
Here is the java code for disabling and enabling user in Active Directory using JNDI.
Make sure to connect with your AD before calling below code.
public void disableEnableUser() throws Exception {
ModificationItem[] mods = new ModificationItem[1];
//To enable user
//int UF_ACCOUNT_ENABLE = 0x0001;
//mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl",Integer.toString(UF_ACCOUNT_ENABLE)));
// To disable user
int UF_ACCOUNT_DISABLE = 0x0002;
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl",Integer.toString(UF_ACCOUNT_DISABLE)));
ctx.modifyAttributes("CN=John ABC,OU=Users,OU=anyone,DC=yourcompanyname,DC=com", mods);
}
Distinguished name = "CN=John ABC,OU=Users,OU=anyone,DC=yourcompanyname,DC=com"
This name is depend on your structure of Active Directory, you can confirm from your suport team.
If the directory software supports a password policy feature, it probably provides attributes to lock/deactivate the user. If not, you can simply nullify the password attribute (e.g., userpassword). The LDAP server should return the "inappropriate authentication" error to the client when the authenticated bind is performed.
You could just change the user's password. If it's OpenLDAP with the password-policy overlay, or another LDAP server that supports locking, you can lock the user as well. You really will have to find out.