Search code examples
.net-coremetadataopenid-connectazure-ad-b2c-custom-policyself-signed-certificate

id_token_hint parameter failed signature validation when Using B2C to generate the metadata endpoints


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:

  1. Create a cert via powershell and get thumbprint to use in local code
  2. Use certmng via MMC to export cert
    • All Task / Export / Next / Yes, Export the private key
    • Personal Information Exchange - PKCS (Include all cert in cert path)(Enable cert privacy)
  3. Security (Password) Randomly Generated Pass 25 character password.
  4. Name: id_token_hint_cert.pfx
  5. Browse Azure / B2C / Identity Experience Framework / Policy keys
    • Add / Option: Upload / Name: IdTokenHintCert / File Upload id_token_hint_cert.pfx / Password: Password from setup 3

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 });
    }
}

}


Solution

  • 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.