4
votes

I have been working on Azure AD Authorization code flow all these days and suddenly started moving everything to Azure AD B2C and I came across lot of differences between Azure AD and Azure AD B2C. Can Someone answer my questions below.

  1. In Azure AD when we register a Native App, it allows http or https as redirect urls. Azure AD B2C doesn't support this (Since both follow OAUTH specs both should behave similarly)

  2. Azure AD JWT access tokens has x5c entry where B2C doesn't have this entry. Any particular reason for this. I tried copying the public keys from Azure AD and tried to upload the same signing keys to B2C but this didn't work. Not sure what I was missing but my question is why these access tokens differ in their signature.

1
You can't upload Azure AD's signing keys to B2C because what you can see are the public keys. You need the private keys to do signing with them. If the signatures are different, then the signing keys are different.juunas

1 Answers

3
votes

For the first issue, I suggest that you rise a feedback from here if you require this feature.

And for the second issue, it is same to verify the tokens from Azure AD B2C and normal Azure AD. We can generate the public key using the exponent(e) and modulus(n). But the keys endpoint is different, we need to using the link like below to retrieve the keys for the Azure AD B2C:

https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys?p={signInPolicy}

Here is the code to verify the token issued by Azure AD B2C for your reference:

static void Main(string[] args)
{          
    var idtoken = "";

    var exponent = "AQAB";
    var modulus = "";
    var result=  VerifyTokenDetails(idtoken, exponent, modulus);
}
private static bool VerifyTokenDetails(string idToken, string exponent, string modulus)
{
    try
    {              
        var parts = idToken.Split('.');
        var header = parts[0];
        var payload = parts[1];
        string signedSignature = parts[2];
        //Extract user info from payload   
        string userInfo = Encoding.UTF8.GetString(Base64UrlDecode(payload));
        //Which will be Verified
        string originalMessage = string.Concat(header, ".", payload);
        byte[] keyBytes = Base64UrlDecode(modulus);
        string keyBase = Convert.ToBase64String(keyBytes);
        string key = @"<RSAKeyValue> <Modulus>" + keyBase + "</Modulus> <Exponent>" + exponent + "</Exponent> </RSAKeyValue>";
        bool result = VerifyData(originalMessage, signedSignature, key);
        if (result)
            return true;
        else
            return false;
    }
    catch (Exception ex) { }
    return false;
}

/// <summary>  
/// Verifies encrypted signed message with public key encrypted original message.  
/// </summary>  
/// <param name="originalMessage">Original message as string. (Encrypted form)</param>  
/// <param name="signedMessage">Signed message as string. (Encrypted form)</param>  
/// <param name="publicKey">Public key as XML string.</param>  
/// <returns>Boolean True if successful otherwise return false.</returns>  
private static bool VerifyData(string originalMessage, string signedMessage, string publicKey)
{
    bool success = false;
    using (var rsa = new RSACryptoServiceProvider())
    {
        var encoder = new UTF8Encoding();
        byte[] bytesToVerify = encoder.GetBytes(originalMessage);
        byte[] signedBytes = Base64UrlDecode(signedMessage);
        try
        {

            rsa.FromXmlString(publicKey);
            SHA256Managed Hash = new SHA256Managed();
            byte[] hashedData = Hash.ComputeHash(signedBytes);
            // Summary:
            //     Verifies that a digital signature is valid by determining the hash value in the
            //     signature using the provided public key and comparing it to the hash value of
            //     the provided data.
            success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA256"), signedBytes);
        }
        catch (CryptographicException e)
        {
            success = false;
        }
        finally
        {
            rsa.PersistKeyInCsp = false;
        }
    }
    return success;
}

private static byte[] Base64UrlDecode(string input)
{
    var output = input;
    output = output.Replace('-', '+'); // 62nd char of encoding  
    output = output.Replace('_', '/'); // 63rd char of encoding  
    switch (output.Length % 4) // Pad with trailing '='s  
    {
        case 0: break; // No pad chars in this case  
        case 2: output += "=="; break; // Two pad chars  
        case 3: output += "="; break; // One pad char  
        default: throw new System.Exception("Illegal base64url string!");
    }
    var converted = Convert.FromBase64String(output); // Standard base64 decoder  
    return converted;
}