Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added sign-in-sign-up-passwordless custom policy #328

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TrustFrameworkPolicy
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
PolicySchemaVersion="0.3.0.0"
TenantId="yourtenant.onmicrosoft.com"
PolicyId="B2C_1A_Passwordless_SUSI_TrustFrameworkExtensions"
PublicPolicyUri="http://yourtenant.onmicrosoft.com/B2C_1A_Passwordless_SUSI_TrustFrameworkExtensions">

<BasePolicy>
<TenantId>yourtenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions_Samples_MFA</PolicyId>
</BasePolicy>

<BuildingBlocks>
<ClaimsSchema>

<ClaimType Id="passwordGUID">
<DataType>string</DataType>
<UserInputType>TextBox</UserInputType>
</ClaimType>

<ClaimType Id="mfaErrorMessage">
<DisplayName>MFA not set</DisplayName>
<DataType>string</DataType>
<UserInputType>Paragraph</UserInputType>
</ClaimType>

</ClaimsSchema>

<ClaimsTransformations>

<ClaimsTransformation Id="CreatePasswordGUID" TransformationMethod="CreateRandomString">
<InputParameters>
<InputParameter Id="randomGeneratorType" DataType="string" Value="GUID"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="passwordGUID" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>

<ClaimsTransformation Id="CreatePassword" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="passwordGUID" TransformationClaimType="inputClaim"/>
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="Q1W2E3R4T5Y6hjdgjahgjhjheg@#$%JGHJGHJGHJgfh-{0}"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="newPassword" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>

<ClaimsTransformation Id="CreateMFAErrorMessage" TransformationMethod="CreateStringClaim">
<InputParameters>
<InputParameter Id="value" DataType="string" Value="You have not yet proofed up for MFA. Please proof up before signing in"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="mfaErrorMessage" TransformationClaimType="createdClaim"/>
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>

</BuildingBlocks>

<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>

<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail-Passwordless">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
<Item Key="language.button_continue">Create</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer"/>
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true"/>
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true"/>
<OutputClaim ClaimTypeReferenceId="authenticationSource"/>
<OutputClaim ClaimTypeReferenceId="newUser"/>

<!-- Optional claims, to be collected from the user -->
<OutputClaim ClaimTypeReferenceId="displayName" Required="true"/>
<OutputClaim ClaimTypeReferenceId="givenName" Required="true"/>
<OutputClaim ClaimTypeReferenceId="surName" Required="true"/>
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail"/>
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD"/>
</TechnicalProfile>

<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<InputClaimsTransformations>
<!-- Create random password -->
<InputClaimsTransformation ReferenceId="CreatePasswordGUID"/>
<InputClaimsTransformation ReferenceId="CreatePassword"/>
</InputClaimsTransformations>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password"/>
</PersistedClaims>
</TechnicalProfile>

<TechnicalProfile Id="AAD-UserReadUsingEmailAddress">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber"/>
</OutputClaims>
</TechnicalProfile>

<TechnicalProfile Id="PhoneFactor-InputOrVerify">
<Metadata>
<!-- Sample action required: If you want the option of a phone call as well as a SMS OTP, comment this out -->
<Item Key="setting.authenticationMode">sms</Item>
</Metadata>
</TechnicalProfile>

</TechnicalProfiles>
</ClaimsProvider>

<ClaimsProvider>
<DisplayName>Local Account Email</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-LocalAccountEmailSignin">
<DisplayName>Local Account Email Signin</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="SignUpTarget">SignUpWithLogonUsernameExchange</Item>
<Item Key="setting.operatingMode">Email</Item>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email"/>
</OutputClaims>
</TechnicalProfile>

</TechnicalProfiles>
</ClaimsProvider>

<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-Error">
<DisplayName>MFA error message</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
<Item Key="setting.showContinueButton">false</Item>
<Item Key="setting.showCancelButton">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="mfaErrorMessage"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="mfaErrorMessage"/>
</OutputClaims>
</TechnicalProfile>

<TechnicalProfile Id="SelfAsserted-MFAError">
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateMFAErrorMessage"/>
</InputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-Error"/>
</TechnicalProfile>

</TechnicalProfiles>
</ClaimsProvider>


</ClaimsProviders>

<UserJourneys>
<UserJourney Id="SignUpOrSignIn-Passwordless">
<OrchestrationSteps>

<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange"/>
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountEmailSignin"/>
</ClaimsExchanges>
</OrchestrationStep>

<!-- Check if the user has selected to sign in using one of the social providers -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>email</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail-Passwordless"/>
</ClaimsExchanges>
</OrchestrationStep>

<!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent
in the token. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress"/>
</ClaimsExchanges>
</OrchestrationStep>

<!-- Throw error if user not proofed up -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>newUser</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>strongAuthenticationPhoneNumber</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAssertedMFAError" TechnicalProfileReferenceId="SelfAsserted-MFAError"/>
</ClaimsExchanges>
</OrchestrationStep>

<!-- This step checks whether there's a phone number on record, for the user. If found, then the user is challenged to verify it. -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>isActiveMFASession</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify"/>
</ClaimsExchanges>
</OrchestrationStep>

<!-- Save MFA phone number: The precondition verifies whether the user provided a new number in the
previous step. If so, then the phone number is stored in the directory for future authentication
requests. -->
<OrchestrationStep Order="6" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>newPhoneNumberEntered</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId"/>
</ClaimsExchanges>
</OrchestrationStep>

<OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/>

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

</TrustFrameworkPolicy>



Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<TrustFrameworkPolicy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" PolicySchemaVersion="0.3.0.0"
TenantId="yourtenant.onmicrosoft.com" PolicyId="B2C_1A_Passwordless_SignUpSignIn" PublicPolicyUri="http://yourtenant.onmicrosoft.com/B2C_1A_Passwordless_SignUpSignIn">

<BasePolicy>
<TenantId>yourtenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_Passwordless_SUSI_TrustFrameworkExtensions</PolicyId>
</BasePolicy>

<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn-Passwordless"/>

<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect"/>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName"/>
<OutputClaim ClaimTypeReferenceId="givenName"/>
<OutputClaim ClaimTypeReferenceId="surname"/>
<OutputClaim ClaimTypeReferenceId="email"/>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
</OutputClaims>
<SubjectNamingInfo ClaimType="sub"/>
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
59 changes: 59 additions & 0 deletions policies/sign-in-sign-up-passwordless/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# A B2C IEF Custom Policy - Sign Up and Sign In passwordless flow

## Community Help and Support
Use [Stack Overflow](https://stackoverflow.com/questions/tagged/azure-ad-b2c) to get support from the community. Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before. Make sure that your questions or comments are tagged with [azure-ad-b2c].
If you find a bug in the sample, please raise the issue on [GitHub Issues](https://github.com/azure-ad-b2c/samples/issues).
To provide product feedback, visit the Azure Active Directory B2C [Feedback page](https://feedback.azure.com/forums/169401-azure-active-directory?category_id=160596).

## Scenario
In this scenario, the user can use a passwordless sign up and sign in flow. The passwordless component is delivered via a SMS OTP on the user's phone.

Because the sign up is passwordless, the user is not required to enter and verify a password. B2C requires a password to create a user so the password is generated in the custom policy. To make it random across users, a GUID is included as part of the password. The user is not made aware of their password.

To stop users randomly typing in email addresses when signing in and then proofing up, the user can only sign in if that email address has a `strongAuthenticationPhoneNumber` attribute associated with it i.e. the user has already proofed up.

## Prerequisites
- You can automate the prerequisites by visiting this [site](https://aka.ms/iefsetup) if you already have an Azure AD B2C tenant. Some policies can be deployed directly through this app via the **Experimental** menu.

- You will require to create an Azure AD B2C directory, see the guidance [here](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant).

- To use the sample policies in this repo, follow the instructions here to setup your AAD B2C environment for Custom Policies [here](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-get-started-custom).

- For any custom policy sample which makes use of Extension attributes, follow the guidance [here](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-create-custom-attributes-profile-edit-custom#create-a-new-application-to-store-the-extension-properties) and [here](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-create-custom-attributes-profile-edit-custom#modify-your-custom-policy-to-add-the-applicationobjectid). The `AAD-Common` Technical profile will always need to be modified to use your `ApplicationId` and `ObjectId`.

## How it works

When the user signs up, the `LocalAccountSignUpWithLogonEmail-Passwordless` technical profile asks the user to enter an email address and verify it.

The user then enters the display name, given name and surname.

The `AAD-UserWriteUsingLogonEmail` validation technical profile first calls the InputClaimsTransformation `CreatePasswordGUID` that creates a random GUID and then calls the InputClaimsTransformation `CreatePassword` that adds this to a long string to create a random password. This information is then written to B2C.

The `PhoneFactor-InputOrVerify` technical profile then asks the user to proof up and validate the phone number via a SMS OTP.

The `AAD-UserWritePhoneNumberUsingObjectId` technical profile then saves the phone number.

The JWT is then sent to the user.

When the user signs in, the `SelfAsserted-LocalAccountEmailSignin` technical profile asks them to enter an email address and then the `AAD-UserReadUsingEmailAddress` technical profile is called to check that the user exists.

The account has to have a `strongAuthenticationPhoneNumber` attribute associated with it otherwise an error is displayed.

If the user exists, the `PhoneFactor-InputOrVerify` technical profile is used to verify the user by means of a SMS OTP sent to the phone number they proofed up with.

The JWT is then sent to the user.

## Notes

This sample policy is based on [SocialAndLocalAccountsWithMfa starter pack](https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/tree/master/SocialAndLocalAccountsWithMfa).

The reference to `B2C_1A_TrustFrameworkExtensions_Samples_MFA` file in the sample is to the `TrustFrameworkExtensions` file in the sample pack.

If you want the option of a phone call as well as a SMS OTP, comment out the `setting.authenticationMode` in this sample.

## Unit Tests
1. Sign up, verify email, and verify that you are asked to proof up.
2. Sign in with an invalid email. This should cause an error.
3. Sign in with a valid email. Check that you are used to verify via a SMS OTP.
4. Create an user via the portal. Then try and sign in with that user. Because that user has not yet proofed up, an error will be thrown.

1 change: 1 addition & 0 deletions readmeV2.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Samples are available for the following categories
|[Password-less sign-in with email verification](policies/passwordless-email)|Password-less authentication is a type of authentication where user doesn't need to sign-in with their password. This is commonly used in B2C scenarios where users use your application infrequently and tend to forget their password. This sample policy demonstrates how to allow user to sign-in, simply by providing and verifying the sign-in email address using OTP code (one time password).|[Go](https://b2ciefsetupapp.azurewebsites.net/Home/Experimental?sampleFolderName=passwordless-email)|
|[Login with Phone Number](policies/signup-signin-with-phone-number)|An example set of policies for password-less login via Phone Number (SMS or Phone Call).|[Go](https://b2ciefsetupapp.azurewebsites.net/Home/Experimental?sampleFolderName=signup-signin-with-phone-number)|
|[Guest Sign-up or Sign-in](policies/guest-signup)|An example of granting a guest user access to the system without the need to register the user in the users directory. | NA |
|[Password-less sign-in & sign-up with email verification](policies/sign-in-sign-up-passwordless)|This sample policy demonstrates how to allow user to sign-in and sign-up, simply by providing and verifying the sign-up email address using OTP code (one time password) and sign-in using OTP code.| NA |

## Multi factor
|Sample name |Description |Quick deploy|
Expand Down