Mots de passes en claires en BDD, gestion des rôles hasardeuse, couche d’authentification personnalisée…Tout développeur c’est déjà retrouvé confronter à ce genre de situation ; pourtant tout RSSI exige une sécurité de l’identité utilisateur et pousse de plus en plus de l’authentification multi-facteurs !

C’est le rôle de l’IdentityProvider de garantir l’identité d’un utilisateur ainsi que les rôles de ce dernier, alors pourquoi sous-estimer ce composant ? En quelques minutes, Azure Active Directory B2C peux fournir des flow d’authentification, de création ou de modification d’utilisateurs permettant ainsi aux développeurs de s’extraire cette épine du pied.

Voici quelques chiffres qui peuvent vous aidez à comprendre la puissance d’Azure Active Directory :

  • 254 millions d’utilisateurs actifs par mois
  • 30 milliards d’authentification par jour

Des données utilisateurs sécurisées et protégées par Microsoft

Avec un IDP comme Azure AD B2C, plus besoin de stocker les informations utilisateurs (plutôt pratique avec la GDPR). Vous vous concentrer donc uniquement sur le service que vous proposez à ses derniers. 

Le parcours est simple, lorsqu’un utilisateur naviguant sur votre application souhaite s’authentifier vous le rediriger vers Microsoft ou il effectuera son parcours d’authentification/d’enregistrement. 

Personnalisation réalisée pour Openfield

 Une fois le parcours réalisé, il sera de nouveau redirigé vers votre application avec un JWT (Json Web Token) qui contiendra les Claims que vous avez demandé (informations échangés lors de l’authentification).  

Voici à quoi ressemble le diagramme de séquence lors d’une authentification : 

Flow standard

Utilisation de l’Identity Experience Framework

L’utilisation basic d’Azure AD B2C cantonne le parcours utilisateur aux possibilités offertes par le portail d’administration d’AD B2C. Afin de libérer pleine puissance de cette solution il faudra utiliser l’IEF (pour Identity Experience Framework). Avec ce dernier il est possible de rentrer au cœur même du parcours d’authentification (et d’enregistrement / modification) de l’utilisateur grâce à l’écriture de Custom Policies. Microsoft documente parfaitement la prise en main d’IEF sur son site internet (https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-get-started-custom

Ajout des groupes dans les claims

Si vous utilisez Azure Active Directory pour l’authentification de vos collaborateurs dans vos applications vous connaissez certainement la possibilité d’intégrer les groupes d’appartenance et les roles applicatifs de l’utilisateur directement dans le JWT en modifiant le manifeste de l’application déclarée dans Azure AD. Malheureusement la modification de ce manifeste pour une application AD B2C n’est pas possible !  

IEF permettant d’appeler des API REST, il aurait été envisageable d’appeler l’AD Graph API pour ajouter les informations manquantes s’il avait été possible d’autoriser les requêtes via un Bearer token (nécessaire pour les appelle AD Graph API). Nous allons donc devoir passer par un proxy : 

Ceci se représente au sein de notre Custom Policy via la création d’un TechnicalProfile : 

<TechnicalProfile Id="GraphProxy-GetUserGroups"> 
  <DisplayName>Query the GraphProxy API to retrieve user's groups</DisplayName> 

  <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> 

  <Metadata> 
    <Item Key="ServiceUrl">#{GraphApiProxy.GetMemberSecurityGroupsEndpoint}#</Item> 
    <Item Key="AuthenticationType">Basic</Item> 
    <Item Key="SendClaimsIn">Body</Item> 
    <Item Key="ClaimsFormat">Body</Item> 
  </Metadata> 

  <CryptographicKeys> 
    <!-- Retrieve login and password from ADB2C Key container --> 
    <Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_GraphApiProxyClientId" /> 
    <Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_GraphApiProxyClientSecret" /> 
  </CryptographicKeys> 

  <InputClaims> 
    <!-- GraphProxy input argument : user’s objectId --> 
    <InputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" /> 
  </InputClaims> 

  <OutputClaims> 
    <!-- GraphProxy output response --> 
    <OutputClaim ClaimTypeReferenceId="groups" PartnerClaimType="groups" /> 
    <OutputClaim ClaimTypeReferenceId="groupsNames" PartnerClaimType="groupsNames" /> 
  </OutputClaims> 

  <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" /> 
</TechnicalProfile> 

Il faut maintenant ajouter une OrchestrationStep au sein de la UserJourney :  

<UserJourney Id="SignIn"> 
  <OrchestrationSteps> 
    [...] 

    <OrchestrationStep Order="4" Type="ClaimsExchange"> 
      <ClaimsExchanges> 
        <ClaimsExchange Id="GraphProxy-GetUserGroupsExchange" TechnicalProfileReferenceId="GraphProxy-GetUserGroups" /> 
      </ClaimsExchanges> 
    </OrchestrationStep> 
    [...] 

  </OrchestrationSteps> 
  <ClientDefinition ReferenceId="DefaultWeb" /> 
</UserJourney> 

That’s it ! Le JWT de l’utilisateur contiendra à présent les claims “groups” et “groupsNames”, deux listes que le graph proxy renvoi. 

Intégration continue des Custom Policies 

Il est indispensable d’y penser de nos jours ; elle nous permet de simplifier et de formaliser les process de livraison.  

Très logiquement vous allez versionner le code de vos policies et vous allez vouloir les livrer de manière automatique sur vos environnements cibles ! Malheureusement ce n’est pas aussi simple car il est aujourd’hui impossible de gérer les policies via des commandes AzureRM !  

Pour pallier ce manque, voici un script qui : 

  • tokenize certain Id récupérés directement depuis le tenant 
  • upload une policy via l’api que le portail Azure utilise :  
param 
( 
    [Parameter(Mandatory = $true, HelpMessage="B2C Tenant ID where to upload the policy")] 
    [string]$tenantId, 
    [Parameter(Mandatory = $true, HelpMessage="Tenant administrator login with API management rights")] 
    [string]$login, 
    [Parameter(Mandatory = $true, HelpMessage="/!\ Plain text password, use keyvault /!\")] 
    [string]$pwd, 
    [Parameter(Mandatory = $true, HelpMessage="Path to the B2C Policy XML to upload")] 
    [string]$b2cPolicyPath, 
    [Parameter(HelpMessage="Used to display policy xml in output")] 
    [switch]$showXml 
) 

Try { 
#Securing password 
$sPwd = ConvertTo-SecureString -String $pwd -AsPlainText -Force -ErrorAction Stop 
 
#Creating credential password 
$credentials = New-Object System.Management.Automation.PSCredential ($login, $sPwd) -ErrorAction Stop 

#Login the tenant admin user 
# /!\ This can't be a Service Principal because /!\ 
# /!\ the API only accepte User Principal call  /!\ 
Login-AzureRmAccount -Scope Process -TenantId $tenantId -Credential $credentials -ErrorAction Stop | Out-Null 

#Retrieving the user context for the management api 
$context = (Get-AzureRmContext).TokenCache.ReadItems() ` 
    | Where-Object { $_.TenantId -eq $tenantId -and $_.Resource -eq "https://management.core.windows.net/"} ` 
    | Sort-Object -Property ExpiresOn -Descending ` 
    | Select-Object -First 1 

#Storing the access token 
$accessToken = $context.AccessToken 

#Retrieving B2C extension application 
$b2cExtensionApp = Get-AzureRmADApplication | Where DisplayName -Like "b2c-extensions-app*" 
$IEFApp = Get-AzureRmADApplication | Where DisplayName -eq "IdentityExperienceFramework" 
$proxyIEFApp = Get-AzureRmADApplication | Where DisplayName -eq "ProxyIdentityExperienceFramework" 

#Loading B2C Policy XML 
$b2cPolicyContent = Get-Content $b2cPolicyPath -ErrorAction Stop | Out-String 
if ($showXml) { 
    Write-Host "Loaded xml document : " -ForegroundColor Cyan 
    Write-Host $b2cPolicyContent -ForegroundColor Gray 
} 

#Replacement of B2C Extension Application Id 
Write-Host "Replacing {##ExtensionApplicationId##} by "$b2cExtensionApp.ApplicationId.ToString()" in policy.." 
$b2cPolicyContent =  
    $b2cPolicyContent.Replace("{##ExtensionApplicationId##}", $b2cExtensionApp.ApplicationId.ToString()) 

#Replacement of B2C Extension Application Object Id 
Write-Host "Replacing {##ExtensionApplicationObjectId##} by "$b2cExtensionApp.ObjectId.ToString()" in policy.." 
$b2cPolicyContent =  
    $b2cPolicyContent.Replace("{##ExtensionApplicationObjectId##}", $b2cExtensionApp.ObjectId.ToString()) 

#Replacement of Identity Experience Framework Application Id 
Write-Host "Replacing {##IdentityExperienceFrameworkAppId##} by "$IEFApp.ApplicationId.ToString()" in policy.." 
$b2cPolicyContent =  
    $b2cPolicyContent.Replace("{##IdentityExperienceFrameworkAppId##}", $IEFApp.ApplicationId.ToString()) 

#Replacement of Proxy Identity Experience Framework Application Id 
Write-Host "Replacing {##ProxyIdentityExperienceFrameworkAppId##} by "$proxyIEFApp.ApplicationId.ToString()" in policy.." 
$b2cPolicyContent =  
    $b2cPolicyContent.Replace("{##ProxyIdentityExperienceFrameworkAppId##}", $proxyIEFApp.ApplicationId.ToString()) 

if ($showXml){ 
    Write-Host "Final policy content to upload : " -ForegroundColor Cyan 
    Write-Host $b2cPolicyContent -ForegroundColor Gray 
} 

#Adding System.Web types 
Add-Type -AssemblyName System.Web 

#Body creation of the request 
$strBody = "<string xmlns=`"http://schemas.microsoft.com/2003/10/Serialization/`">$([System.Web.HttpUtility]::HtmlEncode($b2cPolicyContent))</string>"  

#Header creation of the request 
$htHeaders = @{ "Authorization" = "Bearer $accessToken" } 

#Invoking b2cadmin API 
$response = Invoke-WebRequest -Uri` 
  "https://main.b2cadmin.ext.azure.com/api/trustframework?tenantId=$tenantId&overwriteIfExists=true"` 
  -Method POST -Body $strBody -ContentType "application/xml" ` 
  -Headers $htHeaders -UseBasicParsing -ErrorAction Continue  

Write-Host "Upload status :"$response.StatusCode -ForegroundColor Green 
if($response.Content.Length -gt 0){ 
    Write-Host "Content :"$response.Content -ForegroundColor Green 
} 

if(-not($response.StatusCode -ge 200 -and $response.StatusCode -lt 300)){ 
    Write-Host $response -ForegroundColor Red 
}} Catch { 
    Write-Host $_.Exception.Message -ForegroundColor Red 
    Break
}

Et voici à quoi ressemblerai une release dans Azure DevOps : 

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.