Skip to content

Instantly share code, notes, and snippets.

@rbrayb
Created September 6, 2024 03:09
Show Gist options
  • Save rbrayb/060b2b58e5d600c67c9459e9dccb9284 to your computer and use it in GitHub Desktop.
Save rbrayb/060b2b58e5d600c67c9459e9dccb9284 to your computer and use it in GitHub Desktop.
Using a fake email server to validate OTP codes in Azure AD B2C
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
namespace Mail_Debug_Function
{
public static class MailDebug
{
[FunctionName("MailDebug")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
// Read form data
var formData = await req.ReadFormAsync();
string from = formData["from"];
string subject = formData["subject"];
string body = "The OTP is " + formData["body"];
string to = formData["to"];
var mailMessage = new MailMessage
{
From = new MailAddress(from),
Subject = subject,
Body = body,
IsBodyHtml = true
};
mailMessage.To.Add(to);
var smtpClient = new SmtpClient("app.debugmail.io", 9025)
{
Credentials = new NetworkCredential("16...56",
"86...49"),
EnableSsl = false
};
try
{
smtpClient.Send(mailMessage);
return (ActionResult)new OkObjectResult ("Email sent successfully.");
}
catch (Exception ex)
{
return new BadRequestObjectResult("Failed to send email: {ex.Message}"); // "Failed to send email.
}
}
}
}
<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="tenant.onmicrosoft.com" PolicyId="B2C_1A_MailDebug_DisplayControl_TFExtensions" PublicPolicyUri="http://tenant.onmicrosoft.com/B2C_1A_MailDebug_DisplayControl_TFExtensions">
<BasePolicy>
<TenantId>tenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensionsSAMLMeta</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="from">
<DisplayName>From</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="subject">
<DisplayName>From</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="body">
<DisplayName>From</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="to">
<DisplayName>From</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="Otp">
<DisplayName>Secondary One-time password</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="VerificationCode">
<DisplayName>Secondary Verification Code</DisplayName>
<DataType>string</DataType>
<UserHelpText>Enter your email verification code</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<ClaimType Id="message">
<DisplayName>Email message</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="codeIntro">
<DisplayName>Intro</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="signature">
<DisplayName>Email signature</DisplayName>
<DataType>string</DataType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<ClaimsTransformation Id="GetLocalizedStringsForEmail" TransformationMethod="GetLocalizedStringsTransformation">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="subject" TransformationClaimType="email_subject"/>
<OutputClaim ClaimTypeReferenceId="message" TransformationClaimType="email_message"/>
<OutputClaim ClaimTypeReferenceId="codeIntro" TransformationClaimType="email_code"/>
<OutputClaim ClaimTypeReferenceId="signature" TransformationClaimType="email_signature"/>
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="CopyOTP" TransformationMethod="CopyClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="otp" TransformationClaimType="inputClaim"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="body" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
<ContentDefinitions>
<ContentDefinition Id="api.localaccountsignup">
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.7</DataUri>
<LocalizedResourcesReferences MergeBehavior="Prepend">
<LocalizedResourcesReference Language="en" LocalizedResourcesReferenceId="api.custom-email.en"/>
</LocalizedResourcesReferences>
</ContentDefinition>
<ContentDefinition Id="api.localaccountsignin">
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.7</DataUri>
<LocalizedResourcesReferences MergeBehavior="Prepend">
<LocalizedResourcesReference Language="en" LocalizedResourcesReferenceId="api.custom-email.en"/>
</LocalizedResourcesReferences>
</ContentDefinition>
<ContentDefinition Id="api.localaccountpasswordreset">
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
<LocalizedResourcesReferences MergeBehavior="Prepend">
<LocalizedResourcesReference Language="en" LocalizedResourcesReferenceId="api.custom-email.en"/>
</LocalizedResourcesReferences>
</ContentDefinition>
</ContentDefinitions>
<Localization Enabled="true">
<SupportedLanguages DefaultLanguage="en" MergeBehavior="ReplaceAll">
<SupportedLanguage>en</SupportedLanguage>
<SupportedLanguage>es</SupportedLanguage>
</SupportedLanguages>
<LocalizedResources Id="api.custom-email.en">
<LocalizedStrings>
<!--Email template parameters-->
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_subject">Contoso account email verification code</LocalizedString>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_message">Thanks for validating the account</LocalizedString>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_code">Your code is</LocalizedString>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_signature">Sincerely</LocalizedString>
<!-- Display control sign-up UI elements-->
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationControl" StringId="intro_msg">Verification is necessary. Please click Send button.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationControl" StringId="success_send_code_msg">Verification code has been sent to your inbox. Please copy it to the input box below.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationControl" StringId="failure_send_code_msg">We are having trouble verifying your email address. Please enter a valid email address and try again.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationControl" StringId="success_verify_code_msg">E-mail address verified. You can now continue.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationControl" StringId="failure_verify_code_msg">We are having trouble verifying your email address. Please try again.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationControl" StringId="but_send_code">Send verification code</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationControl" StringId="but_verify_code">Verify code</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationControl" StringId="but_send_new_code">Send new code</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationControl" StringId="but_change_claims">Change e-mail</LocalizedString>
<!-- Display control password reset UI elements-->
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="intro_msg">Verification is necessary. Please click Send button.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="success_send_code_msg">Verification code has been sent to your inbox. Please copy it to the input box below.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="failure_send_code_msg">We are having trouble verifying your email address. Please enter a valid email address and try again.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="success_verify_code_msg">E-mail address verified. You can now continue.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="failure_verify_code_msg">We are having trouble verifying your email address. Please try again.</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="but_send_code">Send verification code</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="but_verify_code">Verify code</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="but_send_new_code">Send new code</LocalizedString>
<LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="but_change_claims">Change e-mail</LocalizedString>
<!-- Claims-->
<LocalizedString ElementType="ClaimType" ElementId="emailVerificationCode" StringId="DisplayName">Verification Code</LocalizedString>
<LocalizedString ElementType="ClaimType" ElementId="emailVerificationCode" StringId="UserHelpText">Verification code received in the email.</LocalizedString>
<LocalizedString ElementType="ClaimType" ElementId="emailVerificationCode" StringId="AdminHelpText">Verification code received in the email.</LocalizedString>
<LocalizedString ElementType="ClaimType" ElementId="email" StringId="DisplayName">Email</LocalizedString>
<!-- Email validation error messages-->
<LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfSessionDoesNotExist">You have exceeded the maximum time allowed.</LocalizedString>
<LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfMaxRetryAttempted">You have exceeded the number of retries allowed.</LocalizedString>
<LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfInvalidCode">You have entered the wrong code.</LocalizedString>
<LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfSessionConflict">Cannot verify the code, please try again later.</LocalizedString>
<LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfVerificationFailedRetryAllowed">The verification has failed, please try again.</LocalizedString>
</LocalizedStrings>
</LocalizedResources>
</Localization>
<DisplayControls>
<DisplayControl Id="emailVerificationControl" UserInterfaceControlType="VerificationControl">
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="email" Required="true"/>
<DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" Required="true"/>
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email"/>
</OutputClaims>
<Actions>
<Action Id="SendCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="GenerateOtp"/>
<!-- <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SendOtp"/> -->
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="REST-API-Mail"/>
</ValidationClaimsExchange>
</Action>
<Action Id="VerifyCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="VerifyOtp"/>
</ValidationClaimsExchange>
</Action>
</Actions>
</DisplayControl>
<DisplayControl Id="emailVerificationSSPRControl" UserInterfaceControlType="VerificationControl">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true"/>
</InputClaims>
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="email" Required="true"/>
<DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" Required="true"/>
</DisplayClaims>
<OutputClaims/>
<Actions>
<Action Id="SendCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="AAD-CheckEmailAddressExists"/>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="GenerateOtp"/>
<!-- <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SendOtp"> -->
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="REST-API-Mail">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationClaimsExchangeTechnicalProfile>
</ValidationClaimsExchange>
</Action>
<Action Id="VerifyCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="VerifyOtp"/>
</ValidationClaimsExchange>
</Action>
</Actions>
</DisplayControl>
</DisplayControls>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>One time password technical profiles</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="GenerateOtp">
<DisplayName>Generate one time password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="Operation">GenerateCode</Item>
<Item Key="CodeExpirationInSeconds">60</Item>
<Item Key="CodeLength">6</Item>
<Item Key="CharacterSet">0-9</Item>
<Item Key="ReuseSameCode">true</Item>
<Item Key="NumRetryAttempts">2</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="otp" PartnerClaimType="otpGenerated"/>
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="VerifyOtp">
<DisplayName>Verify one time password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="Operation">VerifyCode</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier"/>
<InputClaim ClaimTypeReferenceId="verificationCode" PartnerClaimType="otpToVerify"/>
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>RestfulProvider</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-API-Mail">
<DisplayName>Send mail</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="ServiceUrl">https://name.azurewebsites.net/api/MailDebug</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Form</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopyOTP"/>
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="from" DefaultValue="elvis@company.co.nz" AlwaysUseDefaultValue="true"/>
<InputClaim ClaimTypeReferenceId="subject" DefaultValue="My song" AlwaysUseDefaultValue="true"/>
<InputClaim ClaimTypeReferenceId="body" />
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="to" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmailOTP">
<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>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer"/>
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GetLocalizedStringsForEmail"/>
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email"/>
</InputClaims>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl"/>
<DisplayClaim ClaimTypeReferenceId="displayName" Required="true"/>
<DisplayClaim ClaimTypeReferenceId="givenName" Required="true"/>
<DisplayClaim ClaimTypeReferenceId="surName" Required="true"/>
<DisplayClaim ClaimTypeReferenceId="newPassword" Required="true"/>
<DisplayClaim ClaimTypeReferenceId="reenterPassword" Required="true"/>
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true"/>
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true"/>
<OutputClaim ClaimTypeReferenceId="reenterPassword" 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"/>
<OutputClaim ClaimTypeReferenceId="givenName"/>
<OutputClaim ClaimTypeReferenceId="surName"/>
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail"/>
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD"/>
</TechnicalProfile>
<TechnicalProfile Id="AAD-CheckEmailAddressExists">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Reset Password</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GetLocalizedStringsForEmail"/>
</InputClaimsTransformations>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationSSPRControl"/>
</DisplayClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignInOTP">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange"/>
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmailOTP"/>
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when in the token. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/>
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>
</UserJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignInOTP"/>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect"/>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}"/>
</OutputClaims>
<SubjectNamingInfo ClaimType="sub"/>
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment