Testing out Azure AD External Identities

Azure AD External Identities is essentially a new umbrella expression for existing Azure AD features such as the ability to use Google as Identity Provider, B2B guest invitations, Terms of use, Entitlement Management etc., with the new features launched during MS Build 2020 being guest user self service sign-up through “User flows” and Facebook as Identity Provider.

In this blog post i will dive into these new things, to see what these new features can provide of value to Azure AD customers.

First of all, let’s enable the new guest self-service sign up feature:

Navigating to “Customer user attributes” in the menu, points us to the url https://portal.azure.com/#blade/Microsoft_AAD_IAM/CompanyRelationshipsMenuBlade/UserAttributes. I find the naming “CompanyRelationships” interesting here, as there has been talk about being able to add additional attributes to B2B relationships for ages, and this might be the way they finally approached it.

There are a few default attributes defined, and we can add custom string, boolean and integer attributes:

A few interesting notes for techies.

  • Also, when adding the custom attributes, a new app registration pops up in my tenant (did not notice when this poped up though), being the app reg that will contain all my extension propertes:

Anyway, let’s go ahead and define a user flow!

First thing i notice is that the policy is named with a prefix B2X_1_. In a B2C tenant, when creating a policy there, they are called “B2C_1_”.

I have not defined any additional identity provider yet, so only one available there. Let’s add my custom attributes for collection:


Clicking next creates the policy:


Clicking on the policy opens up an extended set of features that we already know from B2C:


We have already configured identity provider and user attributes, so let’s check out page layouts. First of all you can configure the order the collected attributes are presented to the user:

More importantly, you can configure the user input type. Boolean becoming a text box makes little sense, so let’s change it:

My expectation is that now, the user is presented with a radio button and the value will be true if i selected “I accept”. Moving on to languages.

Of course, “Age” is not called “Age” in all other languages, and you can provide custom translations for your custom fields for 35 languages. But not only that, you can also override the translation of error messages etc. Here is an example JSON (downloaded directly from the portal):

{
  "LocalizedStrings": [
    {
      "ElementType": "ClaimType",
      "ElementId": "extension_Age",
      "StringId": "DisplayName",
      "Override": true,
      "Value": "Age"
    },
    {
      "ElementType": "ClaimType",
      "ElementId": "extension_Age",
      "StringId": "UserHelpText",
      "Override": true,
      "Value": ""
    },
    {
      "ElementType": "ClaimType",
      "ElementId": "extension_Marketing",
      "StringId": "DisplayName",
      "Override": true,
      "Value": "Marketing"
    },
    {
      "ElementType": "ClaimType",
      "ElementId": "extension_Marketing",
      "StringId": "UserHelpText",
      "Override": true,
      "Value": "Yes, please send me emails"
    },
    {
      "ElementType": "ClaimType",
      "ElementId": "extension_Nickname",
      "StringId": "DisplayName",
      "Override": true,
      "Value": "Nickname"
    },
    {
      "ElementType": "ClaimType",
      "ElementId": "extension_Nickname",
      "StringId": "UserHelpText",
      "Override": true,
      "Value": ""
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "ServiceThrottled",
      "Override": false,
      "Value": "Det er for mange forespørsler for øyeblikket. Vent en stund, og prøv på nytt."
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfChallengeExpired",
      "Override": false,
      "Value": "Koden er utløpt. Be en ny kode."
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfClaimNotVerified",
      "Override": false,
      "Value": "Krav ikke bekreftet: {0}"
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfClaimsPrincipalAlreadyExists",
      "Override": false,
      "Value": "Du er allerede registrert, velg tilbake-knappen og logg på i stedet."
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfIncorrectPattern",
      "Override": false,
      "Value": "Ugyldig mønster for: {0}"
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfInternalError",
      "Override": false,
      "Value": "Vi har problemer med å bekrefte e-postadressen din. Prøv på nytt senere."
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfInvalidInput",
      "Override": false,
      "Value": "{0} har ugyldige inndata."
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfMissingRequiredElement",
      "Override": false,
      "Value": "Mangler nødvendig element: {0}"
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfThrottled",
      "Override": false,
      "Value": "Det har blitt registrert for mange forespørsler for å bekrefte denne e-postadressen. Vent litt og prøv på nytt."
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfValidationError",
      "Override": false,
      "Value": "Feil under validering av: {0}"
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfVerificationFailedNoRetry",
      "Override": false,
      "Value": "Du har gjort for mange ugyldige forsøk. Prøv på nytt senere."
    },
    {
      "ElementType": "ErrorMessage",
      "ElementId": null,
      "StringId": "UserMessageIfVerificationFailedRetryAllowed",
      "Override": false,
      "Value": "Den koden er ugyldig. Prøv på nytt."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "alert_message",
      "Override": false,
      "Value": "Er du sikker på at du vil avbryte angivelse av opplysninger?"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "alert_no",
      "Override": false,
      "Value": "Nei"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "alert_title",
      "Override": false,
      "Value": "Avbryt angivelse av opplysninger"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "alert_yes",
      "Override": false,
      "Value": "Ja"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "button_cancel",
      "Override": false,
      "Value": "Avbryt"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "button_continue",
      "Override": false,
      "Value": "Fortsett"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "cancel_message",
      "Override": false,
      "Value": "Brukeren har avbrutt angivelse av selvbekreftet informasjon"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "day",
      "Override": false,
      "Value": "Dag"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "error_fieldIncorrect",
      "Override": false,
      "Value": "Ett eller flere felt er fylt ut på feil måte. Kontroller oppføringene og prøv på nytt."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "error_passwordEntryMismatch",
      "Override": false,
      "Value": "Feltene for passordoppføring samsvarer ikke. Angi det samme passordet i begge feltene, og prøv på nytt."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "error_requiredFieldMissing",
      "Override": false,
      "Value": "Det mangler et obligatorisk felt. Fyll ut alle obligatoriske felt, og prøv på nytt."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "helplink_text",
      "Override": false,
      "Value": "Hva er dette?"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "initial_intro",
      "Override": false,
      "Value": "Oppgi følgende opplysninger."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "month",
      "Override": false,
      "Value": "Måned"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "months",
      "Override": false,
      "Value": "Januar, februar, mars, april, mai, juni, juli, august, september, oktober, november, desember"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "preloader_alt",
      "Override": false,
      "Value": "Vent litt"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "required_field",
      "Override": false,
      "Value": "Denne informasjonen er obligatorisk."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_but_default",
      "Override": false,
      "Value": "Standard"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_but_edit",
      "Override": false,
      "Value": "Endre e-postadresse"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_but_resend",
      "Override": false,
      "Value": "Send ny kode"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_but_send",
      "Override": false,
      "Value": "Send bekreftelseskode"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_but_verify",
      "Override": false,
      "Value": "Bekreft kode"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_fail_code_expired",
      "Override": false,
      "Value": "Koden er utløpt. Be en ny kode."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_fail_no_retry",
      "Override": false,
      "Value": "Du har gjort for mange ugyldige forsøk. Prøv på nytt senere."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_fail_retry",
      "Override": false,
      "Value": "Den koden er ugyldig. Prøv på nytt."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_fail_server",
      "Override": false,
      "Value": "Vi har problemer med å bekrefte e-postadressen din. Angi en gyldig e-postadresse og prøv på nytt."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_fail_throttled",
      "Override": false,
      "Value": "Det har blitt registrert for mange forespørsler for å bekrefte denne e-postadressen. Vent litt og prøv på nytt."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_incorrect_format",
      "Override": false,
      "Value": "Ugyldig format."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_info_msg",
      "Override": false,
      "Value": "Bekreftelseskoden er sendt til innboksen. Kopier den til inndataboksen nedenfor."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_input",
      "Override": false,
      "Value": "Verifiseringskode"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_intro_msg",
      "Override": false,
      "Value": "Bekreftelse er nødvendig. Klikk Send-knappen."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_sent",
      "Override": false,
      "Value": "Bekreftelseskoden er sendt til:"
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_success_msg",
      "Override": false,
      "Value": "E-postadressen er bekreftet. Du kan nå fortsette."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "ver_success_screenreader_msg",
      "Override": false,
      "Value": "E-postadressen er bekreftet. Du kan nå fortsette."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "verifying_blurb",
      "Override": false,
      "Value": "Vent mens informasjonen din behandles."
    },
    {
      "ElementType": "UxElement",
      "ElementId": null,
      "StringId": "year",
      "Override": false,
      "Value": "År"
    }
  ],
  "LocalizedCollections": [
    {
      "ElementType": "ClaimType",
      "ElementId": "extension_Marketing",
      "TargetCollection": "Restriction",
      "Override": true,
      "Items": [
        {
          "Name": "I accept",
          "Value": "true"
        },
        {
          "Name": "No thank you",
          "Value": "false"
        }
      ]
    }
  ]
}

As you can see, even my radio button values can be translated!

Ok, let’s simply go ahead and register a demo application, that we want to assign to this user flow:

Now, after registering the application, I click “Add application” on the B2X_1_Blogpost user flow, and select the application:


No more settings available. Wonder how we trigger the user flow? Triggering the authorize endpoint – https://login.microsoftonline.com/M365x030202.onmicrosoft.com/oauth2/v2.0/authorize?client_id=d76ea80d-73ea-4df6-9503-ca10742deedd&response_type=code&redirect_uri=http://localhost:8888&response_mode=query&scope=openid&state=12345 – without any additional settings than usual returns the regular Azure AD signin experience, but with a new option:

Experience after assigning a user flow
Experience before assigning application to user flow
I am not already a guest in this tenant
My home tenant authentication
Wasn’t this the whole point?

Ok, did not work as expected. Apparently, for Azure AD users, you must be preinvited as a guest. Let’s try adding Facebook signin (guide here https://docs.microsoft.com/en-us/azure/active-directory/b2b/facebook-federation):

Facebook has been added, and I should be able to select it as my one of my identity providers:

Now I have a new feature on the Create Account screen – a Facebook option:

Facebook consent screen

And voila, my expected experience is here:

Submitting and signing in works like a charm. Now, let’s have a look in the Graph Explorer, how this user looks:

{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#users/$entity",
    "id": "f78b1425-d5d6-4de8-89b6-e8975dcb58d9",
    "deletedDateTime": null,
    "accountEnabled": true,
    "ageGroup": null,
    "businessPhones": [],
    "city": null,
    "createdDateTime": "2020-05-21T23:12:20Z",
    "creationType": "SelfServiceSignup",
    "companyName": null,
    "consentProvidedForMinor": null,
    "country": null,
    "department": null,
    "displayName": "Marius Solbakken Mellum",
    "employeeId": null,
    "faxNumber": null,
    "givenName": "Marius",
    "imAddresses": [],
    "infoCatalogs": [],
    "isResourceAccount": null,
    "jobTitle": null,
    "legalAgeGroupClassification": null,
    "mail": "marius.msm@gmail.com",
    "mailNickname": "unknown",
    "mobilePhone": null,
    "onPremisesDistinguishedName": null,
    "officeLocation": null,
    "onPremisesDomainName": null,
    "onPremisesImmutableId": null,
    "onPremisesLastSyncDateTime": null,
    "onPremisesSecurityIdentifier": null,
    "onPremisesSamAccountName": null,
    "onPremisesSyncEnabled": null,
    "onPremisesUserPrincipalName": null,
    "otherMails": [
        "marius.msm@gmail.com"
    ],
    "passwordPolicies": "DisablePasswordExpiration",
    "passwordProfile": null,
    "postalCode": null,
    "preferredDataLocation": null,
    "preferredLanguage": null,
    "proxyAddresses": [
        "SMTP:marius.msm@gmail.com"
    ],
    "refreshTokensValidFromDateTime": "2020-05-21T23:12:20Z",
    "showInAddressList": null,
    "signInSessionsValidFromDateTime": "2020-05-21T23:12:20Z",
    "state": null,
    "streetAddress": null,
    "surname": "Mellum",
    "usageLocation": null,
    "userPrincipalName": "marius.msm_gmail.com#EXT#@M365x030202.onmicrosoft.com",
    "externalUserState": null,
    "externalUserStateChangeDateTime": null,
    "userType": "Guest",
    "extension_8974121d38914d1f94bcc79d7cc40b7a_Age": 32,
    "extension_8974121d38914d1f94bcc79d7cc40b7a_Marketing": true,
    "assignedLicenses": [],
    "assignedPlans": [],
    "deviceKeys": [],
    "identities": [
        {
            "signInType": "federated",
            "issuer": "facebook.com",
            "issuerAssignedId": "10163865636000314"
        },
        {
            "signInType": "userPrincipalName",
            "issuer": "M365x030202.onmicrosoft.com",
            "issuerAssignedId": "marius.msm_gmail.com#EXT#@M365x030202.onmicrosoft.com"
        }
    ],
    "onPremisesExtensionAttributes": {
        "extensionAttribute1": null,
        "extensionAttribute2": null,
        "extensionAttribute3": null,
        "extensionAttribute4": null,
        "extensionAttribute5": null,
        "extensionAttribute6": null,
        "extensionAttribute7": null,
        "extensionAttribute8": null,
        "extensionAttribute9": null,
        "extensionAttribute10": null,
        "extensionAttribute11": null,
        "extensionAttribute12": null,
        "extensionAttribute13": null,
        "extensionAttribute14": null,
        "extensionAttribute15": null
    },
    "onPremisesProvisioningErrors": [],
    "provisionedPlans": []
}

Notice especially extension_8974121d38914d1f94bcc79d7cc40b7a_Marketing = true and identities containing my facebook assigned ID.

Now, let’s test one final thing. First I invite my work account as a guest. Then I update the user attributes of the user flow to “Job Title” only:

What happens when the users sign in? Let’s try Facebook first.

Typing email address expecting to be sent to Facebook
Sent to my Microsoft Account instead…
Which as expected failed.

Instead, I needed to go through the “Create one!” experience, using Facebook. This worked, but is a poor user experience. Also, after changing the user flow, no attribute input was requested from the user.

Now, what happens to the work account?

Nothing. It works for signing in, now that it is already a guest, but no additional attributes are requested as input.

So, that’s it for now. This feature has great potential, but needs some fixing 🙂

5 thoughts on “Testing out Azure AD External Identities

  1. Hi Marius,

    I hope you’re well and keeping safe amidst the COVID-19 situation.

    My name is Sangeeta Ranjit and I’m a Program Manager on the External Identities team. First of all, thank you very much for trying the External Identities feature. Would it be possible to talk about the issues and potential bug that you might have identified?

    With regards,
    Sangeeta

  2. Hi Marius,

    That is a nice post! I wonder how you can get the IssuerAssignedId for Google and Facebook, could you please explain more?

    Thank you,
    Phong Vo

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s