Azure-Samples / active-directory-b2c-advanced-policies

Sample for use with Azure AD B2C with Custom Policies.
http://aka.ms/aadb2ccustom
MIT License
218 stars 143 forks source link

Trying to make custom policy work with username instead of email #29

Open mmaedler opened 6 years ago

mmaedler commented 6 years ago

Hi there,

I am desperately trying to setup a custom policy that uses the users username for login and also provides a registration. To achieve this I took the example here and replaced every occurance of email with username or signInName. However, no matter what combination of username, signInName or signInNames.userName I use, the registration fails because it seems like Active Directory fails to create the user since it throws this exception (or one alike):

 {
    "Key": "Exception",
    "Value": {
        "Kind": "Handled",
        "HResult": "80131500",
        "Message": "An error occurred while writing User claims using identifier claim type \"signInNames.userName\" in tenant \"mytenant.onmicrosoft.com\". Error returned was 400/Request_BadRequest: One or more property values specified are invalid.",
        "Data": {
            "TenantId": "mytenat.onmicrosoft.com",
            "PolicyId": "B2C_1A_signup_signin"
        },
        "Exception": {
            "Kind": "Handled",
            "HResult": "80131509",
            "Message": "The remote server returned an error: (400) Bad Request.",
            "Data": {}
        }
    }
}

The related technical profile where the exception is thrown looks as follows:

<TechnicalProfile Id="AAD-UserWriteUsingLogonName">
    <Metadata>
        <Item Key="Operation">Write</Item>
        <Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
    </Metadata>
    <IncludeInSso>false</IncludeInSso>
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="username" PartnerClaimType="signInNames.userName" Required="true" />
    </InputClaims>
    <PersistedClaims>
        <!-- Required claims -->
        <PersistedClaim ClaimTypeReferenceId="username" PartnerClaimType="signInNames.userName"/>
        <PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
        <PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password"/>
        <PersistedClaim ClaimTypeReferenceId="displayName" DefaultValue="unknown" />
        <PersistedClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration" />
        <!-- Optional claims. -->
        <PersistedClaim ClaimTypeReferenceId="givenName" />
        <PersistedClaim ClaimTypeReferenceId="surname" />
    </PersistedClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="objectId" />
        <OutputClaim ClaimTypeReferenceId="newUser" PartnerClaimType="newClaimsPrincipalCreated" />
        <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
        <OutputClaim ClaimTypeReferenceId="userPrincipalName" />
        <OutputClaim ClaimTypeReferenceId="signInNames.userName" />
    </OutputClaims>
    <IncludeTechnicalProfile ReferenceId="AAD-Common" />
    <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>

Since I have tried every possible combination I really do not now to proceed with this. Hopefully you can push me in the right direction...

Thanks!

PS: Here's the complete TrustFrameworkBase.xml for your reference: https://gist.github.com/mmaedler/452c8bfdf179ef706931d36f1bb93183

chrispadgettlivecom commented 6 years ago

The following line looks correct:

<PersistedClaim ClaimTypeReferenceId="username" PartnerClaimType="signInNames.userName" />

The following line looks wrong because the user object doesn't contain an email property:

<PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />

You can replace the preceding line with the following one:

<PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="strongAuthenticationEmailAddress" />

where the strongAuthenticationEmailAddress claim type is declared as follows:

<ClaimType Id="strongAuthenticationEmailAddress">
  <DisplayName>Authentication Email Address</DisplayName>
  <DataType>string</DataType>
</ClaimType>

See https://stackoverflow.com/questions/45699101/custom-b2c-policy-for-username-based-local-accounts/46282586#46282586 for a complete solution.

mmaedler commented 6 years ago

Hi @chrispadgettlivecom! Thanks for your quick reply and sorry for my confusing issue title which I have corrected now. Your saying I'd use strongAuthenticationEmailAddress — does that mean the user still needs to give a unique email address as this can be used to login? The problem is, that for our systems the email cannot be unique since it can be used for multiple accounts (hence the login with username instead of email address).

Thanks,

Moritz

mmaedler commented 6 years ago

And its me again. I have implemented the change you've suggested, but the error remains the same:

 "Message": "An error occurred while writing User claims using identifier claim type \"signInNames.userName\" in tenant \"llssocaps.onmicrosoft.com\". Error returned was 400/Request_BadRequest: One or more properties contains invalid values.",

It seems like Azure is not able to handle the signInNames.userName while saving the user into the directory. What is the field identifier for AD that stores the username?

chrispadgettlivecom commented 6 years ago

Hi @mmaedler

signInNames.userName is the correct claim.

Note that you can't persist an email address to the signInNames.userName claim.

Can you please paste your latest changes includes examples of the claim values that you are persisting to the Azure AD B2C directory?

If you persist the email address to the strongAuthenticationEmailAddress property, then the email address doesn't have to be unique, since it isn't acting as a sign-in name.

mmaedler commented 6 years ago

Hi @chrispadgettlivecom,

thanks again for your reply. Here you'll find the updated TrustFrameworkBase.xml: --> https://gist.github.com/mmaedler/c0d25989a400dbe884c4253d4ccc0b06

Maybe also a bit of a background of what I actually try to achieve: We want to add SSO to some of our apps and therefore need to migrate our user base into Azure AD B2C. For legacy reasons the users login could either be a username OR an email address. So I thought we simply use the username provider and migrate the users using whatever he/she had use as a login credential and simply put that into the username field in AD B2C. Obviously we also want to have new users register with whatever they feel is best for them — email or username. So what you are saying that it is not possible to store an email address into signInNames.userName?

Thanks again!

chrispadgettlivecom commented 6 years ago

Hi @mmaedler

If you attempt to create a local account with a "userName" sign-in type that is set to an email address sign-in value, then the following error is returned by the Azure AD Graph API:

{
    "odata.error": {
        "code": "Request_BadRequest",
        "message": {
            "lang": "en",
            "value": "One or more properties contains invalid values."
        },
        "date": "2018-07-29T04:25:16",
        "requestId": "407e5528-e937-4bbd-9c79-fd4f2026445c",
        "values": null
    }
}
mmaedler commented 6 years ago

Ok — just to be 100% clear on this: you are saying AD cannot persist an email address to the username field? If so, why can I create an user with exactly that via the API?

Also the error you posted is not the one that I receive:

{
                        "Key": "Exception",
                        "Value": {
                          "Kind": "Handled",
                          "HResult": "80131500",
                          "Message": "An error occurred while writing User claims using identifier claim type \"signInNames.userName\" in tenant \"mytenant.onmicrosoft.com\". Error returned was 400/Request_BadRequest: One or more properties contains invalid values.",
                          "Data": {
                            "TenantId": "mytenant.onmicrosoft.com",
                            "PolicyId": "B2C_1A_signup_signin"
                          },
                          "Exception": {
                            "Kind": "Handled",
                            "HResult": "80131509",
                            "Message": "The remote server returned an error: (400) Bad Request.",
                            "Data": {}
                          }
                        }

Any further ideas? Thanks a mil.

felickz commented 5 years ago

Ever get this working or find a public sample with UserName instead of Email? Have same concern - dont want email to be unique (store in othermails). Would be nice it was as simple as clicking this dropdown!

anweshars commented 3 years ago

Ok — just to be 100% clear on this: you are saying AD cannot persist an email address to the username field? If so, why can I create an user with exactly that via the API?

Also the error you posted is not the one that I receive:

{
                        "Key": "Exception",
                        "Value": {
                          "Kind": "Handled",
                          "HResult": "80131500",
                          "Message": "An error occurred while writing User claims using identifier claim type \"signInNames.userName\" in tenant \"mytenant.onmicrosoft.com\". Error returned was 400/Request_BadRequest: One or more properties contains invalid values.",
                          "Data": {
                            "TenantId": "mytenant.onmicrosoft.com",
                            "PolicyId": "B2C_1A_signup_signin"
                          },
                          "Exception": {
                            "Kind": "Handled",
                            "HResult": "80131509",
                            "Message": "The remote server returned an error: (400) Bad Request.",
                            "Data": {}
                          }
                        }

Any further ideas? Thanks a mil.

hey - could you get this working?

githubtomb commented 6 months ago

For those pulling their hair out:

You need to add this meta tag to your SelfAssertedAttributeProvider profile where you're asking the user for username and other details.

<Item Key="LocalAccountType">Username</Item>

See the example:

https://github.com/azure-ad-b2c/samples/blob/d502e3e173ff643061088d9259851c3bb6dc6ba8/policies/username-signup-or-signin/policy/TrustFrameworkExtensions_Username.xml#L146