I am using Microsoft.Azure.ActiveDirectory.GraphClient version 2.1.1.0 to get groups that my user belongs to.
Method call is like this:
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(
new Uri(GraphUrl),
async () => await GetAppTokenAsync());
IEnumerable<string> groups = GetGroupsAsync(activeDirectoryClient, "currentUserObjectId").Result;
private static async Task<IEnumerable<string>> GetGroupsAsync(ActiveDirectoryClient activeDirectoryClient, string currentUserObjectId )
{
return await activeDirectoryClient.Users.GetByObjectId(currentUserObjectId).GetMemberGroupsAsync(true);
}
private static async Task<string> GetAppTokenAsync()
{
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(ServiceRoot);
var token = await authContext.AcquireTokenAsync(GraphUrl,new ClientCredential("clientId", "clientSecret"));
return token.AccessToken;
}
However the method hangs even though in Fiddler I see that the request has succedeed and contains correct groups.
My question is duplicate of Azure ActiveDirectory Graph API GraphClient not returning AD Groups. A workaround exists but not a explanation why the method does not work.
If indeed your ServiceRoot value is the same for your instantiation of ActiveDirectoryClient and for your call to AuthenticationContext, that could be the source of your problem.
ActiveDirectoryClient should be instantiated with https://graph.windows.net/
AuthenticationContext should be called with
https://login.microsoftonline.com/
Though that wouldn't manifest itself in the method hanging nor a successful request, that was the only change I had to make to your code for it to work for me, otherwise it would return with a Not Found error.
I've had similar issues with the Graph Api library when using the Result property, try changing your call to this:-
IEnumerable<string> groups = await GetGroupsAsync(activeDirectoryClient, "currentUserObjectId");
Related
I'm trying to put two things working at the same time and I have no luck.
In my .Net 6 Blazor WebAssembly Hosted, I can log to Azure AD accounts and it works fine following the sample:
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-azure-active-directory?view=aspnetcore-6.0
Also, I can log to Microsoft Graph following this:
https://github.com/microsoftgraph/msgraph-training-blazor-clientside
But what I want is to be able to have a token valid for both. I want to call to Microsoft Graph and to call to my API from the server side.
Any idea how to mix both "samples" to make it work? I think the only I need is to "mix" in the program.cs this:
builder.Services.AddHttpClient("ReservasSalasAuth.ServerAPI", client =>
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ReservasSalasAuth.ServerAPI"));
And this:
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://graph.microsoft.com") });
But I have no luck...
After some more investigation...I realize that the order in the AddMsalAuthentication makes the difference...
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
var ApiScope = builder.Configuration.GetValue<string>("ApiScope");
options.ProviderOptions.DefaultAccessTokenScopes.Add(ApiScope);
options.UserOptions.RoleClaim = "appRole";
var scopes = builder.Configuration.GetValue<string>("GraphScopes");
foreach (var scope in scopes.Split(';'))
{
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
}
}).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, GraphUserAccountFactory>();
This way, I take the Scope for the Api an it works the Api call but not the Graph call.
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.UserOptions.RoleClaim = "appRole";
var scopes = builder.Configuration.GetValue<string>("GraphScopes");
foreach (var scope in scopes.Split(';'))
{
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
}
var ApiScope = builder.Configuration.GetValue<string>("ApiScope");
options.ProviderOptions.DefaultAccessTokenScopes.Add(ApiScope);
}).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, GraphUserAccountFactory>();
And changing the order I put the scopes, it works the Graph call but not the API call.
Any ideas to make it work both two?
I had this same issue. You CAN'T use Msal to operate on two different authority.
So if you want to use graph and your api in the same time you need to chose One to use Msal with and for the other one you need to make the entire requirement yourself. So ask for authorize code, use it to get new access token and then use this new one for your second Http client as bearer in the header.
So yes you cannot achieve what you want with only one login.
Here look at last comment from Allen Wu
https://stackoverflow.com/a/65694725
I am starting out with MS Graph API. I need a login example with least amount of user intervention; if possible at most only once and the first time app is run. The sample GraphTutorial app (https://github.com/microsoftgraph/msgraph-training-dotnet-core/tree/main/demo) seems to require user intervention every time it is run with a code required to be manually entered.
Thanks
Regards
To call the graph api without login, it's possible with client credential flow. Microsoft also provides sample code for it. I also tested and pick some of the key code like below:
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
namespace WebApplication4clientflow.Controllers
{
public class HelloController : Controller
{
public async Task<string> Index()
{
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create("azure_ad_app_clientid")
.WithClientSecret("client_secret_of_your_app")
.WithAuthority(new Uri("https://login.microsoftonline.com/tenant_name.onmicrosoft.com"))
.Build();
AuthenticationResult result = null;
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
return result.AccessToken;
}
}
}
With the code above, we can generate access token to call graph api, but pls note, it's not suitable for those apis that don't support application permission, like below:
Pls feel free to let me know if you met further problems.
In my previous MVC projects, I was using default identity pages to login (/Identity/Account/Login)
By using the code below on my Controller.cs, I can get various values like the roles that current user is assigned to, the claims of the roles, etc...
var claims = User.Claims; // or HttpContext.User.Claims
//expected claim values are:
//{the-user-guid}
//email#test.com
//["Admin", "Manager"]
//Permission.Module1.Create <-- I need this (in Blazor)
//Permission.Module2.Read <-- I need this (in Blazor)
//and so on...
However, having the same setup with Blazor, calling User.Claims doesn't include the roles and the claims of the roles by default.
I was able to include the roles (ie. Admin, Manager) that user is assigned to by doing this. So the next bit I am aiming to achieve is getting the RoleClaims (from AspNetRoleClaims table) - which would give me the Permissions.
In the context of the solution from the link, I am not sure if there are other "keywords" I can use (apart from "role") to be able to get the RoleClaims. I would also appreciate it if you could point me to a resource with the list of these keywords.
You will need to inject The following provider in your page
#inject Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider AuthenticationStateProvider
Then use it as following
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
user = authState.User;
var claims = user.Claims?.ToList();
Also if you want to secure your pages based on Roles/policy
you could add the policy in program.cs
as following
builder.Services.AddAuthorizationCore(options =>
{
options.AddPolicy("Admin", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin");
});
});
Then use it within your partial class as following
[Authorize(Policy = "Admin")]
or within your page as following
#using Microsoft.AspNetCore.Authorization
#attribute [Authorize(Policy ="Admin")]
Good luck
Edit April 28, 2021
Thanks for the Update Majo
If you want to get the user information within the controller
you will need to inject the UserManager in your controller constructor
private readonly ILogger<WeatherForecastController> _logger;
private readonly UserManager<ApplicationUser> userManager;
public WeatherForecastController(ILogger<WeatherForecastController> logger ,
UserManager<ApplicationUser> userManager)
{
_logger = logger;
this.userManager = userManager;
}
Then inside your action you will be able to get all the claims and rols as desired using the instance of UserManager
within your action you could use something like this
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); // get the user ID
var user = await userManager.FindByIdAsync(userId); // get the user object
var claims = await userManager.GetClaimsAsync(user); // get the claims based on the user object
var rols = await userManager.GetRolesAsync(user); // get the roles based on the user object
I hope this answer your question!
Regards,
Khaled Dehia
i 'm working on an azure functions that make some graph call to different tenant (multitenant)
I want to reuse a GraphServiceClient and leveraging token cache
I generate the GraphServiceClient in this way:
List<string> scopes = new List<string>() { "https://graph.microsoft.com/.default" };
var authProvider = ConfidentialClientApplicationBuilder.Create("e9b93362-a788-4644-8623-da9f4d4776a7")
.WithAuthority(AzureCloudInstance.AzurePublic, AadAuthorityAudience.AzureAdMultipleOrgs)
.WithClientSecret("fkpx53225awyQJDHV35:^][")
.Build();
var dd = new MsalAuthenticationProvider(authProvider, scopes.ToArray(),"ugochotmail.onmicrosoft.com");
var appGraphClient = new GraphServiceClient(dd);
Than i should call
authResult = await _clientApplication.AcquireTokenForClient(_scopes)
.WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
.ExecuteAsync();
To obtain a token for the app to access the specific tenant.
The problem is in the authentication provider that is call on every send request but doen't offer a parameter with the tenant name
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var token = await GetTokenAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
At the moment i just add a property to the Authentication provider to set the tenant. It works but i would like to know if there is a better solution
Per my understanding, it seems your function doesn't allow a parameter which specify the tenant name and then use the tenant name when do GetTokenAsync() method. And now you can just hardcode the tenant name in the line new MsalAuthenticationProvider(... to specify the tenant.
For this problem, I think you can add a variable named tenant in the "Application settings" of your function app (as below screenshot show).
Then add a line of code string tenant = System.Environment.GetEnvironmentVariable("tenant"); above var token = await GetTokenAsync();
After that, you can add parameter in method GetTokenAsync() like GetTokenAsync(tenant). Then you do not need to hardcode tenant name in code, you just need to change the tenant name in "Application settings" of your function.
If I misunderstand your requirement, please provide more details.
=============================Update===============================
It seems you just want to specify the tenant in your code by a parameter, but not add the tenant name as a property in var dd = new MsalAuthenticationProvider(authProvider, scopes.ToArray(),"tenant name");. If so, you can refer to the code below (just add a line .WithTenantId("xxx.onmicrosoft.com") when do ConfidentialClientApplicationBuilder)
No it doesn't fix the problem as, in a multitenant, the target tenant is send as a parameter to the function. I'm working on an other approach i will come back when i will finish tests.
Thanks a lot
I am using Identityserver with multiple external authorities(providers). The scenario which I am trying to get here is I have a client configured with "EnableLocalLogin" as false. I do have multiple external providers. The below code line in the "LoginViewModel.cs" in the quick start is not making sense.
public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1;
This is returning false and I am not getting redirected to external provider. Should this be ExternalProviders?.Count() > 0
In my opinion, IsExternalLoginOnly is not well named. it is called only when you show the login page :
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
var vm = await _account.BuildLoginViewModelAsync(returnUrl);
if (vm.IsExternalLoginOnly)
{
// only one option for logging in
return await ExternalLogin(vm.ExternalProviders.First().AuthenticationScheme, returnUrl);
}
return View(vm);
}
It is used to directly redirect to a provider in case the user has no choice about it.
Now in your case, you have multiple external providers and you have to ask the user which one to use. You can not automaticly pass this step as long as your client allows multiple providers
You can still code your own login and try to automate this step following the returnUrl