AzureAD / azure-activedirectory-identitymodel-extensions-for-dotnet

IdentityModel extensions for .Net
MIT License
1.06k stars 400 forks source link

Validation of JWT based on the x5c header field is not working #1542

Closed RufusJWB closed 3 years ago

RufusJWB commented 4 years ago

I have the following JWT which was signed with an X509 certificate. The signing certificates itself is embedded in the x5c field of the JWT header.

eyJhbGciOiJSUzI1NiIsImtpZCI6IjM2ODZEMEQ2NzgyQzUyQ0U1NjI5RThENkI4MjFCMkU2RENGMjE4RkYiLCJ4NXQiOiJOb2JRMW5nc1VzNVdLZWpXdUNHeTV0enlHUDgiLCJ0eXAiOiJKV1QiLCJ4NWMiOlsiTUlJSGZEQ0NCV1NnQXdJQkFnSVFiMEd0ZG5YdXRZWXM2VSt1Yy9TbEtqQU5CZ2txaGtpRzl3MEJBUXNGQURDQm56RUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWdNQmtKaGVXVnliakVSTUE4R0ExVUVCd3dJVFhWbGJtTm9aVzR4RURBT0JnTlZCQW9NQjFOcFpXMWxibk14RVRBUEJnTlZCQVVUQ0ZwYVdscGFXa0l5TVIwd0d3WURWUVFMREJSVGFXVnRaVzV6SUZSeWRYTjBJRU5sYm5SbGNqRW9NQ1lHQTFVRUF3d2ZVMmxsYldWdWN5QkpjM04xYVc1bklFTkJJRVZGSUVGMWRHZ2dNakF5TURBZUZ3MHlNREV3TURjd09EUXdORFphRncweU16RXdNRGN3T0RRd05EWmFNR0V4RVRBUEJnTlZCQVVUQ0Zvd01ESk5OelpCTVE0d0RBWURWUVFxRXdWU2RXWjFjekVSTUE4R0ExVUVCQk1JUW5WelkyaGhjblF4RURBT0JnTlZCQW9UQjFOcFpXMWxibk14RnpBVkJnTlZCQU1URGtKMWMyTm9ZWEowSUZKMVpuVnpNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXVlV0MzR0IwdHRPaFNtZlBGQWJ5cVc0YjRjWG1nUUJBOGJzalU2bDNvT0JqczV6M1pYWFlMM3FsdTA5Mm8rcERKdEFMVGpYNmlaQ3dwWlJuQURYU1pVeWpWNm1XZEF6aWxqdG1FVXlZZG56NnhzQ1lpR2NHQzdaMTdNMmQ2OXFBdEl2d2hGeWE4alVZbDB5QTJGK1ppb1ViaStQUmhtdFBSZmg1bi80RXp4b1J5bG0xVlJzNkdQcTFRMjNBZTdPbGhBOGxxVFg2YkJZdVYwQ0E4Q2tTWjB3MzBaSXJGUTJ6UVZHSEJwTm8rcjNiSlRWdFNRZDduaUNTNkN5bFVEWWI4NGZ4emFPMmZoczJXMU5TcEI5SzVpR1dFaWpNQ0I2Ti9kTTMvMFVTbTJMSmt2cExKK2VQUlkwQ1I2TVptZmdxQ3l2QVFVWFRxdmFOZ2krOXNSUlNmUUlEQVFBQm80SUM3ekNDQXVzd0tRWURWUjBsQkNJd0lBWUlLd1lCQlFVSEF3SUdDQ3NHQVFVRkJ3TUVCZ29yQmdFRUFZSTNGQUlDTUI4R0ExVWRJd1FZTUJhQUZOYnYrNmZuS3JIQVhVeG9oY0l0ajFabWZvTHVNSUgzQmdnckJnRUZCUWNCQVFTQjZqQ0I1ekF5QmdnckJnRUZCUWN3QW9ZbWFIUjBjRG92TDJGb0xuTnBaVzFsYm5NdVkyOXRMM0JyYVQ5YVdscGFXbHBDTWk1amNuUXdRUVlJS3dZQkJRVUhNQUtHTld4a1lYQTZMeTloYkM1emFXVnRaVzV6TG01bGRDOURUajFhV2xwYVdscENNaXhNUFZCTFNUOWpRVU5sY25ScFptbGpZWFJsTUVrR0NDc0dBUVVGQnpBQ2hqMXNaR0Z3T2k4dllXd3VjMmxsYldWdWN5NWpiMjB2UTA0OVdscGFXbHBhUWpJc2J6MVVjblZ6ZEdObGJuUmxjajlqUVVObGNuUnBabWxqWVhSbE1DTUdDQ3NHQVFVRkJ6QUJoaGRvZEhSd09pOHZiMk56Y0M1emFXVnRaVzV6TG1OdmJUQkdCZ05WSFNBRVB6QTlNRHNHRFNzR0FRUUJvV2tIQWdJREFRRXdLakFvQmdnckJnRUZCUWNDQVJZY2FIUjBjSE02THk5M2QzY3VjMmxsYldWdWN5NWpiMjB2Y0d0cEx6Q0J5Z1lEVlIwZkJJSENNSUcvTUlHOG9JRzVvSUcyaGlab2RIUndPaTh2WTJndWMybGxiV1Z1Y3k1amIyMHZjR3RwUDFwYVdscGFXa0l5TG1OeWJJWkJiR1JoY0RvdkwyTnNMbk5wWlcxbGJuTXVibVYwTDBOT1BWcGFXbHBhV2tJeUxFdzlVRXRKUDJObGNuUnBabWxqWVhSbFVtVjJiMk5oZEdsdmJreHBjM1NHU1d4a1lYQTZMeTlqYkM1emFXVnRaVzV6TG1OdmJTOURUajFhV2xwYVdscENNaXh2UFZSeWRYTjBZMlZ1ZEdWeVAyTmxjblJwWm1sallYUmxVbVYyYjJOaGRHbHZia3hwYzNRd0hRWURWUjBPQkJZRUZFaEVhQmM0S1gzRG94aVB1dUd3aFF2RFhldVdNQTRHQTFVZER3RUIvd1FFQXdJSGdEQlJCZ05WSFJFRVNqQklvQ29HQ2lzR0FRUUJnamNVQWdPZ0hBd2FjblZtZFhNdVluVnpZMmhoY25SQWMybGxiV1Z1Y3k1amIyMkJHbkoxWm5WekxtSjFjMk5vWVhKMFFITnBaVzFsYm5NdVkyOXRNQXdHQTFVZEV3RUIvd1FDTUFBd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFDdEk4UDVvQTR1UG9JWVpLV0tYNUJvSlZsaTArOFhuckM5L0xIb29NYlI1WVJyekhCdC8wbFc0d2p4OHJjY2thTGN5ZzZyVXJIYXpra3k2dWRCaDVBTlBmSE9HQ0M4b3N3REV6UFQveUtZazBqcUx2eStmckVNNFlrZk5aSTUzYXVXYTJXNlYxNUxIYkVyVzJCOU9SS2lOdEsrYmtIa09zR0xuWlFFaXFGQ3RZL3I1cy9XNVdSaGlNb1ppUTZsMUh0ZXNVSWZIR1piNFhXVGlwSUd3RnltU2RrM2xpQWorekJqWmoydm15MURZVHVuQytSUmxHZFpnemkrRC9LK1NoQ240YjZWR2ZPR3AxMFVLNUtTbm8zL3lhSTY2aXNBNXZtbFlJRk8xWkYvMGhqRnl6NUdVeW9NdDFJRk9qcFA3ZS9kaEtiUElsWHA1aDJ3c3hrNjJKRlBRN3hpNHBIYlpvUlBMbVE1RkVoZVI4UXpCZHJuMTJuWnp2bHk0dXhiaWlISStVMFlaUnEzVm1YRnorYzJIcHM3SEVtR3ArUmhaRXVDcit1NklobDMySTljTlBMMVNKZTh3aW04b2p6bmFaRmZoenlYbDl0cnppSHJ6NUdOdFdEZzRNWkhlL0pTN0tRNC95eHVqL2w4cDBwTHdXaU9Ycnl0WEZsSGx1b0tka2J2cXlCRGdvb25iSjBpSzhVMDE1WnR2MVYwc2pBbDYzYnFZMDZuaHYvZUxWemorOWE1V1FHNHE4aTk2RkNGNGJqOUFtK0ZzRDU5MnUwM3M2VVdUa01kbWMrRmJ0V0xXdDlrL25hWjk0ZFRWRklQaDBFTG5XeWNTYWlySnA0LzNvRkpPNjJkWkRpc1ZCbENiQmJUZ1NPS1VaeVdJMVJ6NlRTVXp3UFVHS0REVCJdfQ.eyJjb250ZW50IjoiVGVzdCBkYXRhIiwibmJmIjoxNjAyNzUwMjI1LCJleHAiOjE2MDI3NTA1MjUsImlhdCI6MTYwMjc1MDIyNSwiaXNzIjoiQ049QnVzY2hhcnQgUnVmdXMsIE89U2llbWVucywgU049QnVzY2hhcnQsIEc9UnVmdXMsIFNFUklBTE5VTUJFUj1aMDAyTTc2QSJ9.Ax1E_6ZqgdstOmYBxO2bSITpppW_K8JN4f_kAcUPBc77ucaZ4419bzOrx9eF9ib22nTa2JDRivGa_sUfs3W4OjjoEiXatzzTXWZYFcpAz60CYRuvgpu1rKOl4T5aiz5JAyVOoFWopDGjKPYIM2Hkg51JgDKcYDcX-JNBoH8ZEyXSkzhHwjwnDMZf6RT1RLCIpb4urcrfXl7nyJiwyLRzwAaO9gSdJiTPRGm5qrcmTy93eBABb0zjp0HbTbfqlMcHQozPB7ARAkn5d60BqRj2CrOMGak4yBzliYUVJqmkYZAtZ2NK-3tMZXY_Z8fRDiOJS4ys1Up3Zum8f-X2ZSeJog

I'm trying to validate this JWT with the following code:

var handler = new JwtSecurityTokenHandler();

var validationParameters =
new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
};

SecurityToken validatedSecurityToken = null;
var cp = handler.ValidateToken(jwt, validationParameters, out validatedSecurityToken);

This results in an exception:

Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: 'IDX10501: Signature validation failed. Unable to match key: kid: 'System.String'. Exceptions caught: 'System.Text.StringBuilder'. token: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.'

As an alternative approach, I did try to extract the certificate from the x5c field in the header

JwtSecurityToken outerJWT = handler.ReadJwtToken(jwt);
JwtHeader header = outerJWT.Header;
header.X5c

but this is not working either, since there is no X5c field in the JwtHeader class.

GeoK commented 4 years ago

Hi @RufusJWB - I don't fully understand what is the goal of adding the X5c property on JwtHearer (#1543).

To validate a signature, you'll need to set IssuerSigningKey on TokenValidationParameters.

IDX10501 signals that the library couldn't match a signing key with a 'kid' claim, and that's expected as you haven't set an IssuerSigningKey.

RufusJWB commented 3 years ago

Hi @GeoK ! Yes, setting the IssuerSigningKey is exactly what I want to do, but to do this, I need (at least to my understanding) have access to the X5c property of the received JWT. If you decode the jwt I have pasted above, you will see, that it is signed with the private key that matches the certificate in the X5c field.

The code could look like this in the end:

            string jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjM2ODZEMEQ2NzgyQzUyQ0U1NjI5RThENkI4MjFCMkU2RENGMjE4RkYiLCJ4NXQiOiJOb2JRMW5nc1VzNVdLZWpXdUNHeTV0enlHUDgiLCJ0eXAiOiJKV1QiLCJ4NWMiOlsiTUlJSGZEQ0NCV1NnQXdJQkFnSVFiMEd0ZG5YdXRZWXM2VSt1Yy9TbEtqQU5CZ2txaGtpRzl3MEJBUXNGQURDQm56RUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWdNQmtKaGVXVnliakVSTUE4R0ExVUVCd3dJVFhWbGJtTm9aVzR4RURBT0JnTlZCQW9NQjFOcFpXMWxibk14RVRBUEJnTlZCQVVUQ0ZwYVdscGFXa0l5TVIwd0d3WURWUVFMREJSVGFXVnRaVzV6SUZSeWRYTjBJRU5sYm5SbGNqRW9NQ1lHQTFVRUF3d2ZVMmxsYldWdWN5QkpjM04xYVc1bklFTkJJRVZGSUVGMWRHZ2dNakF5TURBZUZ3MHlNREV3TURjd09EUXdORFphRncweU16RXdNRGN3T0RRd05EWmFNR0V4RVRBUEJnTlZCQVVUQ0Zvd01ESk5OelpCTVE0d0RBWURWUVFxRXdWU2RXWjFjekVSTUE4R0ExVUVCQk1JUW5WelkyaGhjblF4RURBT0JnTlZCQW9UQjFOcFpXMWxibk14RnpBVkJnTlZCQU1URGtKMWMyTm9ZWEowSUZKMVpuVnpNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXVlV0MzR0IwdHRPaFNtZlBGQWJ5cVc0YjRjWG1nUUJBOGJzalU2bDNvT0JqczV6M1pYWFlMM3FsdTA5Mm8rcERKdEFMVGpYNmlaQ3dwWlJuQURYU1pVeWpWNm1XZEF6aWxqdG1FVXlZZG56NnhzQ1lpR2NHQzdaMTdNMmQ2OXFBdEl2d2hGeWE4alVZbDB5QTJGK1ppb1ViaStQUmhtdFBSZmg1bi80RXp4b1J5bG0xVlJzNkdQcTFRMjNBZTdPbGhBOGxxVFg2YkJZdVYwQ0E4Q2tTWjB3MzBaSXJGUTJ6UVZHSEJwTm8rcjNiSlRWdFNRZDduaUNTNkN5bFVEWWI4NGZ4emFPMmZoczJXMU5TcEI5SzVpR1dFaWpNQ0I2Ti9kTTMvMFVTbTJMSmt2cExKK2VQUlkwQ1I2TVptZmdxQ3l2QVFVWFRxdmFOZ2krOXNSUlNmUUlEQVFBQm80SUM3ekNDQXVzd0tRWURWUjBsQkNJd0lBWUlLd1lCQlFVSEF3SUdDQ3NHQVFVRkJ3TUVCZ29yQmdFRUFZSTNGQUlDTUI4R0ExVWRJd1FZTUJhQUZOYnYrNmZuS3JIQVhVeG9oY0l0ajFabWZvTHVNSUgzQmdnckJnRUZCUWNCQVFTQjZqQ0I1ekF5QmdnckJnRUZCUWN3QW9ZbWFIUjBjRG92TDJGb0xuTnBaVzFsYm5NdVkyOXRMM0JyYVQ5YVdscGFXbHBDTWk1amNuUXdRUVlJS3dZQkJRVUhNQUtHTld4a1lYQTZMeTloYkM1emFXVnRaVzV6TG01bGRDOURUajFhV2xwYVdscENNaXhNUFZCTFNUOWpRVU5sY25ScFptbGpZWFJsTUVrR0NDc0dBUVVGQnpBQ2hqMXNaR0Z3T2k4dllXd3VjMmxsYldWdWN5NWpiMjB2UTA0OVdscGFXbHBhUWpJc2J6MVVjblZ6ZEdObGJuUmxjajlqUVVObGNuUnBabWxqWVhSbE1DTUdDQ3NHQVFVRkJ6QUJoaGRvZEhSd09pOHZiMk56Y0M1emFXVnRaVzV6TG1OdmJUQkdCZ05WSFNBRVB6QTlNRHNHRFNzR0FRUUJvV2tIQWdJREFRRXdLakFvQmdnckJnRUZCUWNDQVJZY2FIUjBjSE02THk5M2QzY3VjMmxsYldWdWN5NWpiMjB2Y0d0cEx6Q0J5Z1lEVlIwZkJJSENNSUcvTUlHOG9JRzVvSUcyaGlab2RIUndPaTh2WTJndWMybGxiV1Z1Y3k1amIyMHZjR3RwUDFwYVdscGFXa0l5TG1OeWJJWkJiR1JoY0RvdkwyTnNMbk5wWlcxbGJuTXVibVYwTDBOT1BWcGFXbHBhV2tJeUxFdzlVRXRKUDJObGNuUnBabWxqWVhSbFVtVjJiMk5oZEdsdmJreHBjM1NHU1d4a1lYQTZMeTlqYkM1emFXVnRaVzV6TG1OdmJTOURUajFhV2xwYVdscENNaXh2UFZSeWRYTjBZMlZ1ZEdWeVAyTmxjblJwWm1sallYUmxVbVYyYjJOaGRHbHZia3hwYzNRd0hRWURWUjBPQkJZRUZFaEVhQmM0S1gzRG94aVB1dUd3aFF2RFhldVdNQTRHQTFVZER3RUIvd1FFQXdJSGdEQlJCZ05WSFJFRVNqQklvQ29HQ2lzR0FRUUJnamNVQWdPZ0hBd2FjblZtZFhNdVluVnpZMmhoY25SQWMybGxiV1Z1Y3k1amIyMkJHbkoxWm5WekxtSjFjMk5vWVhKMFFITnBaVzFsYm5NdVkyOXRNQXdHQTFVZEV3RUIvd1FDTUFBd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFDdEk4UDVvQTR1UG9JWVpLV0tYNUJvSlZsaTArOFhuckM5L0xIb29NYlI1WVJyekhCdC8wbFc0d2p4OHJjY2thTGN5ZzZyVXJIYXpra3k2dWRCaDVBTlBmSE9HQ0M4b3N3REV6UFQveUtZazBqcUx2eStmckVNNFlrZk5aSTUzYXVXYTJXNlYxNUxIYkVyVzJCOU9SS2lOdEsrYmtIa09zR0xuWlFFaXFGQ3RZL3I1cy9XNVdSaGlNb1ppUTZsMUh0ZXNVSWZIR1piNFhXVGlwSUd3RnltU2RrM2xpQWorekJqWmoydm15MURZVHVuQytSUmxHZFpnemkrRC9LK1NoQ240YjZWR2ZPR3AxMFVLNUtTbm8zL3lhSTY2aXNBNXZtbFlJRk8xWkYvMGhqRnl6NUdVeW9NdDFJRk9qcFA3ZS9kaEtiUElsWHA1aDJ3c3hrNjJKRlBRN3hpNHBIYlpvUlBMbVE1RkVoZVI4UXpCZHJuMTJuWnp2bHk0dXhiaWlISStVMFlaUnEzVm1YRnorYzJIcHM3SEVtR3ArUmhaRXVDcit1NklobDMySTljTlBMMVNKZTh3aW04b2p6bmFaRmZoenlYbDl0cnppSHJ6NUdOdFdEZzRNWkhlL0pTN0tRNC95eHVqL2w4cDBwTHdXaU9Ycnl0WEZsSGx1b0tka2J2cXlCRGdvb25iSjBpSzhVMDE1WnR2MVYwc2pBbDYzYnFZMDZuaHYvZUxWemorOWE1V1FHNHE4aTk2RkNGNGJqOUFtK0ZzRDU5MnUwM3M2VVdUa01kbWMrRmJ0V0xXdDlrL25hWjk0ZFRWRklQaDBFTG5XeWNTYWlySnA0LzNvRkpPNjJkWkRpc1ZCbENiQmJUZ1NPS1VaeVdJMVJ6NlRTVXp3UFVHS0REVCJdfQ.eyJjb250ZW50IjoiVGVzdCBkYXRhIiwibmJmIjoxNjAyNzUwMjI1LCJleHAiOjE2MDI3NTA1MjUsImlhdCI6MTYwMjc1MDIyNSwiaXNzIjoiQ049QnVzY2hhcnQgUnVmdXMsIE89U2llbWVucywgU049QnVzY2hhcnQsIEc9UnVmdXMsIFNFUklBTE5VTUJFUj1aMDAyTTc2QSJ9.Ax1E_6ZqgdstOmYBxO2bSITpppW_K8JN4f_kAcUPBc77ucaZ4419bzOrx9eF9ib22nTa2JDRivGa_sUfs3W4OjjoEiXatzzTXWZYFcpAz60CYRuvgpu1rKOl4T5aiz5JAyVOoFWopDGjKPYIM2Hkg51JgDKcYDcX-JNBoH8ZEyXSkzhHwjwnDMZf6RT1RLCIpb4urcrfXl7nyJiwyLRzwAaO9gSdJiTPRGm5qrcmTy93eBABb0zjp0HbTbfqlMcHQozPB7ARAkn5d60BqRj2CrOMGak4yBzliYUVJqmkYZAtZ2NK-3tMZXY_Z8fRDiOJS4ys1Up3Zum8f-X2ZSeJog";

            var handler = new JwtSecurityTokenHandler();

            JwtSecurityToken outerJWT = handler.ReadJwtToken(jwt);
            JwtHeader header = outerJWT.Header;
            //string x5c = header.X5c; // <-- This is not working!!!
            string x5c = "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";

            var certificate = new X509Certificate2(Convert.FromBase64String(x5c));

            TokenValidationParameters validationParameters = new TokenValidationParameters()
            {
                ValidateIssuerSigningKey = true,
                ValidateLifetime = false,
                ValidateAudience = false,
                ValidateIssuer = false,
                IssuerSigningKeyResolver = (t, st, i, p) => new[] {
                    new X509SecurityKey(certificate) 
                },
            };

            SecurityToken validatedSecurityToken = null;
            var cp = handler.ValidateToken(jwt, validationParameters, out validatedSecurityToken);
brentschmaltz commented 3 years ago

@RufusJWB it is important to ensure a token has been created by a trusted authority. How will the code above ensure the token has been issued by a trusted authority?

Our model for validation is to obtain keys and the issuer value from a trusted authority and then ensure the token is signed by that authority. We should also check the audience and expiration.

RufusJWB commented 3 years ago

@RufusJWB it is important to ensure a token has been created by a trusted authority. How will the code above ensure the token has been issued by a trusted authority?

@brentschmaltz the code will validate the signer certificate which is extracted from the x5c field and check, if it is issued by a CA that chains up to a trusted root CA. This can be done by out-of-the-box mechanism of any PKI infrastructure.

Our model for validation is to obtain keys and the issuer value from a trusted authority and then ensure the token is signed by that authority. We should also check the audience and expiration.

Of course, audience and expiration will be checked in production. I only disabled it for the sake of clarity in the code example.

RufusJWB commented 3 years ago

@brentschmaltz any thoughts on this?

shadow-cs commented 3 years ago

Well what you can do @RufusJWB is set TokenValidationParameters.IssuerSigningKeyResolver to a delegate which will extract the key from the x5c header while validating the chain by your PKI (do checkout new .NET 5 APIs 😉).

Obtaining the x5c header and properly validating its contents is a little quirky though. From the delegate you have access to a SecurityToken instance which you can safely cast to JsonWebToken then call TryGetHeaderValue<string[]>(JwtHeaderParameterNames.X5c, out var result) on it. The last part is the quirky one, you cannot really validate if the JWT does contains invalid x5c header the method returns false if someone passes a integer or null there. Could something be done about that @brentschmaltz (maybe this is out of scope of this issue)?

RufusJWB commented 3 years ago

@shadow-cs thank you for your feedback. I'll try this. Did you see my PR #1543 ? This one line of code would make the x5c header directly available without casting.

gergoszekeresnuance commented 2 years ago

I had a really similar issue and it was impossible to find any documentation regarding how the JWT signature is being verified. With defined kid the standard supports 3 ways: x5c, jwk, x5u. As far as I managed to test, the validation supports x5u like way to call "jwt's issuer + .well-known/jwks.json" URL to get the public key. However it is not 100% clear if x5c, jwk and x5u are not supported and they cannot take priority over the built-in validation. E.g.: if the x5u is supported then the http request to fetch the public key could be tampered to a fake endpoint via providing a value to x5u and the whole signature validation can be broken. Similarly with jwk and x5c, what will happen if someone provides a value in these fields? Would they be ignored by default? Will this behavior stay like this? FYI @GeoK

brentschmaltz commented 2 years ago

@gergoszekeresnuance x5c, x5u, where the key is inside the JWT, is not supported directly. Our validation logic expects that TokenValidationParameters either has the signing keys OR a delegate is used.

Microsoft's WCF runtime would validate SAML token signatures that contained x5c, but we made a design decision to require all accepted keys to be available.

Dyu13 commented 7 months ago

@RufusJWB in order to get the x5c from a JWT created by Microsoft Entra (Active Directory), you need to:

  1. extract the tenant id (jwt.Payload["tid"])
  2. make a call to https://login.microsoftonline.com/{tenantId}/discovery/v2.0/keys
  3. extract kid from your jwt header (jwt.Header.Kid)
  4. find in your key array response the key with the same kid you extracted
  5. from that key get the x5c (which is an array so get the first string inside)

For simplicity you can deserialize your response into a simpler object since you only need the kid and x5x. Something like: { "keys": [ { "kid": "kid_val", "x5c": ["x5c_val"] } ] }

I will leave this article for reference on how to do it: https://www.voitanos.io/blog/validating-entra-id-generated-oauth-tokens/