Created
July 17, 2019 04:13
-
-
Save rbrayb/1025b45ff224875dac6bbb13970f1cdb to your computer and use it in GitHub Desktop.
Using a JWT to invoke an Azure AD B2C flow using id_token_hint
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Mvc; | |
using System.IdentityModel.Tokens.Jwt; | |
using Microsoft.IdentityModel.Tokens; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.Extensions.Options; | |
using System.IO; | |
using System.Security.Cryptography.X509Certificates; | |
using Microsoft.AspNetCore.Http.Extensions; | |
using AADB2C.Invite.Models; | |
using System.Net.Mail; | |
using System.Net; | |
namespace AADB2C.Invite.Controllers | |
{ | |
public class HomeController : Controller | |
{ | |
private static Lazy<X509SigningCredentials> SigningCredentials; | |
private readonly AppSettingsModel AppSettings; | |
private readonly IHostingEnvironment HostingEnvironment; | |
// Sample: Inject an instance of an AppSettingsModel class into the constructor of the consuming class, | |
// and let dependency injection handle the rest | |
public HomeController(IOptions<AppSettingsModel> appSettings, IHostingEnvironment hostingEnvironment) | |
{ | |
this.AppSettings = appSettings.Value; | |
this.HostingEnvironment = hostingEnvironment; | |
// Sample: Load the certificate with a private key (must be pfx file) | |
SigningCredentials = new Lazy<X509SigningCredentials>(() => | |
{ | |
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); | |
certStore.Open(OpenFlags.ReadOnly); | |
X509Certificate2Collection certCollection = certStore.Certificates.Find( | |
X509FindType.FindByThumbprint, | |
this.AppSettings.SigningCertThumbprint, | |
false); | |
// Get the first cert with the thumb-print | |
if (certCollection.Count > 0) | |
{ | |
return new X509SigningCredentials(certCollection[0]); | |
} | |
throw new Exception("Certificate not found"); | |
}); | |
} | |
[HttpGet] | |
public ActionResult Index(string Name, string email, string phone) | |
{ | |
if (string.IsNullOrEmpty(email)) | |
{ | |
ViewData["Message"] = ""; | |
return View(); | |
} | |
string token = BuildIdToken(Name, email); | |
string link = BuildUrl(token); | |
string Body = string.Empty; | |
string htmlTemplate = System.IO.File.ReadAllText(Path.Combine(this.HostingEnvironment.ContentRootPath, "App_Data\\Template.html")); | |
try | |
{ | |
//MailMessage mailMessage = new MailMessage(); | |
//mailMessage.To.Add(email); | |
//mailMessage.From = new MailAddress(AppSettings.SMTPFromAddress); | |
//mailMessage.Subject = AppSettings.SMTPSubject; | |
//mailMessage.Body = string.Format(htmlTemplate, email, link); | |
//mailMessage.IsBodyHtml = true; | |
//SmtpClient smtpClient = new SmtpClient(AppSettings.SMTPServer, AppSettings.SMTPPort); | |
//smtpClient.Credentials = new NetworkCredential(AppSettings.SMTPUsername, AppSettings.SMTPPassword); | |
//smtpClient.EnableSsl = AppSettings.SMTPUseSSL; | |
//smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network; | |
//smtpClient.Send(mailMessage); | |
//ViewData["Message"] = $"Email sent to {email}"; | |
ViewData["Message"] = $"Link is : {link}"; | |
} | |
catch (Exception ex) | |
{ | |
throw ex; | |
} | |
return View(); | |
} | |
private string BuildIdToken(string Name, string ClubId) | |
{ | |
//string issuer = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase.Value}/"; | |
string issuer = "https://aadb2cinvite20190711090801.azurewebsites.net/"; | |
// All parameters send to Azure AD B2C needs to be sent as claims | |
IList<System.Security.Claims.Claim> claims = new List<System.Security.Claims.Claim>(); | |
claims.Add(new System.Security.Claims.Claim("name", Name, System.Security.Claims.ClaimValueTypes.String, issuer)); | |
claims.Add(new System.Security.Claims.Claim("email", ClubId, System.Security.Claims.ClaimValueTypes.String, issuer)); | |
// Create the token | |
JwtSecurityToken token = new JwtSecurityToken( | |
issuer, | |
this.AppSettings.B2CClientId, | |
claims, | |
DateTime.Now, | |
DateTime.Now.AddDays(7), | |
HomeController.SigningCredentials.Value); | |
// Get the representation of the signed token | |
JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler(); | |
return jwtHandler.WriteToken(token); | |
} | |
private string BuildUrl(string token) | |
{ | |
string nonce = Guid.NewGuid().ToString("n"); | |
return string.Format(this.AppSettings.B2CSignUpUrl, | |
this.AppSettings.B2CTenant, | |
this.AppSettings.B2CPolicy, | |
this.AppSettings.B2CClientId, | |
Uri.EscapeDataString(this.AppSettings.B2CRedirectUri), | |
nonce) + "&id_token_hint=" + token; | |
} | |
public IActionResult Privacy() | |
{ | |
return View(); | |
} | |
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] | |
public IActionResult Error() | |
{ | |
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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="tenant.onmicrosoft.com" | |
PolicyId="B2C_1A_signin_with_email" | |
PublicPolicyUri="http://tenant.onmicrosoft.com/B2C_1A_signin_with_email" | |
DeploymentMode="Development" | |
UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights" > | |
<BasePolicy> | |
<TenantId>tenant.onmicrosoft.com</TenantId> | |
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId> | |
</BasePolicy> | |
<BuildingBlocks> | |
<ClaimsSchema> | |
<!--Sample: Stores the error message for unsolicited request (a request without id_token_hint) and user not found--> | |
<ClaimType Id="errorMessage"> | |
<DisplayName>Error</DisplayName> | |
<DataType>string</DataType> | |
<UserHelpText>Add help text here</UserHelpText> | |
<UserInputType>Paragraph</UserInputType> | |
</ClaimType> | |
</ClaimsSchema> | |
<ClaimsTransformations> | |
<!--Sample: Initiates the errorMessage claims type with the error message--> | |
<ClaimsTransformation Id="CreateUnsolicitedErrorMessage" TransformationMethod="CreateStringClaim"> | |
<InputParameters> | |
<InputParameter Id="value" DataType="string" Value="You cannot sign-in without invitation. Please click the link in the email" /> | |
</InputParameters> | |
<OutputClaims> | |
<OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" /> | |
</OutputClaims> | |
</ClaimsTransformation> | |
<!--Sample: Initiates the errorMessage claims type with the error message user not found--> | |
<ClaimsTransformation Id="CreateUserNotFoundErrorMessage" TransformationMethod="CreateStringClaim"> | |
<InputParameters> | |
<InputParameter Id="value" DataType="string" Value="You aren't registered in the system! Please contact the Help Desk on 0800 xxx yyy and ask to be added to the system" /> | |
</InputParameters> | |
<OutputClaims> | |
<OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" /> | |
</OutputClaims> | |
</ClaimsTransformation> | |
</ClaimsTransformations> | |
</BuildingBlocks> | |
<ClaimsProviders> | |
<!--Sample: This technical profile specifies how B2C should validate your token, and what claims you want B2C to extract from the token. | |
The METADATA value in the TechnicalProfile meta-data is required. | |
The “IdTokenAudience” and “issuer” arguments are optional (see later section)--> | |
<ClaimsProvider> | |
<DisplayName>My ID Token Hint ClaimsProvider</DisplayName> | |
<TechnicalProfiles> | |
<TechnicalProfile Id="IdTokenHint_ExtractClaims"> | |
<DisplayName> My ID Token Hint TechnicalProfile</DisplayName> | |
<Protocol Name="None" /> | |
<Metadata> | |
<!--Sample action required: replace with your endpoint location --> | |
<Item Key="METADATA">https://aadb2cinvitexxx.azurewebsites.net/.well-known/openid-configuration</Item> | |
<!-- <Item Key="IdTokenAudience">your_optional_audience_override</Item> --> | |
<!-- <Item Key="issuer">your_optional_token_issuer_override</Item> --> | |
</Metadata> | |
<OutputClaims> | |
<!--Sample: Read the email cliam from the id_token_hint--> | |
<OutputClaim ClaimTypeReferenceId="email" /> | |
</OutputClaims> | |
</TechnicalProfile> | |
</TechnicalProfiles> | |
</ClaimsProvider> | |
<ClaimsProvider> | |
<DisplayName>Self Asserted</DisplayName> | |
<TechnicalProfiles> | |
<!-- Demo: Show error message--> | |
<TechnicalProfile Id="SelfAsserted-Error"> | |
<DisplayName>Unsolicited 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> | |
<!-- Sample: Remove the continue button--> | |
<Item Key="setting.showContinueButton">false</Item> | |
</Metadata> | |
<InputClaims> | |
<InputClaim ClaimTypeReferenceId="errorMessage"/> | |
</InputClaims> | |
<OutputClaims> | |
<OutputClaim ClaimTypeReferenceId="errorMessage"/> | |
</OutputClaims> | |
</TechnicalProfile> | |
<!-- Demo: Show unsolicited error message--> | |
<TechnicalProfile Id="SelfAsserted-Unsolicited"> | |
<InputClaimsTransformations> | |
<InputClaimsTransformation ReferenceId="CreateUnsolicitedErrorMessage" /> | |
</InputClaimsTransformations> | |
<IncludeTechnicalProfile ReferenceId="SelfAsserted-Error" /> | |
</TechnicalProfile> | |
<!-- Demo: Show user not found error message--> | |
<TechnicalProfile Id="SelfAsserted-UserNotFound"> | |
<InputClaimsTransformations> | |
<InputClaimsTransformation ReferenceId="CreateUserNotFoundErrorMessage" /> | |
</InputClaimsTransformations> | |
<IncludeTechnicalProfile ReferenceId="SelfAsserted-Error" /> | |
</TechnicalProfile> | |
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-Hint"> | |
<Metadata> | |
<Item Key="Operation">Read</Item> | |
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item> | |
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item> | |
</Metadata> | |
<IncludeInSso>false</IncludeInSso> | |
<InputClaims> | |
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" /> | |
</InputClaims> | |
<OutputClaims> | |
<!-- Required claims --> | |
<OutputClaim ClaimTypeReferenceId="objectId" /> | |
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" /> | |
<!-- Optional claims --> | |
<OutputClaim ClaimTypeReferenceId="userPrincipalName" /> | |
<OutputClaim ClaimTypeReferenceId="displayName" /> | |
<OutputClaim ClaimTypeReferenceId="accountEnabled" /> | |
<OutputClaim ClaimTypeReferenceId="otherMails" /> | |
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" /> | |
<OutputClaim ClaimTypeReferenceId="givenName"/> | |
<OutputClaim ClaimTypeReferenceId="surname"/> | |
</OutputClaims> | |
<!-- <OutputClaimsTransformations> | |
<OutputClaimsTransformation ReferenceId="AssertAccountEnabledIsTrue" /> | |
</OutputClaimsTransformations> --> | |
<IncludeTechnicalProfile ReferenceId="AAD-Common" /> | |
</TechnicalProfile> | |
</TechnicalProfiles> | |
</ClaimsProvider> | |
</ClaimsProviders> | |
<UserJourneys> | |
<UserJourney Id="SignUpOrSignInWithEmail"> | |
<OrchestrationSteps> | |
<!--Sample: Read the input claims from the id_token_hint--> | |
<OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" /> | |
<!-- Sample: Check if user tries to run the policy without invitation --> | |
<OrchestrationStep Order="2" Type="ClaimsExchange"> | |
<Preconditions> | |
<Precondition Type="ClaimsExist" ExecuteActionsIf="true"> | |
<Value>email</Value> | |
<Action>SkipThisOrchestrationStep</Action> | |
</Precondition> | |
</Preconditions> | |
<ClaimsExchanges> | |
<ClaimsExchange Id="SelfAsserted-Unsolicited" TechnicalProfileReferenceId="SelfAsserted-Unsolicited" /> | |
</ClaimsExchanges> | |
</OrchestrationStep> | |
<!--Sample: Read the user properties from the directory--> | |
<OrchestrationStep Order="3" Type="ClaimsExchange"> | |
<ClaimsExchanges> | |
<ClaimsExchange Id="AADUserReadUsingEmailAddress" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress-Hint"/> | |
</ClaimsExchanges> | |
</OrchestrationStep> | |
<!-- Sample: Check whether the user not existed in the directory --> | |
<OrchestrationStep Order="4" Type="ClaimsExchange"> | |
<Preconditions> | |
<Precondition Type="ClaimsExist" ExecuteActionsIf="true"> | |
<Value>objectId</Value> | |
<Action>SkipThisOrchestrationStep</Action> | |
</Precondition> | |
</Preconditions> | |
<ClaimsExchanges> | |
<ClaimsExchange Id="SelfAssertedUserNotFound" TechnicalProfileReferenceId="SelfAsserted-UserNotFound" /> | |
</ClaimsExchanges> | |
</OrchestrationStep> | |
<!--Sample: Issue an access token--> | |
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/> | |
</OrchestrationSteps> | |
<ClientDefinition ReferenceId="DefaultWeb"/> | |
</UserJourney> | |
</UserJourneys> | |
<RelyingParty> | |
<DefaultUserJourney ReferenceId="SignUpOrSignInWithEmail" /> | |
<UserJourneyBehaviors> | |
<JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="your key" | |
DeveloperMode="true" ClientEnabled="false" | |
ServerEnabled="true" TelemetryVersion="1.0.0" /> | |
</UserJourneyBehaviors> | |
<TechnicalProfile Id="PolicyProfile"> | |
<DisplayName>PolicyProfile</DisplayName> | |
<Protocol Name="OpenIdConnect" /> | |
<!--Sample: Set the input claims to be read from the id_token_hint--> | |
<InputClaims> | |
<InputClaim ClaimTypeReferenceId="email" /> | |
</InputClaims> | |
<OutputClaims> | |
<OutputClaim ClaimTypeReferenceId="displayName" /> | |
<OutputClaim ClaimTypeReferenceId="givenName" /> | |
<OutputClaim ClaimTypeReferenceId="surname" /> | |
<OutputClaim ClaimTypeReferenceId="email" /> | |
<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
https://medium.com/the-new-control-plane/using-a-jwt-to-invoke-an-azure-ad-b2c-flow-using-id-token-hint-13752ee6d9df