On my client side (nextAuth) I am getting an access token once from Microsoft, now I want that token to be verified from backend which is on ASP.NET Core Web API. As far as I know the process is to get the keys from Microsoft and the one which has same "kid" use that to verify.
This is the header part of my JWT that I got from Microsoft:
{
"nonce": "v9eWI-Nv75YgvN8IgSNSlHPK-UH8iOcEUz8KXqj6DeA",
"alg": "RS256",
"x5t": "H9nj5AOSswMphg1SFx7jaV-lB9w",
"kid": "H9nj5AOSswMphg1SFx7jaV-lB9w"
}
Now after lots of trying I was forced to hardcode certificate value of key that I got from Microsoft to get some success
I visited the URL https://login.microsoftonline.com/{myTenantId}/discovery/keys?appid={clientId}
And got this
{
"kty": "RSA",
"use": "sig",
"kid": "H9nj5AOSswMphg1SFx7jaV-lB9w",
"x5t": "H9nj5AOSswMphg1SFx7jaV-lB9w",
"n": "iheTlbqieo0KzaFBTz6dxt9LAENfe2d0ywnpvZx9khzurtApc4ThWBpeoTBI6UpReeCwyW6DQjJSGCqHwe-wqRdgoiMc0PV-danrh0px38x2KL_j7VoHR0hlQBYOpp5GBMdz-Nsc80wBtHAqxz7Nno3qYNTXUvwZ2LSbbvgoPXrh0zhLlSrn2gAroRv6Z8xSOVg3CSmZeVgZHJv4aMYQiBiIIZW68YN5ywHxOf6-LdrhqN24NPSLYUNPKGxCkUkWg-VV-iulUqIkDxn3SKiX4zVA-jevbuUrsK3MeRHoJLqXMf8KnpPkf4ZLCTLdUNFyLyxFvqnm6QUAgpDrQ_rxhw",
"e": "AQAB",
"x5c": [
"MIIC/jCCAeagAwIBAgIJAJjMBdn72zEjMA0GCSqGSIb3DQEBCwUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMjQwODE4MTkzMzIzWhcNMjkwODE4MTkzMzIzWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiheTlbqieo0KzaFBTz6dxt9LAENfe2d0ywnpvZx9khzurtApc4ThWBpeoTBI6UpReeCwyW6DQjJSGCqHwe+wqRdgoiMc0PV+danrh0px38x2KL/j7VoHR0hlQBYOpp5GBMdz+Nsc80wBtHAqxz7Nno3qYNTXUvwZ2LSbbvgoPXrh0zhLlSrn2gAroRv6Z8xSOVg3CSmZeVgZHJv4aMYQiBiIIZW68YN5ywHxOf6+LdrhqN24NPSLYUNPKGxCkUkWg+VV+iulUqIkDxn3SKiX4zVA+jevbuUrsK3MeRHoJLqXMf8KnpPkf4ZLCTLdUNFyLyxFvqnm6QUAgpDrQ/rxhwIDAQABoyEwHzAdBgNVHQ4EFgQUVOgUL8kBykezhhopW+Whb6mtqhowDQYJKoZIhvcNAQELBQADggEBAHqgxwjA+D7HzUT5mNRf8uizC9SkHdLXULyZTehFbSYEB3SYXZIbUWJcd88uaDUZ7CxPaojg6swmuB9XnzcZh8Gpa5y1ThSWvmPvUIDq1e03OJzhsC6dPmwoysmLvovngkGJ/AWURK7zRot53DLYWt0d/0KPDExPsovava0LeQjU8bLxHwQ9+8JFFxoN/aE85TQc8Whw+xZ4NX6BdTiucY8ku0smJLblwWui+mGyFSfDFW9r/r8LMUifgXTkaLYz4Z7sQdPFsdSoAR2jOzKhiz6acyQ1R4QUheN0EBmuPSsbu9M+E6tSYD+4q3GmQWtXjVagJoKPos0DIhe785YZjtI="
]
}
As you can see both have same kid
.
This is my backend code:
private const string KeyString = @"MIIC/jCCAeagAwIBAgIJAJjMBdn72zEjMA0GCSqGSIb3DQEBCwUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMjQwODE4MTkzMzIzWhcNMjkwODE4MTkzMzIzWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiheTlbqieo0KzaFBTz6dxt9LAENfe2d0ywnpvZx9khzurtApc4ThWBpeoTBI6UpReeCwyW6DQjJSGCqHwe+wqRdgoiMc0PV+danrh0px38x2KL/j7VoHR0hlQBYOpp5GBMdz+Nsc80wBtHAqxz7Nno3qYNTXUvwZ2LSbbvgoPXrh0zhLlSrn2gAroRv6Z8xSOVg3CSmZeVgZHJv4aMYQiBiIIZW68YN5ywHxOf6+LdrhqN24NPSLYUNPKGxCkUkWg+VV+iulUqIkDxn3SKiX4zVA+jevbuUrsK3MeRHoJLqXMf8KnpPkf4ZLCTLdUNFyLyxFvqnm6QUAgpDrQ/rxhwIDAQABoyEwHzAdBgNVHQ4EFgQUVOgUL8kBykezhhopW+Whb6mtqhowDQYJKoZIhvcNAQELBQADggEBAHqgxwjA+D7HzUT5mNRf8uizC9SkHdLXULyZTehFbSYEB3SYXZIbUWJcd88uaDUZ7CxPaojg6swmuB9XnzcZh8Gpa5y1ThSWvmPvUIDq1e03OJzhsC6dPmwoysmLvovngkGJ/AWURK7zRot53DLYWt0d/0KPDExPsovava0LeQjU8bLxHwQ9+8JFFxoN/aE85TQc8Whw+xZ4NX6BdTiucY8ku0smJLblwWui+mGyFSfDFW9r/r8LMUifgXTkaLYz4Z7sQdPFsdSoAR2jOzKhiz6acyQ1R4QUheN0EBmuPSsbu9M+E6tSYD+4q3GmQWtXjVagJoKPos0DIhe785YZjtI=";
[HttpPost("validate-token3")]
public IActionResult DecodeValidToken()
{
try
{
string accessToken = "MyTokenHereWhichICannotPasteHere";
var certificate = new X509Certificate2(Convert.FromBase64String(KeyString));
var rsa = certificate.GetRSAPublicKey();
var rsaSecurityKey = new RsaSecurityKey(rsa);
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = rsaSecurityKey,
ValidateIssuer = false,
ValidateAudience = false
};
var principal = tokenHandler.ValidateToken(accessToken, validationParameters, out SecurityToken validatedToken);
return Ok(principal.Claims);
}
catch (SecurityTokenInvalidSignatureException ex)
{
return Unauthorized(new { Message = "Invalid token signature", Details = ex.Message });
}
catch (Exception ex)
{
return StatusCode(500, new { Message = "Internal server error", Details = ex.Message });
}
}
Even though my token has kid
still I am getting this error:
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: 'IDX10503: Signature validation failed. Token does not have a kid. Keys tried: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey, KeyId: '', InternalId: 'y16ADo8cPNxOAHfa262kqg7euKagSoVzzY5Wo4CzT5c'. , KeyId: '. Number of keys in TokenValidationParameters: '1'. Number of keys in Configuration: '0'. Exceptions caught:
''.
token: '[Security Artifact of type 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' is hidden. For more details, see https://aka.ms/IdentityModel/SecurityArtifactLogging.]'. See https://aka.ms/IDX10503 for details.'
I am trying to solve this issue from last 4 days, but still no luck yet, even I have tried a different approach in which we are getting keys from Microsoft and passing all the keys to validate
I generated access token and tried to validate the token using the same code as below:
Got the same error:
Note that: Microsoft Graph API tokens are not meant to be validated.
Hence to resolve the error, check the below:
Expose an API and add a scope:
Grant API permission:
Make sure to generate access token by passing scope as api://***/ScopeName
Now the token is valid and got the output successfully:
class Program
{
private const string KeyString = @"xxx";
static void Main(string[] args)
{
string accessToken = "MyTokenHereWhichICannotPasteHere";
try
{
var certificate = new X509Certificate2(Convert.FromBase64String(KeyString));
var rsa = certificate.GetRSAPublicKey();
var rsaSecurityKey = new RsaSecurityKey(rsa);
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = rsaSecurityKey,
ValidateIssuer = false,
ValidateAudience = false
};
var principal = tokenHandler.ValidateToken(accessToken, validationParameters, out SecurityToken validatedToken);
Console.WriteLine("Token is valid. Claims:");
foreach (var claim in principal.Claims)
{
Console.WriteLine($"{claim.Type}: {claim.Value}");
}
}
catch (SecurityTokenInvalidSignatureException ex)
{
Console.WriteLine($"Unauthorized: Invalid token signature. Details: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Refer this GitHub blog Azure AD Authentication, Next-Auth JWT and .NET Core Web API · nextauthjs/next-auth · Discussion #5996 · GitHub where the access token is generated in the frontend using Next-Auth.