I am attempting to set up a Magic link like system using Azure B2C. Using the following samples:
Primary:
https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-in-with-magic-link
For sing B2C to generate the metadata endpoints: https://github.com/azure-ad-b2c/samples/tree/master/policies/invite#using-b2c-to-generate-the-metadata-endpoints
As a note I believe I had it working at one point but after a clean up I have been getting the error:
The provided id_token_hint parameter failed signature validation. Please provide another token and try again.
The steps I took to set up is as follows:
This is where I have tried 2 different set ups. The first was to setup a set of custom policies so that I could update the following claims provider to have issuer_secret set to B2C_1A_IdTokenHintCert
<ClaimsProvider>
<DisplayName>Token Issuer</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="JwtIssuer">
<DisplayName>JWT Issuer</DisplayName>
<Protocol Name="None" />
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="client_id">{service:te}</Item>
<Item Key="issuer_refresh_token_user_identity_claim_type">objectId</Item>
<Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_IdTokenHintCert" />
<Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
</CryptographicKeys>
<InputClaims />
<OutputClaims />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
This is set of policies grabbed from https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/tree/master/LocalAccounts and updated to my tenant but left mostly alone.
I also tried changing out the issuer_secret in my main custom policies with the same error being output.
Heading into my code: This is the important part of my startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
OpenIdConnectDefaults.AuthenticationScheme
).AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C"),"OpenIdConnect", "Cookies",true);
services.AddControllersWithViews();
services.AddRazorPages()
.AddMicrosoftIdentityUI();
services.AddTransient<IClaimsTransformation, AppClaimsTransformations>();
}
And here is my Home Controller where I submit a form, create the token and link and then just redirect to that link using it as a run now endpoint. (I know this wont end of working but need to get the signature validate before I can move on.)
public class HomeController : Controller
{
private static Lazy<X509SigningCredentials> SigningCredentials;
private readonly AppSettingsModel _appSettings;
private readonly IWebHostEnvironment HostingEnvironment;
private readonly ILogger<HomeController> _logger;
// Sample: Inject an instance of an AppSettingsModel class into the constructor of the consuming class,
// and let dependency injection handle the rest
public HomeController(ILogger<HomeController> logger, IOptions<AppSettingsModel> appSettings, IWebHostEnvironment hostingEnvironment)
{
_appSettings = appSettings.Value;
this.HostingEnvironment = hostingEnvironment;
this._logger = logger;
// 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,
"***************************************",
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, phone);
string link = BuildUrl(token);
return Redirect(link);
}
private string BuildIdToken(string Name, string email, string phone)
{
string issuer = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase.Value}/";
// 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", email, System.Security.Claims.ClaimValueTypes.String, issuer));
if (!string.IsNullOrEmpty(phone))
{
claims.Add(new System.Security.Claims.Claim("phone", phone, System.Security.Claims.ClaimValueTypes.String, issuer));
}
// Create the token
JwtSecurityToken token = new JwtSecurityToken(
issuer,
"******************************************",
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("https://{0}.b2clogin.com/{0}.onmicrosoft.com/{1}/oauth2/v2.0/authorize?client_id={2}&nonce={4}&redirect_uri={3}&scope=openid&response_type=id_token",
"myTenant",
"B2C_1A_SIGNIN_WITH_EMAIL",
"************************************",
Uri.EscapeDataString("https://jwt.ms"),
nonce)
+ "&id_token_hint=" + token;
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
Location Location Location.
I was adjusting the base profile which I learned I should not be doing. When I applied my change to the extension file instead everything starting working properly.