Blazor and Active Directory -Is getting it working here different than net core 5 MVC? And is that different that getting it working with Azure AD? - active-directory

I have past net core 5 MVC applcation that I am rewriting in Blazor server.
The code I have used in MVC to get it working includes the following:
public void GetADinfo(out string givenName, out string surname, out string homePhone, out string email)
{
//===========================================================
//Go and get AD info for the current user or equivalent
var components = User.Identity.Name.Split('\\');
var username = components.Last();
// create LDAP connection object
DirectoryEntry myLdapConnection = createDirectoryEntry();
DirectorySearcher search = new DirectorySearcher(myLdapConnection);
search.Filter = "(cn=" + username + ")";
SearchResult result = search.FindOne();
DirectoryEntry dsresult = result.GetDirectoryEntry();
givenName = dsresult.Properties["givenName"][0].ToString();
surname = dsresult.Properties["sn"][0].ToString();
email = dsresult.Properties["mail"][0].ToString();
homePhone = dsresult.Properties["homePhone"][0].ToString();
//=============================================================================
}
public DirectoryEntry createDirectoryEntry()
{
// create and return new LDAP connection with desired settings
string ADconn = _context.ApplicConfs.Select(s => s.Ldapconn).FirstOrDefault();
string LDAPConn = _context.ApplicConfs.Select(s => s.Ldappath).FirstOrDefault();
//string ADconn;
//ADconn = "SERVER.A3HR.local";
//string LDAPConn;
//LDAPConn = "LDAP://SERVER.A3HR.local";
//DirectoryEntry ldapConnection = new DirectoryEntry("SERVER.A3HR.local");
//ldapConnection.Path = "LDAP://OU=staffusers,DC=leeds-art,DC=ac,DC=uk";
//ldapConnection.Path = "LDAP://SERVER.A3HR.local";
DirectoryEntry ldapConnection = new DirectoryEntry(ADconn);
ldapConnection.Path = LDAPConn;
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
return ldapConnection;
}
Does the code for using Active Directory in Blazor Server require anything different? In other words is the authentication different using Blazor as compared to net core MVC?
My app uses Blazor server windows authentication. So I get current user in the normal Blazor way. When I get that I want to use the current userid to look up the email and telephone number in AD and pre-populate it on a page if it exists. That way - in the application- the user doesn't have to re enter this information all the time.
Does anyone have an example of this using Blazor? Is the approach dramatically different between local AD and Azure AD in the logic/coding used? I see a few examples of Azure AD use in Blazor out there.
Thanks for any information provided...

Related

Accessing Dynamics CRM via username/password throwing AdalServiceException: AADSTS65001

I followed the quickstart here: https://learn.microsoft.com/en-us/powerapps/developer/common-data-service/webapi/enhanced-quick-start
Which worked great, so then I need to register my app, so I followed this:
https://learn.microsoft.com/en-us/powerapps/developer/common-data-service/walkthrough-register-app-azure-active-directory
But now my unit tests give me the error:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException:
AADSTS65001: The user or administrator has not consented to use the
application with ID '[GUID]' named '[AppName]'. Send an interactive
authorization request for this user and resource.
I feel like I understand the error, that the administrator needs to consent. My program is doing some magic in the bakcgorund and the user is not signing in, it is using a set username and password and the user should not be consenting to anyone. Is there any way to set this consent permanently, or force it every time through the Helper class in the first tutorial? All my Google-fu came up empty... Thank you.
You can use something like this:
CrmserviceClient is from Microsoft.Xrm.Tooling.Connector nuget
private CrmServiceClient GenerateService()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.Expect100Continue = true;
ServicePointManager.CheckCertificateRevocationList = true;
ServicePointManager.DefaultConnectionLimit = 10;
var service = new CrmServiceClient(new Uri(organizationUrl), clientId, secret, false, string.Empty);
if (service.IsReady == false)
{
throw new Exception("CrmOrgService isn't ready. " + service.LastCrmError);
}
return service;
}
Or if you want to use connection string you can use this:
Connection string : https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/developer/xrm-tooling/use-connection-strings-xrm-tooling-connect
var connectionString =
ConfigurationManager.ConnectionStrings["XY"].ConnectionString;
var conn = new CrmServiceClient(connectionString);
IOrganizationService orgService = conn.OrganizationServiceProxy;

Get login user's Full Name when login to different domain windows authentication C#

We have problem like, unable to get the user full name when reading from different domain.
eg: My userName is dom1\jsmith and full name is John Smith. When we deploy the project in dom1 domain, we are able to login and get the full name of the user. When we deploy the project in another domain(dom2) where the user(dom1\jsmith) has login permission, able to access the site but not able to get the full name.
We tried different solutions but didn't work.
//output: dom1\jsmith
User.Identity.Name;
//output: dom1\jsmith
string s = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
//output: dom1\jsmith
string sUserName = System.Environment.UserName;
//output: John Smith in same domain but not able to find identity
using (var context = new PrincipalContext(ContextType.Domain))
{
var principal = UserPrincipal.FindByIdentity(context, User.Identity.Name);
if (principal != null)
var fullName = string.Format("{0} {1}", principal.GivenName, principal.Surname);
Pass the name of the logon domain into the constructor for PrincipalContext. So split the DOMAIN\username that you have, and use just the domain portion:
var split = User.Identity.Name.Split('\\');
using (var context = new PrincipalContext(ContextType.Domain, split[0])) {
...
}
Although I am surprised it doesn't work as you have it, since it works for me and I log into a different domain than what my computer is joined to. Although in my case, the two domains are in the same AD forest. Maybe that's not the case for you.

SQL AAD Token Based Authentication - Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON

Requirement - I am trying to connect to azure SQL DB from a asp.net MVC application and the connection type to azure SQL DB is "token based" and below are the set up done from my end.
a. Created an AAD application( ex : MTSLocal ) with certificate based authentication.
b. Added permission to the above AAD in SQL.
CREATE USER [MTSLocal] FROM external provider;
c.In code level I am trying to get a access token by using Client ID( obtained from step a.) and certificate and the resource I am connecting to is "https://database.windows.net". Please refer the sample code -
string authority = string.Format(System.Globalization.CultureInfo.InvariantCulture, "https://login.windows.net/{0}",
"xxxx.onmicrosoft.com");
var authContext = new AuthenticationContext(authority);
AuthenticationResult result = null;
result = await authContext.AcquireTokenAsync("https://database.windows.net", AssertionCert);
token = result.AccessToken;
d. I am able to retrieve the access token but when I am trying to open the SQL connection.I am getting the above said error.
sqlBuilder["Data Source"] = serverName;
sqlBuilder["Initial Catalog"] = databaseName;
sqlBuilder["Connect Timeout"] = 30;
string accesstoken = GetAccessToken();
using (SqlConnection connection = new SqlConnection(sqlBuilder.ConnectionString))
{
try
{
connection.AccessToken = accesstoken;
connection.Open();
}
catch (Exception ex)
{
}
}
Any help on this would be really helpful.
Here is some rough and ready code on how I solved this. I had to supply the host tenant (see in the code below.
private async Task<string> SqlServerVersion()
{
var provider = new AzureServiceTokenProvider();
var token = await provider.GetAccessTokenAsync("https://database.windows.net/", "<host tenant>.onmicrosoft.com").ConfigureAwait(false);
SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder
{
csb.DataSource = "<your server>.database.windows.net";
csb.InitialCatalog = "<your database>";
};
using (var conn = new SqlConnection(csb.ConnectionString))
{
conn.AccessToken = token;
await conn.OpenAsync().ConfigureAwait(false);
using (var sqlCommand = new SqlCommand("SELECT ##VERSION", conn))
{
var result = await sqlCommand.ExecuteScalarAsync().ConfigureAwait(false);
return result.ToString();
}
}
}
The Application Registered in the AAD should be added to the users list of the DB and respective roles should be given to DB USER.
For suppose the name of the App registered is "App_AAD_Register_Name". add this user to the corresponding DB like executing the below query. With this the user will be added to Principal Users list of the DB server.
CREATE USER [App_AAD_Register_Name] FROM EXTERNAL PROVIDER.
Create some generic Role like below
CREATE ROLE [RoleUser]
GO
GRANT SELECT ON SCHEMA :: dbo TO [RoleUser]
GO
GRANT INSERT ON SCHEMA :: dbo TO [RoleUser]
GO
Once Role is created and respective permissions are given, assign the role to the user created in the first step.
EXEC sp_addrolemember N'RoleUser', N'App_AAD_Register_Name'.
Once all these steps are done you will be able to connect to DB with the token.
These steps worked for me.

Active Directory - Cross Domains

My role on the developer side. I have an application that I am trying check to see if a user has access to a share. In the application, I check the groups on that share. Then I check all the groups the user is in.
In one case, I not able to see the Local group that the users is in both code or the AD in windows
For example:
Domain A\User1 > Domain A\Global Group > Do not see: Domain B\Local Group
But when I look from Domain B I see:
Share > Domain B\Local Group > Domain A\Global Group > Do not see Domain A\User1
Is there some security setting that is not set correctly since I dont see in the windows tool or code.
Update
I have tried the following code. I am still unable to to see Domain B\Local Group.
string account = "{User**Or**Group}";
string domain = "{Domain}";
string dn = ADHelper.GetDistinguishedName(domain, account);
using (var forest = Forest.GetCurrentForest())
{
foreach (Domain domainName in forest.Domains)
{
Console.WriteLine(string.Format("Domain: {0}", domainName.Name));
Console.WriteLine("========================================================");
GetAllGroups(dn, domainName.Name);
domainName.Dispose();
}
}
void GetAllGroups(string dn, string domain)
{
DirectorySearcher ds = new DirectorySearcher(string.Format("GC://{0}", domain));
ds.Filter = String.Format("(&(distinguishedName={0}))", dn);
SearchResult sr = ds.FindOne();
if (sr == null)
return;
DirectoryEntry Diruser = sr.GetDirectoryEntry();
Diruser.RefreshCache(new string[] { "tokenGroups" });
for (int i = 0; i < Diruser.Properties["tokenGroups"].Count; i++)
{
SecurityIdentifier sid = new SecurityIdentifier((byte[])Diruser.Properties["tokenGroups"][i], 0);
try
{
NTAccount nt = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(nt.Value + "\t" + domain);
}
catch { }
}
}
In order to retrieve all groups user belongs to you have to query one Global Catalog in each domain of the entire forest for the user's membership (user's tokenGroups attribute will return you nested groups as well), then remove duplicated groups.
Be aware that Active Directory cannot return more than 5K values of a single attribute in one query.
If a user belongs to more than 10K groups, then AD will return you only first 5K. You have to use technique called range retrieval to query for membership in that case.
Also, there may be some external trusted domains that you also have to handle.
Other solution is to use GetEffectiveRightsFromAcl function to calculate effective user permissions for the specified share.
The solution is described here
Note that you will need to pass SE_OBJECT_TYPE.SE_LMSHARE as and object type to the function.

Azure Graph service not finding newly created user

I have a web application that uses Azure ACS and Azure AD to handle our authentication.
We have a user management feature in the web application that allows a user to create new users. This takes the details such as username, password, email etc. and uses the graph service to create a user in azure.
var newUser = new Microsoft.WindowsAzure.ActiveDirectory.User
{
userPrincipalName = user.UserName,
mailNickname = user.MailNickname,
accountEnabled = true,
displayName = user.FirstName + " " + user.Surname,
givenName = user.FirstName,
surname = user.Surname
};
newUser.passwordProfile = new PasswordProfile
{
forceChangePasswordNextLogin = false,
password = user.Password
};
var graphService = GetGraphService(tenantName);
graphService.AddTousers(newUser);
graphService.SaveChanges();
We are then required to create a record in the web application database for this user. The record needs the object ID from azure. So we use the graphService to get the newly-created user details. This is where my problem lies. It doesn't find the user.
private string GetObjectIdFromAzure(string userName, string tenantName)
{
var graphService = GetGraphService(tenantName);
var users = graphService.users;
QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User> response;
response = users.Execute() as QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User>;
var user = response.FirstOrDefault(x => x.userPrincipalName == userName);
return user != null ? user.objectId : "";
}
My code was working without any issues for a few months and only today I am having issues. What frustrates me more it that I have another deployment of the same code where it works without any issues. Some differences between the two deployments are:
The deployments use different Access control namespaces in Azure
The deployments have separate applications in Azure AD
One is https, one is http
The users for both system are under the same Directory.
I have put in logging in both deployments to get the number of users returned by
users.Execute()
In both systems it reported 100 (they share the same users)
Any ideas of what would cause this to stop working? I didn't change any code relating to this recently, I haven't changed any configuration on Azure and I didn't change the web.config of the application
The problem was caused by the fact that I was filtering the users after retrieving them. The graph API was only returning a maximum of 100 users.
So the process was like so:
User created in Azure
Success message returned
Web App searches Azure for user to get Object ID
Graph Api only returns top 100 users. User was not in top 100 alphabetically so error thrown
The reason it was working on our second deployment was that I was prefixing the user name with demo_ (we use this site to demo new features before releasing). This meant that it was being returned in the top 100 users.
I changed the code as follows so it filters during the retrieval instead of after:
private Microsoft.WindowsAzure.ActiveDirectory.User GetUserFromAzure(string userName, string tenantName, out DirectoryDataService graphService)
{
graphService = GetGraphService(tenantName);
var users = (DataServiceQuery<Microsoft.WindowsAzure.ActiveDirectory.User>)graphService.users.Where(x => x.userPrincipalName == userName);
QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User> response;
response = users.Execute() as QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User>;
var user = response.FirstOrDefault();
return user;
}

Resources