Manipulando senhas antes de efetuar o login no Azure AD B2C (Password Salt)
Introdução
Nesse post, abordarei um método polêmico, pois o envio de senhas através de requisições não é recomendado, mas às vezes é a única saída que você encontrará.
Imagine que você está migrando seus usuários para o Azure AD B2C. Todas as suas senhas são criptografadas em SHA-256, e a gestão do projeto não quer que os usuários efetuem a troca de senha. O tipo de criptografia não importa, desde que o hash nunca altere. Caso a criptografia que seu projeto use seja reversível, também não há necessidade de seguir esse post. Você pode simplesmente descriptografar as senhas e importá-las direto (seria até o ideal).
Levando em consideração que sua criptografia é irreversível e imutável (a menos que o usuário defina uma nova senha), no momento que você começou a popular o seu Azure AD B2C, você não tinha controle nenhum de qual era a senha real do seu usuário. Foi algo parecido com isso:

Então quando você criou o seu usuário foi algo parecido com:
1var graphNewUser =2new User3{4Identities =5new List<ObjectIdentity>()6{7new ObjectIdentity()8{9SignInType = "emailAddress",10Issuer = AzureAdB2CTenant,11IssuerAssignedId = "julianobiffi@hotmail.com"12}13},14PasswordProfile =15new PasswordProfile16{17ForceChangePasswordNextSignIn = false,18Password = "157edfef911735f5446d87c1889e47a6b6e34b3be4caf511b1dbf2d74fae7117"19},20PasswordPolicies = "DisablePasswordExpiration,DisableStrongPassword",21//Other properties22};2324await _GraphApiService.GraphServiceClient25.Users26.PostAsync(graphNewUser);
Partindo disso, quando o usuário tentar logar com sua senha techbiffi123, não conseguirá, pois, para o Azure AD B2C, sua senha é 157edfef911735f5446d87c1889e47a6b6e34b3be4caf511b1dbf2d74fae7117. Daí surge essa solução: iremos interceptar a senha do usuário, aplicar o hash do seu projeto legado e tentar fazer o login com essa senha com hash usando as custom policies e utilizando o SocialAndLocalAccounts.
Implementação
Criação da ClaimType
Para o controle e a utilização da senha com o hash no decorrer do fluxo, será necessária a definição de um ClaimType que fica dentro da ClaimsSchema.
TrustFrameworkBase.xml1<ClaimsSchema>2<!-- Others Claim types -->34<ClaimType Id="passwordWithEncrypt">5<DisplayName>passwordWithEncrypt</DisplayName>6<DataType>string</DataType>7<UserHelpText>Defines the user password with my legacy hash applied.</UserHelpText>8</ClaimType>9</ClaimsSchema>
Criação dos perfis técnicos
O primeiro perfil técnico que será necessário definir é o perfil técnico RESTful para enviar a senha que o usuário inseriu, enviá-la para sua API e receber a senha com o hash aplicado.
TrustFrameworkBase.xml1<ClaimsProvider>2<Domain>Password Encription</Domain>3<DisplayName>Request To encrypt the password</DisplayName>4<TechnicalProfiles>5<TechnicalProfile Id="REST-PasswordEncription">6<DisplayName>Apply the legacy encryption into the provided password</DisplayName>7<Protocol Name="Proprietary"8Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />9<Metadata>10<Item Key="ServiceUrl">11{Settings:MyApiUrl}/EncryptPassword</Item>12<Item Key="SendClaimsIn">Body</Item>13<Item Key="AuthenticationType">ApiKeyHeader</Item>14</Metadata>15<CryptographicKeys>16<Key Id="x-functions-key" StorageReferenceId="B2C_1A_RestApiKey" />17</CryptographicKeys>18<InputClaims>19<InputClaim ClaimTypeReferenceId="password" />20</InputClaims>21<OutputClaims>22<OutputClaim ClaimTypeReferenceId="passwordWithEncrypt" PartnerClaimType="passwordWithEncrypt" />23</OutputClaims>24</TechnicalProfile>25</TechnicalProfiles>26</ClaimsProvider>
Agora você precisará duplicar o perfil técnico login-NonInteractive, perfil responsável pelo login do usuário. Após duplicar, altere a InputClaim de password, apontando para o ClaimTypeReferenceId passwordWithEncrypt.
TrustFrameworkBase.xml1<ClaimsProvider>2<DisplayName>Local Account Sign In With Password Encryption</DisplayName>3<TechnicalProfiles>4<TechnicalProfile Id="login-NonInteractive-password-encryption">5<DisplayName>Local Account Sign In With Password Encryption</DisplayName>6<Protocol Name="OpenIdConnect" />7<Metadata>8<Item Key="ProviderName">https://sts.windows.net/</Item>9<Item Key="METADATA">https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration</Item>10<Item Key="authorization_endpoint">https://login.microsoftonline.com/{tenant}/oauth2/token</Item>11<Item Key="response_types">id_token</Item>12<Item Key="response_mode">query</Item>13<Item Key="scope">email openid</Item>14<!-- <Item Key="grant_type">password</Item> -->1516<!-- Policy Engine Clients -->17<Item Key="UsePolicyInRedirectUri">false</Item>18<Item Key="HttpBinding">POST</Item>19</Metadata>20<InputClaims>21<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" />22<InputClaim ClaimTypeReferenceId="passwordWithEncrypt" PartnerClaimType="password" Required="true" />23<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" AlwaysUseDefaultValue="true" />24<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" AlwaysUseDefaultValue="true" />25<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />26</InputClaims>27<OutputClaims>28<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />29<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid" />30<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />31<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />32<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />33<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />34<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />35</OutputClaims>36</TechnicalProfile>37</TechnicalProfiles>38</ClaimsProvider>
Seguindo, precisaremos incluir ambos os perfis no perfil técnico no fluxo de login, que é feito através do SelfAsserted-LocalAccountSignin-Email. Iremos adicioná-los dentro do ValidationTechnicalProfiles. Note que adicionei o fluxo de senha com criptografia antes do login convencional. Em um cenário onde o usuário já redefiniu a senha no portal do Azure, ele não precisaria passar pelo fluxo de criptografia; porém, ele ainda assim irá passar, aumentando o tempo na jornada de login. O que podemos fazer é criar um atributo customizado para definir se ele já redefiniu a senha pelo Azure AD B2C e evitar que ele passe por esse fluxo caso erre a senha. Contudo, isso fica para outro post.
TrustFrameworkBase.xml1<ValidationTechnicalProfiles>2<!-- Login flow with legacy encryption -->3<ValidationTechnicalProfile ReferenceId="REST-PasswordEncription" ContinueOnError="true" />4<ValidationTechnicalProfile ReferenceId="login-NonInteractive-password-encryption" ContinueOnError="true" ContinueOnSuccess="false">5<Preconditions>6<Precondition Type="ClaimsExist" ExecuteActionsIf="false">7<Value>passwordWithEncrypt</Value>8<Action>SkipThisValidationTechnicalProfile</Action>9</Precondition>10</Preconditions>11</ValidationTechnicalProfile>1213<ValidationTechnicalProfile ReferenceId="login-NonInteractive" ContinueOnError="true" ContinueOnSuccess="false"/>14</ValidationTechnicalProfiles>
A utilização do ContinueOnError="true" e do ContinueOnSuccess="false" é necessária, pois, sem o ContinueOnError="true", o perfil de validação já retornaria erro (em cenários onde o usuário já redefiniu a senha ou a sua API retornar algum erro) e não continuaria. Mas não se preocupe, caso os três perfis deem erro, o usuário receberá a mensagem de que a senha informada está errada.
Como duplicamos o perfil técnico do login-NonInteractive e o transformamos no login-NonInteractive-password-encryption, será necessário também incluir as informações de client_id, IdTokenAudience e resource_id, que ficam no TrustFrameworkExtensions.xml.
TrustFrameworkExtensions.xml1<ClaimsProvider>2<DisplayName>Local Account Sign In With Password Encryption</DisplayName>3<TechnicalProfiles>4<TechnicalProfile Id="login-NonInteractive-password-encryption">5<Metadata>6<Item Key="client_id">{Settings:ProxyIdentityExperienceFrameworkClientId}</Item>7<Item Key="IdTokenAudience">{Settings:IdentityExperienceFrameworkClientId}</Item>8</Metadata>9<InputClaims>10<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{Settings:ProxyIdentityExperienceFrameworkClientId}" />11<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="{Settings:IdentityExperienceFrameworkClientId}" />12</InputClaims>13</TechnicalProfile>14</TechnicalProfiles>15</ClaimsProvider>
Tudo pronto agora, basta compilar suas políticas e fazer o upload. Algo importante a ser reforçado é que o Azure AD B2C costuma ter um delay de até 30 minutos na propagação de novas políticas. Então, não se assuste caso você faça o upload e nada aconteça imediatamente. Uma prática que me ajuda bastante na hora de testar é deletar a TrustFrameworkBase.xml e esperar a propagação na minha SignUpOrSignin. Quando ela começa a dar erro, faço o upload da nova TrustFrameworkBase.xml e realizo os testes.
Abaixo, demonstro o fluxo do login. Inicialmente, com a senha "original" populada no Azure AD B2C do meu usuário (o login é realizado através do fluxo convencional). Em seguida, refaço o login, agora com a senha "real" do usuário (o login é realizado pelo fluxo de criptografia de senha, onde envio a senha, recebo a nova senha e efetuo o login com ela).

Caso tenha se perdido em algum momento, recomendo que volte e siga os passos com atenção. Se achar mais fácil, vou deixar o meu repositório do B2C do projeto, onde as alterações foram feitas no commit fc43d32.