I have the following terraform config.
# Terraform Block
terraform {
required_version = ">= 1.0.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.0"
}
}
}
# Provider Block
provider "azurerm" {
features {}
}
# Datasources
# https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/client_config
data "azuread_client_config" "current" {}
output "object_id" {
value = data.azuread_client_config.current.object_id
}
output "client_id" {
value = data.azuread_client_config.current.client_id
}
output "tenant_id" {
value = data.azuread_client_config.current.tenant_id
}
When I plan and then apply, I get the following output.
Outputs:
client_id = "04b07795-8ddb-YYYY-bbee-XXXXXXXXXXXX"
object_id = "3603657a-34b8-YYYY-b7df-XXXXXXXXXXXX"
tenant_id = "35b02984-c026-YYYY-8cb3-XXXXXXXXXXXX"
I could figure out where object_id and tenant_id come from. See below, I have screen shots to show.
But I could not figure out client_id source.
Here is where object_id and tenant_id come from.
First the object id.
And now the tenant id.
So the question again, what is this client id and where does this come from?
Update
I dont see anything in the enterprise applications tab of the AD. What am I missing?
I checked the clientId that resulted from the below output
data "azuread_client_config" "current" {}
output "object_id" {
value = data.azuread_client_config.current.object_id
}
output "client_id" {
value = data.azuread_client_config.current.client_id
}
output "tenant_id" {
value = data.azuread_client_config.current.tenant_id
}
If I check with that Id in azure Ad applications, it the Microsoft Azure CLIs applicationId
This client ID or (application ID) is the appId of the one which
used to authenticate to ( i.e; the application used for delegated
authentication.) or is linked to the authenticated principal.
As in azure to deploy Terraform configurations , we need to complete
authentication.
Terraform can actually use the current account logged into Azure CLI
for authentication which is my case.
Reference: client_config#attributes-reference | registry.terraform.io
Related
I'm trying to add optional claims using Microsoft Identity Web - NuGet for user authentication in NET Core 3.1 WebApp. Reading the MS Docs, it seems that the only steps needed are to declare the optional claims within the App Registration Manifest file in Azure. But when testing the login process using two different apps (my own code and an MS project example) it looks like the optional claims are not being added to the ID Token when returned from Azure following a successful login i.e they're not present at all when viweing the token details in Debug.
I'm not sure how to diagnose this and where to trace the issue i.e am I missing any required steps in Azure setup?
Side Note: Just to confirm it is the jwt ID Token I want to receive the additional claims, NOT the jwt access token used for calling the graph or another Web API endpoint.
MS Docs reference: v2.0-specific optional claims set
Below is the extract from the Manifest file: (note I've even declared the "accessTokenAcceptedVersion": 2, given that optional claims I'm using are not available in ver.1, which if the above was left at default 'null' value then Azure will assume we're using legacy ver.1 - a possible gotcha)
"accessTokenAcceptedVersion": 2,
"optionalClaims": {
"idToken": [
{
"name": "given_name",
"source": "user",
"essential": false,
"additionalProperties": []
},
{
"name": "family_name",
"source": "user",
"essential": false,
"additionalProperties": []
}
],
"accessToken": [],
"saml2Token": []
},
Extract from startup class:
public void ConfigureServices(IServiceCollection services)
{
// Added to original .net core template.
// ASP.NET Core apps access the HttpContext through the IHttpContextAccessor interface and
// its default implementation HttpContextAccessor. It's only necessary to use IHttpContextAccessor
// when you need access to the HttpContext inside a service.
// Example usage - we're using this to retrieve the details of the currrently logged in user in page model actions.
services.AddHttpContextAccessor();
// DO NOT DELETE (for now...)
// This 'Microsoft.AspNetCore.Authentication.AzureAD.UI' library was originally used for Azure Ad authentication
// before we implemented the newer Microsoft.Identity.Web and Microsoft.Identity.Web.UI NuGet packages.
// Note after implememting the newer library for authetication, we had to modify the _LoginPartial.cshtml file.
//services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
// .AddAzureAD(options => Configuration.Bind("AzureAd", options));
///////////////////////////////////
// Add services required for using options.
// e.g used for calling Graph Api from WebOptions class, from config file.
services.AddOptions();
// Add service for MS Graph API Service Client.
services.AddTransient<OidcConnectEvents>();
// Sign-in users with the Microsoft identity platform
services.AddSignIn(Configuration);
// Token acquisition service based on MSAL.NET
// and chosen token cache implementation
services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { Constants.ScopeUserRead })
.AddInMemoryTokenCaches();
// Add the MS Graph SDK Client as a service for Dependancy Injection.
services.AddGraphService(Configuration);
///////////////////////////////////
// The following lines code instruct the asp.net core middleware to use the data in the "roles" claim in the Authorize attribute and User.IsInrole()
// See https://learn.microsoft.com/aspnet/core/security/authorization/roles?view=aspnetcore-2.2 for more info.
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
// The claim in the Jwt token where App roles are available.
options.TokenValidationParameters.RoleClaimType = "roles";
});
// Adding authorization policies that enforce authorization using Azure AD roles. Polices defined in seperate classes.
services.AddAuthorization(options =>
{
options.AddPolicy(AuthorizationPolicies.AssignmentToViewLogsRoleRequired, policy => policy.RequireRole(AppRole.ViewLogs));
});
///////////////////////////////////
services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
// Adds the service for creating the Jwt Token used for calling microservices.
// Note we are using our independant bearer token issuer service here, NOT Azure AD
services.AddScoped<JwtService>();
}
Sample Razor PageModel method:
public void OnGet()
{
var username = HttpContext.User.Identity.Name;
var forename = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "given_name")?.Value;
var surname = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "family_name")?.Value;
_logger.LogInformation("" + username + " requested the Index page");
}
UPDATE
Getting closer to a solution but not quite there yet. Couple of issues resolved:
I originally created the Tenant in Azure to use B2C AD, even though I was no longer using B2C and had switched to Azure AD. It wasn't until I deleted the tenant and created a new one before I started to see the optional claims come through to the webapp correctly. After creating the new tenant and assigning the tenant type to use Azure AD, I then found that the 'Token Configuration' menu was now available for configuring the optional claims through the UI, it seems that modifying the App manifest is still required as well, as shown above.
I had to add the 'profile' scope as type 'delegated' to the webapp API Permissions in Azure.
The final issue still unresolved is that although I can see the claims present during Debug, I cant figure out how to retrieve the claim values.
In the method below, I can see the required claims when using Debug, but can't figure out how to retrieve the values:
public void OnGet()
{
var username = HttpContext.User.Identity.Name;
var forename = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "given_name")?.Value;
var surname = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "family_name")?.Value;
_logger.LogInformation("" + username + " requested the Index page");
}
Debug Screenshots shows the given_name & family_name are present:
I've tried different code examples using the claims principal to try and get the values out, but nothing is working for me. Hoping this final riddle is fairly simple to someone who knows the required syntax, as said we now have the required optional claims present, its just not knowing how to actually get the values out.
Big thanks to 'Dhivya G - MSFT Identity' for their assistance (see comments below my original question) method below now allows me to access the required claim values from the Token ID returned from Azure following successful login.
public void OnGet()
{
var username = HttpContext.User.Identity.Name;
var forename = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
var surname = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value;
_logger.LogInformation("" + username + " requested the Index page");
}
My scenario is to create app & spn via AAD Graph. That ist rather easy (with a redirect for browser-based consent), what I now want to do is consent the spn right away (like you can do in the portal). The code itself is straight-forward:
var g = new OAuth2PermissionGrant();
g.ClientId = thePrincipal.ObjectId;
g.ConsentType = "AllPrincipals";
g.PrincipalId = null;
g.ResourceId = ##resourceId##;
g.ExpiryTime = DateTime.Now.AddYears(10);
g.Scope = "User.Read";
await client.Oauth2PermissionGrants.AddOAuth2PermissionGrantAsync(g);
Now the part that I haven't figured out properly is ##resourceId##. This is supposed to be the resourceId - in the code sample, it should be Windows Azure Active Directory. How do I get the resourceId for eg the following required resource access (00000002-0000-0000-c000-000000000000):
RequiredResourceAccess =
new [] {
new RequiredResourceAccess() {
ResourceAppId = "00000002-0000-0000-c000-000000000000",
ResourceAccess = new [] {
new ResourceAccess() {
Id = new Guid("311a71cc-e848-46a1-bdf8-97ff7156d8e6"), // sign in and read profile (delegated perm)
Type = "Scope"
},
The lookup ResourceAppId -> resourceId (app to spn) is what I am missing. For eg AAD, Graph, manage.office.com et al.
From the documentation for the OAuth2PermissionGrant entity, the resourceId field of an OAuth2PermissionGrant is the objectId of the ServicePrincipal object for the resource:
Specifies the objectId of the resource service principal to which access has been granted.
So, from the tenant in which you are creating the OAuth2PemrissionGrant, you need to retrieve the ServicePrincipal object corresponding to the resource app you would like to grant permission to, and from that object, read the objectId property.
If you have the resource app's AppId, you can retrieve the corresponding ServicePrincipal object (if one exists) with:
GET https://graph.windows.net/{tenant}/servicePrincipals
?$filter=appId eq '{app-id-guid}'
&api-version=1.6
With Microsoft.Azure.ActiveDirectory.GraphClient (which I think is what you're using in your code), you would do this with:
graphClient.ServicePrincipals.Where(sp => sp.AppId == "{app-id-guid}")
If what you have to identify the resource app is not the Guid app ID, but a (somewhat) friendly identifier URI (e.g. "https://graph.microsoft.com"), you can retrieve the matching ServicePrincipal object by filtering on servicePrincipalNames.
With Azure AD Graph:
GET https://graph.windows.net/{tenant}/servicePrincipals
?$filter=servicePrincipalNames/any(n:n eq 'https://graph.microsoft.com'))
&api-version=1.6
With Microsoft.Azure.ActiveDirectory.GraphClient:
graphClient.ServicePrincipals
.Where(sp => sp.ServicePrincipalNames.Any(n => n == "https://graph.microsoft.com"))
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.
I have just written an app with Azure Active Directory Single Sign-On.
The consent framework is handled in the AccountController, in the ApplyForConsent action listed below. Until recently, everything has worked seamlessly. I could grant consent as an admin user of an external tenant, then sign out of the app, and sign in again as a non-admin user.
My Azure Active Directory app requires the following delegated permissions:
Read directory data
Enable sign-on and read users' profiles
Access your organisation's directory
Now, after I have gone through the consent framework (by POSTing from a form to ApplyForConsent as an admin user), signing in as a non-admin user fails with the error message AADSTS90093 (This operation can only be performed by an administrator). Unhelpfully, the error message doesn't say what "this operation" actually is, but I suspect it is the third one (Access your organisation's directory).
I stress again, this has only recently stopped working. Nothing has changed in this part of the code, although I grant it is possible that other changes elsewhere in the codebase may have had knock-on effects of which I remain blissfully ignorant.
Looking at the documentation, it seems that this use of the consent framework is already considered "Legacy", but I'm having a difficult time finding a more up-to-date implementation.
The requested permissions in the code sample below is the single string "DirectoryReaders".
I have three questions for helping me debug this code:
What is the difference between "Read directory data" and "Access your organisation's directory"? When would I need one rather than another?
Do I need to request more than just "DirectoryReaders"?
Is there now a better way to implement the Consent Framework?
This is the existing code:
private static readonly string ClientId = ConfigurationManager.AppSettings["ida:ClientID"];
[HttpPost]
public ActionResult ApplyForConsent()
{
string signupToken = Guid.NewGuid().ToString();
string replyUrl = Url.Action("ConsentCallback", "Account", new { signupToken }, Request.Url.Scheme);
DatabaseIssuerNameRegistry.CleanUpExpiredSignupTokens();
DatabaseIssuerNameRegistry.AddSignupToken(signupToken, DateTimeOffset.UtcNow.AddMinutes(5));
return new RedirectResult(CreateConsentUrl(ClientId, "DirectoryReaders", replyUrl));
}
[HttpGet]
public ActionResult ConsentCallback()
{
string tenantId = Request.QueryString["TenantId"];
string signupToken = Request.QueryString["signupToken"];
if (DatabaseIssuerNameRegistry.ContainsTenant(tenantId))
{
return RedirectToAction("Validate");
}
string consent = Request.QueryString["Consent"];
if (!String.IsNullOrEmpty(tenantId) && String.Equals(consent, "Granted", StringComparison.OrdinalIgnoreCase))
{
if (DatabaseIssuerNameRegistry.TryAddTenant(tenantId, signupToken))
{
return RedirectToAction("Validate");
}
}
const string error = "Consent could not be provided to your Active Directory. Please contact SharpCloud for assistance.";
var reply = Request.Url.GetLeftPart(UriPartial.Authority) + Url.Action("Consent", new { error });
var config = FederatedAuthentication.FederationConfiguration.WsFederationConfiguration;
var signoutMessage = new SignOutRequestMessage(new Uri(config.Issuer), reply);
signoutMessage.SetParameter("wtrealm", config.Realm);
FederatedAuthentication.SessionAuthenticationModule.SignOut();
return Redirect(signoutMessage.WriteQueryString());
}
private static string CreateConsentUrl(string clientId, string requestedPermissions, string consentReturnUrl)
{
string consentUrl = String.Format(CultureInfo.InvariantCulture, ConsentUrlFormat, HttpUtility.UrlEncode(clientId));
if (!String.IsNullOrEmpty(requestedPermissions))
{
consentUrl += "&RequestedPermissions=" + HttpUtility.UrlEncode(requestedPermissions);
}
if (!String.IsNullOrEmpty(consentReturnUrl))
{
consentUrl += "&ConsentReturnURL=" + HttpUtility.UrlEncode(consentReturnUrl);
}
return consentUrl;
}
I think this link addresses your issue:
http://blogs.msdn.com/b/aadgraphteam/archive/2015/03/19/update-to-graph-api-consent-permissions.aspx
The quick summary is that now only admins can grant consent to a web app for '•Access your organisation's directory'.
This change was made back in March. Native apps are not affected by the change.
My suspicion was correct. I was using legacy tech in the question. By moving to Owin and Identity 2.0, all issues were solved.
The new approach is summarised by https://github.com/AzureADSamples/WebApp-GroupClaims-DotNet
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;
}