J'essaie de comprendre comment autoriser l'utilisation de groupes dans Azure Active Directory B2C. Je peux autoriser via l'utilisateur par exemple:
[Authorize(Users="Bill")]
Cependant, ce n'est pas très efficace et je vois très peu de cas d'utilisation pour cela. Une autre solution serait l'autorisation via le rôle. Cependant, pour une raison quelconque, cela ne semble pas surprendre. Si je donne à un utilisateur le rôle "Global Admin" par exemple, et que j'essaye:
[Authorize(Roles="Global Admin")]
Ça ne marche pas. Existe-t-il un moyen d'autoriser via des groupes ou des rôles?
Cela fonctionnera, mais vous devez écrire quelques lignes de code dans votre logique d'authentification afin d'obtenir ce que vous recherchez.
Tout d'abord, vous devez faire la distinction entre Roles
et Groups
dans Azure AD (B2C).
User Role
est très spécifique et uniquement valide dans Azure AD (B2C) lui-même. Le rôle définit les autorisations dans Azure AD dont dispose l'utilisateur.
Group
(ou Security Group
) définit l'appartenance à un groupe d'utilisateurs, qui peut être exposée aux applications externes. Les applications externes peuvent modéliser Contrôle d'accès basé sur les rôles en plus de Groupes de sécurité. Oui, je sais que cela peut sembler un peu déroutant, mais c'est ce que c'est.
Ainsi, votre première étape consiste à modéliser votre Groups
dans Azure AD B2C - vous devez créer les groupes et affecter manuellement des utilisateurs à ces groupes. Vous pouvez le faire dans le portail Azure ( https://portal.Azure.com/ ):
Ensuite, revenez à votre application, vous devrez coder un peu et demander à Azure AD B2C Graph API les adhésions des utilisateurs une fois que l'utilisateur est correctement authentifié. Vous pouvez utiliser cet exemple pour vous inspirer sur la façon d'obtenir des adhésions aux groupes d'utilisateurs. Il est préférable d'exécuter ce code dans l'une des notifications OpenID (c'est-à-dire SecurityTokenValidated ) et d'ajouter le rôle des utilisateurs au ClaimsPrincipal.
Une fois que vous modifiez le ClaimsPrincipal pour avoir des groupes de sécurité Azure AD et des valeurs de "revendication de rôle", vous pourrez utiliser l'attribut Authrize avec la fonction de rôles. C'est vraiment 5-6 lignes de code.
Enfin, vous pouvez donner votre vote pour la fonctionnalité ici: https://feedback.Azure.com/forums/169401-Azure-active-directory/suggestions/10123836-get-user-membership-groups-in- the-claims-with-ad-b afin d'obtenir la revendication d'appartenance à un groupe sans avoir à interroger l'API Graph pour cela.
L'obtention de l'appartenance à un groupe pour un utilisateur à partir d'Azure AD nécessite bien plus que "quelques lignes de code", donc j'ai pensé partager ce qui a finalement fonctionné pour moi pour sauver les autres quelques jours de tirage et de tête- claquement.
Commençons par ajouter les dépendances suivantes à project.json:
"dependencies": {
...
"Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.8",
"Microsoft.Azure.ActiveDirectory.GraphClient": "2.0.2"
}
La première est nécessaire car nous devons authentifier notre application afin qu'elle puisse accéder à l'API AAD Graph. La seconde est la bibliothèque cliente de l'API Graph que nous utiliserons pour interroger les membres des utilisateurs. Il va sans dire que les versions ne sont valables qu'au moment de la rédaction de ce document et peuvent changer à l'avenir.
Ensuite, dans la méthode Configure () de la classe Startup, peut-être juste avant de configurer l'authentification OpenID Connect, nous créons le client API Graph comme suit:
var authContext = new AuthenticationContext("https://login.microsoftonline.com/<your_directory_name>.onmicrosoft.com");
var clientCredential = new ClientCredential("<your_b2c_app_id>", "<your_b2c_secret_app_key>");
const string AAD_GRAPH_URI = "https://graph.windows.net";
var graphUri = new Uri(AAD_GRAPH_URI);
var serviceRoot = new Uri(graphUri, "<your_directory_name>.onmicrosoft.com");
this.aadClient = new ActiveDirectoryClient(serviceRoot, async () => await AcquireGraphAPIAccessToken(AAD_GRAPH_URI, authContext, clientCredential));
AVERTISSEMENT: NE codez PAS en dur votre clé d'application secrète, mais conservez-la à la place dans un endroit sûr. Eh bien, vous le saviez déjà, non? :)
La méthode asynchrone AcquireGraphAPIAccessToken () que nous avons remise au constructeur du client AD sera appelée si nécessaire lorsque le client doit obtenir un jeton d'authentification. Voici à quoi ressemble la méthode:
private async Task<string> AcquireGraphAPIAccessToken(string graphAPIUrl, AuthenticationContext authContext, ClientCredential clientCredential)
{
AuthenticationResult result = null;
var retryCount = 0;
var retry = false;
do
{
retry = false;
try
{
// ADAL includes an in-memory cache, so this will only send a request if the cached token has expired
result = await authContext.AcquireTokenAsync(graphAPIUrl, clientCredential);
}
catch (AdalException ex)
{
if (ex.ErrorCode == "temporarily_unavailable")
{
retry = true;
retryCount++;
await Task.Delay(3000);
}
}
} while (retry && (retryCount < 3));
if (result != null)
{
return result.AccessToken;
}
return null;
}
Notez qu'il dispose d'un mécanisme de relance intégré pour gérer les conditions transitoires, que vous souhaiterez peut-être adapter aux besoins de votre application.
Maintenant que nous avons pris soin de l'authentification des applications et de la configuration du client AD, nous pouvons aller de l'avant et puiser dans les événements OpenIdConnect pour enfin en faire usage. De retour dans la méthode Configure () où nous appelons généralement app.UseOpenIdConnectAuthentication()
et créons une instance d'OpenIdConnectOptions, nous ajoutons un gestionnaire d'événements pour l'événement OnTokenValidated:
new OpenIdConnectOptions()
{
...
Events = new OpenIdConnectEvents()
{
...
OnTokenValidated = SecurityTokenValidated
},
};
L'événement est déclenché lorsque le jeton d'accès pour l'utilisateur connecté a été obtenu, validé et que l'identité de l'utilisateur a été établie. (À ne pas confondre avec le jeton d'accès de l'application requis pour appeler l'API AAD Graph!)
private Task SecurityTokenValidated(TokenValidatedContext context)
{
return Task.Run(async () =>
{
var oidClaim = context.SecurityToken.Claims.FirstOrDefault(c => c.Type == "oid");
if (!string.IsNullOrWhiteSpace(oidClaim?.Value))
{
var pagedCollection = await this.aadClient.Users.GetByObjectId(oidClaim.Value).MemberOf.ExecuteAsync();
do
{
var directoryObjects = pagedCollection.CurrentPage.ToList();
foreach (var directoryObject in directoryObjects)
{
var group = directoryObject as Group;
if (group != null)
{
((ClaimsIdentity)context.Ticket.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, group.DisplayName, ClaimValueTypes.String));
}
}
pagedCollection = pagedCollection.MorePagesAvailable ? await pagedCollection.GetNextPageAsync() : null;
}
while (pagedCollection != null);
}
});
}
Le type de revendication de rôle est utilisé ici, mais vous pouvez en utiliser un personnalisé.
Après avoir fait ce qui précède, si vous utilisez ClaimType.Role, tout ce que vous devez faire est de décorer votre classe ou méthode de contrôleur comme suit:
[Authorize(Role = "Administrators")]
C'est, bien sûr, à condition que vous ayez un groupe désigné configuré dans B2C avec un nom d'affichage "Administrateurs".
Si, cependant, vous avez choisi d'utiliser un type de revendication personnalisé, vous devez définir une stratégie d'autorisation basée sur le type de revendication en ajoutant quelque chose comme ceci dans la méthode ConfigureServices (), par exemple:
services.AddAuthorization(options => options.AddPolicy("ADMIN_ONLY", policy => policy.RequireClaim("<your_custom_claim_type>", "Administrators")));
puis décorez une classe ou une méthode de contrôleur privilégiée comme suit:
[Authorize(Policy = "ADMIN_ONLY")]
Ok, on a déjà fini? - Pas exactement.
Si vous exécutiez votre application et tentiez de vous connecter, vous obtiendrez une exception de l'API Graph réclamant "Privilèges insuffisants pour terminer l'opération". Cela peut ne pas être évident, mais bien que votre application s'authentifie avec succès avec AD en utilisant son app_id et app_key, elle n'a pas les privilèges requis pour lire les détails des utilisateurs de votre AD. Afin d'accorder un tel accès à l'application, j'ai choisi d'utiliser le Module Azure Active Directory pour PowerShell
Le script suivant a fait l'affaire pour moi:
$tenantGuid = "<your_tenant_GUID>"
$appID = "<your_app_id>"
$userVal = "<admin_user>@<your_AD>.onmicrosoft.com"
$pass = "<admin password in clear text>"
$Creds = New-Object System.Management.Automation.PsCredential($userVal, (ConvertTo-SecureString $pass -AsPlainText -Force))
Connect-MSOLSERVICE -Credential $Creds
$msSP = Get-MsolServicePrincipal -AppPrincipalId $appID -TenantID $tenantGuid
$objectId = $msSP.ObjectId
Add-MsolRoleMember -RoleName "Company Administrator" -RoleMemberType ServicePrincipal -RoleMemberObjectId $objectId
Et maintenant, nous avons enfin terminé! Comment ça pour "quelques lignes de code"? :)
j'ai implémenté cela comme écrit, mais en mai 2017, la ligne
((ClaimsIdentity)context.Ticket.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, group.DisplayName, ClaimValueTypes.String));
doit être changé en
((ClaimsIdentity)context.Ticket.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, group.DisplayName));
Pour le faire fonctionner avec les dernières bibliothèques
Excellent travail à l'auteur
Aussi, si vous rencontrez un problème avec Connect-MsolService donnant une mauvaise mise à jour du nom d'utilisateur et du mot de passe à la dernière bibliothèque