JWT Token valid by JwtSecurityTokenHandler but no valid by JWT.IO - IdentityServer4 - c#

We use IdentityServer4 and the generated tokens are accepted by the Token middleware.
But the token validation on jwt.io fails
I've already tried the validation on jwt.io with PEM and JWK. Both fails.
As supposed here: Other question
If I do the same with another IdentiyServer Instance but same configuration (IdentityServer3) It works, but the JWK looks different.
(no x5c / c5t)
Are there other tools to check why the validation is failing?
Can the other JWK format be related to this (x5t / x5c)? And if so, can i Configure IdentyServer to return the 'old format' ?
Any other Ideas?
To check the token without middleware i wrote this small piece of code:
var jwksJson = #"
{
""keys"": [
{
""kty"": ""RSA"",
""use"": ""sig"",
""kid"": ""0313B7152576EF7415003F309C7E7F5EABADD0B7RS256"",
""x5t"": ""AxO3FSV273QVAD8wnH5_Xqut0Lc"",
""e"": ""AQAB"",
""n"": ""3FOm0...J0"",
""x5c"": [
""MI....=""
],
""alg"": ""RS256""
}
]
}
";
var token = "ey...ktYx7QBZw";
var jwks = new JsonWebKeySet(jwksJson);
var jwk = jwks.Keys.First();
var validationParameters = new TokenValidationParameters
{
IssuerSigningKey = jwk,
ValidateLifetime = false,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidAudiences = new []{"aaaa","bbbb","ccc"}, // Your API Audience, can be disabled via ValidateAudience = false
ValidIssuer = "https://dev.xxxx.xxxx.com" // Your token issuer, can be disabled via ValidateIssuer = false
};
WritePem(jwk);
if (ValidateToken(token, validationParameters))
{
Console.WriteLine("Token Valid");
}
else
{
Console.WriteLine("Token Invalid");
}
static bool ValidateToken(string token, TokenValidationParameters validationParameters)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
return validatedToken != null;
}
catch (Exception e)
{
Console.WriteLine(e);
return false;
}
}
static void WritePem(JsonWebKey jsonWebKey)
{
var key = new RSACryptoServiceProvider();
key.ImportParameters(new RSAParameters
{
Modulus = Base64Url.Decode(jsonWebKey.N),
Exponent = Base64Url.Decode(jsonWebKey.E)
});
var pubkey = Convert.ToBase64String(key.ExportSubjectPublicKeyInfo());
const string pemHeader = "-----BEGIN PUBLIC KEY-----";
const string pemFooter = "-----END PUBLIC KEY-----";
var publicKeyPem = pemHeader + Environment.NewLine + pubkey + Environment.NewLine + pemFooter;
Console.WriteLine(publicKeyPem);
}

I've created a new certificate and it works now. I've no idea why the old certificate did not work.

Related

C# - Verify Google Cloud Platform token

I'm asking for help to verify the GCP token (x-gcp-marketplace-token).
Basically I would like to implement a token verification explained here.
Expected language C#. preferable to be compatible with .Net Core.
UPDATE
This is what I have so far. It is a POC don't worry about fields naming.
Actually validation fails.
private async Task<OrderViewModel> GoogleResolveOrder(string token)
{
OrderViewModel result = new OrderViewModel { Transaction = new TransactionViewModel() };
//Decode token
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
JwtSecurityToken tokenInfo = handler.ReadJwtToken(token);
if (!String.IsNullOrEmpty(tokenInfo.Header.Kid) && !String.IsNullOrEmpty(tokenInfo.Payload.Sub) && !String.IsNullOrEmpty(tokenInfo.Payload.Iss))
{
string keyId = tokenInfo.Header.Kid;
string keysEndpoint = tokenInfo.Payload.Iss;
string procurementId = tokenInfo.Payload.Sub;
//load gcp certificate
var response = await _client.GetAsync(keysEndpoint);
string googleCretificateInfoResponse = await response.Content.ReadAsStringAsync();
Dictionary<string, string> certifateInfo = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(googleCretificateInfoResponse);
//Get public key from certificate
string certificatePublicKeyText = certifateInfo[keyId];
byte[] certicateStream = Encoding.UTF8.GetBytes(certificatePublicKeyText);
X509Certificate certificate = new X509Certificate(certicateStream);
try
{
// Verify token with public key, leave private key as null
SecurityToken tokenValidated = null;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateAudience = true,
ValidAudience = tokenInfo.Payload.Aud[0],
ValidateIssuer = true,
ValidIssuer = "https://www.googleapis.com/robot/v1/metadata/x509/cloud-commerce-partner#system.gserviceaccount.com",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(certificate.GetPublicKey())
};
ClaimsPrincipal clainsResult = handler.ValidateToken(token, validationParameters, out tokenValidated);
//TODO- IF not exception Token is valid
result.Transaction.MinimunQuantity = 1;
}
catch (Exception ex)
{
result.Transaction.MinimunQuantity = -1;
result.Transaction.FulfillmentStatus = ex.Message;
}
//TODO- Delete
result.Transaction.OrgId = tokenInfo.Payload.Sub;
result.Transaction.PlanName = tokenInfo.Payload.Iss;
result.Transaction.PlanID = tokenInfo.Header.Kid;
result.Transaction.Customer = String.Join(',', tokenInfo.Payload.Aud);
result.Transaction.CustomerEmailId = tokenInfo.Payload.Exp.Value.ToString();
result.Transaction.SubscriptionId = token;
}
return result;
}
I'm still working on this.
Getting this error:
IDX10501: Signature validation failed. Unable to match key: kid: '[PII is hidden. For more details, see aka.ms/IdentityModel/PII.]'. Exceptions caught: '[PII is hidden. For more details, see aka.ms/IdentityModel/PII.]'. token: '[PII is hidden. For more details, see aka.ms/IdentityModel/PII.]'.

How to validate jwt token from different issuer

I'm using actionable messages (with Outlook web app) to call an Logic App. Therefore I am getting an Bearer token in the request:
"Action-Authorization": "Bearer eyJ0eXAi..."
Callstack:
Outlook web app -> Logic App -> my endpoint hosted in azure
Now I tried to validate the token with jwt.io but getting an Issue that the Signature is invalid.
So I tried to validate it in c# with the JwtSecurityTokenHandler.
I tried to add https://substrate.office.com/sts/ to the issuer list, but it seems like the validation don't even get there.
I'm using the following code to validate the jwt token issued by office.com:
bool IsAuthorized(HttpActionContext actionContext)
{
var valid = base.IsAuthorized(actionContext);
// Custom handle for Bearer token, when invalid from base-class
if (!valid && actionContext.Request.Headers.Authorization.Scheme == "Bearer")
{
var jwt = actionContext.Request.Headers.Authorization.Parameter;
var th = new JwtSecurityTokenHandler();
var sjwt = th.ReadToken(jwt) as JwtSecurityToken;
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
//IssuerSigningToken = sjwt,
ValidateActor = false,
ValidateAudience = false,
ValidateIssuer = true,
ValidateLifetime = true,
ValidIssuers = new[] { "https://substrate.office.com/sts/" },
ValidAudiences = new[] {"https://XXX.logic.azure.com"}
};
SecurityToken validatedToken;
try
{
th.ValidateToken(jwt, validationParameters, out validatedToken);
}
catch (Exception ex)
{
return false;
}
}
return valid;
}
Here is my JWT token:
I am getting the exception:
IDX10500: Signature validation failed. Unable to resolve SecurityKeyIdentifier: 'SecurityKeyIdentifier
(
IsReadOnly = False,
Count = 2,
Clause[0] = X509ThumbprintKeyIdentifierClause(Hash = 0x818...),
Clause[1] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause
)
', ...
Even though I set ValidateIssuerSigningKey = false.
Is there a way to accept https://substrate.office.com/sts/ as a valid issuer?
The exception says that the "Signature validation failed".
To resolve this problem we can't just add the wanted valid issuer to ValidIssuers, we need the to verify that the token is issued from the issuer itself.
Especially for this case with office.com being the issuer I found the expected key (JWK - JSON Web Key) here:
https://substrate.office.com/sts/common/discovery/keys
(also https://substrate.office.com/sts/common/.well-known/openid-configuration)
Here is the working code:
bool IsAuthorized(HttpActionContext actionContext)
{
var valid = base.IsAuthorized(actionContext);
// Custom handle for Bearer token, when invalid from base-class
if (!valid && actionContext.Request.Headers.Authorization.Scheme == "Bearer")
{
var jwt = actionContext.Request.Headers.Authorization.Parameter;
var th = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new JsonWebKey(GetJWK()),
ValidIssuers = new[] { "https://substrate.office.com/sts/" }
};
Microsoft.IdentityModel.Tokens.SecurityToken validatedToken;
try
{
var claims = th.ValidateToken(jwt, validationParameters, out validatedToken);
valid = true;
}
catch (Exception ex)
{
valid = false;
}
}
return valid;
}
// Get the token from configuration
private string GetJWK()
{
return ConfigurationManager.AppSettings["ida:jwks_json"];
}
In the appsettings I put the RSA key from the website for validating the token, it looks like:
{"kty":"RSA","use":"sig","kid":"gY...","x5t":"gY...","n":"2w...","e":"AQAB","x5c":["MII..."]}

Manually validating a JWT token in C#

I am having some trouble manually validating a JWT token issued by Identity Server 4. Using the
ClientId: "CLIENT1"
ClientSecret: "123456"
The exception I keep getting is: IDX10501: Signature validation failed. Unable to match keys: '[PII is hidden by default. Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]'
Is anyone able to advise me where I am going wrong.
private static void ValidateJwt(string jwt, DiscoveryResponse disco)
{
var parameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidIssuer = disco.Issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("123456")),
ValidAudience = "CLIENT1",
//IssuerSigningKeys = keys,
// ValidateAudience = true,
// ValidateLifetime = true,
};
SecurityToken validatedToken;
var handler = new JwtSecurityTokenHandler();
handler.InboundClaimTypeMap.Clear();
try
{
var user = handler.ValidateToken(jwt, parameters, out validatedToken);
}
catch(Exception ex)
{
var error = ex.Message;
}
}
Check out ValidateJwt() in this sample:
https://github.com/IdentityServer/IdentityServer4/blob/master/samples/Clients/old/MvcManual/Controllers/HomeController.cs
The bit you're missing is loading the public key from the discovery document.
Try changing the length of your private key. Your private key is too small to be encoded I suppose.
For manual verification, you could just use
static byte[] FromBase64Url(string base64Url)
{
string padded = base64Url.Length % 4 == 0
? base64Url : base64Url + "====".Substring(base64Url.Length % 4);
string base64 = padded.Replace("_", "/")
.Replace("-", "+");
return Convert.FromBase64String(base64);
}
This also answers #henk-holterman 's question
Encoding.UTF8.GetBytes( can't be the right way to do this.
Although realistically a better way to do this is via the OIDC discovery-endpoint
Auth0 has a good article about this using standard NuGet packages. Basically, you load everything needed from the discovery end-point.
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{auth0Domain}.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
TokenValidationParameters validationParameters =
new TokenValidationParameters
{
ValidIssuer = auth0Domain,
ValidAudiences = new[] { auth0Audience },
IssuerSigningKeys = openIdConfig.SigningKeys
};
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken("eyJhbGciOi.....", validationParameters, out validatedToken);
You can read more about it here Or their GitHub sample page about this here
In my case, I did not have a discovery endpoint. Just a JWKS endpoint.
So I opted to do this.
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
public class ExpectedJwksResponse
{
[JsonProperty(PropertyName = "keys")]
public List<JsonWebKey> Keys { get; set; }
}
private static async Task<List<SecurityKey>> GetSecurityKeysAsync()
{
// Feel free to use HttpClient or whatever you want to call the endpoint.
var client = new RestClient("<https://sample-jwks-endpoint.url>");
var request = new RestRequest(Method.GET);
var result = await client.ExecuteTaskAsync<ExpectedJwksResponse>(request);
if (result.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new Exception("Wasnt 200 status code");
}
if (result.Data == null || result.Data.Keys == null || result.Data.Keys.Count == 0 )
{
throw new Exception("Couldnt parse any keys");
}
var keys = new List<SecurityKey>();
foreach ( var key in result.Data.Keys )
{
keys.Add(key);
}
return keys;
}
private async Task<bool> ValidateToken(token){
TokenValidationParameters validationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateLifetime = true,
ValidIssuer = "https://sample-issuer.com",
ValidAudiences = new[] { "https://sample-audience/resource" },
IssuerSigningKeys = await GetSecurityKeysAsync()
};
var user = null as System.Security.Claims.ClaimsPrincipal;
SecurityToken validatedToken;
try
{
user = handler.ValidateToken(token, validationParameters, out validatedToken);
}
catch ( Exception e )
{
Console.Write($"ErrorMessage: {e.Message}");
return false;
}
var readToken = handler.ReadJwtToken(token);
var claims = readToken.Claims;
return true;
}
You have specified:
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("secret"))
but the JwtSecurityTokenHandler could not match it with the key which can be part of jwt header itself. Basically it means that your configuration has mismatch[es] with configuration of the real issuer. The error suggests that this relates to the signature keys.
Please, check the configuration of that issuer (if you can), find out missed parts, and try again.
You can use jwt.io to debug your jwt online.
IdentityServer signs the JWT using RS256. This means you need to use a public key to verify the JWT (you can get this from the discovery document).
The client id & client secret are client credentials used for requesting tokens. They have no part in validating them.
You are trying to use SymmetricKey for JWT validation. Try looking your token in JWT.io and if algorithm is"RS256" then SymmetricKey won't work.
Please check when you create JWT token make sure that you added SigningCredentials.
var token = new JwtSecurityToken(
Constants.Audiance,Constants.Issuer,claims
notBefore:DateTime.Now,
expires:DateTime.Now.AddHours(1),
**signinCredential**
);

Verify Firebase JWT in c# .net

I am trying to verify a json web token obtained by a firebase android client and passed to a server running .net
Following the answer here I created these methods to validate the token and extract the uid:
public static async Task<string> GetUserNameFromTokenIfValid(string jsonWebToken)
{
const string FirebaseProjectId = "testapp-16ecd";
try
{
// 1. Get Google signing keys
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://www.googleapis.com/robot/v1/metadata/");
HttpResponseMessage response = await client.GetAsync("x509/securetoken#system.gserviceaccount.com");
if (!response.IsSuccessStatusCode) { return null; }
var x509Data = await response.Content.ReadAsAsync<Dictionary<string, string>>();
SecurityKey[] keys = x509Data.Values.Select(CreateSecurityKeyFromPublicKey).ToArray();
// Use JwtSecurityTokenHandler to validate the JWT token
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
// Set the expected properties of the JWT token in the TokenValidationParameters
TokenValidationParameters validationParameters = new TokenValidationParameters()
{
ValidAudience = FirebaseProjectId,
ValidIssuer = "https://securetoken.google.com/" + FirebaseProjectId,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = keys
};
SecurityToken validatedToken;
ClaimsPrincipal principal = tokenHandler.ValidateToken(jsonWebToken, validationParameters, out validatedToken);
var jwt = (JwtSecurityToken)validatedToken;
return jwt.Subject;
}
catch (Exception e)
{
return null;
}
}
static SecurityKey CreateSecurityKeyFromPublicKey(string data)
{
return new X509SecurityKey(new X509Certificate2(Encoding.UTF8.GetBytes(data)));
}
When I run the code I get the response:
{"IDX10501: Signature validation failed. Unable to match 'kid': 'c2154b0435d58fc96a4480bd7655188fd4370b07', \ntoken: '{"alg":"RS256","typ":"JWT","kid":"c2154b0435d58fc96a4480bd7655188fd4370b07"}......
Calling https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com does return a certificate with a matching id:
{
"c2154b0435d58fc96a4480bd7655188fd4370b07": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIRZGQCmoKoNQwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYx\nMTIxMDA0NTI2WhcNMTYxMTI0MDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKHbxqFaNQyrrrv8gocpQjES+HCum8XRQYYLRqstJ12FGtDN\np32qagCbc0x94TaBZF7tCPMgyFU8pBQP7CvCxWxoy+Xdv+52lcR0sG/kskr23E3N\nJmWVHT3YwiMwdgsbWDIpWEbvJdn3DPFaapvD9BJPwNoXuFCO2vA2rhi1LuNWsaHt\nBj5jTicGCnt2PGKUTXJ9q1hOFi90wxTVUVMfFqDa4g9iKqRoaNaLOo0w3VgsFPlr\nMBca1fw1ArZpEGm3XHaDOiCi+EZ2+GRvdF/aPNy1+RdnUPMEEuHErULSxXpYGIdt\n/Mo7QvtFXkIl6ZHvEp5pWkS8mlAJyfPrOs8RzXMCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAJYXDQFIOC0W0ZwLO/5afSlqtMZ+lSiFJJnGx/IXI5Mi\n0sBI3QA7QXmiNH4tVyEiK+HsFPKAYovsbh7HDypEnBGsz9UmEU6Wn6Qu9/v38+bo\nLant6Ds9ME7QHhKJKtYkso0F2RVwu220xZQl1yrl4bjq+2ZDncYthILjw5t+8Z4c\nQW5UCr2wlVtkflGtIPR1UrvyU13eiI5SPkwOWPZvG2iTabnLfcRIkhQgIalkznMe\niz8Pzpk9eT8HFeZYiB61GpIWHG4oEb1/Z4Q//os+vWDQ+X0ARTYhTEbwLLQ0dcjW\nfg/tm7J+MGH5NH5MwjO+CI4fA3NoGOuEzF1vb7/hNdU=\n-----END CERTIFICATE-----\n"
I have successfully validated this token using the Java call (made in kotlin)
FirebaseAuth.getInstance().verifyIdToken(idToken).addOnSuccessListener { decodedToken ->
val uid = decodedToken.uid
}
I'm sure by now you have figured out the solution for this, but for future people who come across this question.
Set the KeyId for the X509SecurityKey
x509Data.Select(cert => new X509SecurityKey(new X509Certificate2(Encoding.UTF8.GetBytes(cert.Value)))
{
KeyId = cert.Key
})
.ToArray()
This will allow the TokenValidationParameters to look up which issuerKey to validate against.

Ignoring signature in JWT

I have an web application that is using OpenId Connect. I created a self signed certificate but it is still not signed by a CA.
How can I ignore the signature validation?
This is what I have so far:
SecurityToken validatedToken = null;
var tokenHandler = new JwtSecurityTokenHandler {
Configuration = new SecurityTokenHandlerConfiguration {
CertificateValidator = X509CertificateValidator.None
},
};
TokenValidationParameters validationParams =
new TokenValidationParameters()
{
ValidAudience = ConfigurationManager.AppSettings["Audience"],
ValidIssuer = ConfigurationManager.AppSettings["Issuer"],
AudienceValidator = AudienceValidator,
ValidateAudience = true,
ValidateIssuer = true
};
return tokenHandler.ValidateToken(jwtToken, validationParams, out validatedToken);
It throws the following exception:
IDX10500: Signature validation failed. Unable to resolve
SecurityKeyIdentifier: 'SecurityKeyIdentifier\r\n (\r\n
IsReadOnly = False,\r\n Count = 1,\r\n Clause[0] =
System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause\r\n
)\r\n', \ntoken:
'{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"issuer_rsaKey\"}.{\"iss\":...
Don't ignore the signature, this is dangerous!
Even if you use a self-signed certificate, you will be able to use the public key for signature validation.
Since you are using OpenId Connect, you should be able to get the public key for your signing certificate by heading over to /.well-known/jwks.
Then you can setup your validation parameters like this:
var certificate = new X509Certificate2(Convert.FromBase64String(yourPublicKeyGoesHere));
var validationParameters = new TokenValidationParameters {
IssuerSigningTokens = new[] { new X509SecurityToken(certificate) }
};
After that, you can call ValidateToken:
SecurityToken token;
var claimsPrincipal = handler.ValidateToken(encodedToken, validationParameters, out token);
You really want to ignore the signature?
Remember, if you do, how do you know someone didn't tamper with the data inside the token? You could easily decode the base64 url encoded payload and change the subject. And if you rely on that in your application, you'll be in trouble (hint: someone accessing someone else data)
You REALLY, REALLY want to ignore it?
You can use ReadToken and just skip every validation there is:
var badJwt = new JwtSecurityTokenHandler()
.ReadToken(encodedMaliciousToken) as JwtSecurityToken;
DON'T do that though, it's bad practice.
public TokenValidationParameters CreateTokenValidationParameters()
{
var result = new TokenValidationParameters
{
ValidateIssuer = false,
ValidIssuer = ValidIssuer,
ValidateAudience = false,
ValidAudience = ValidAudience,
ValidateIssuerSigningKey = false,
//IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey)),
//comment this and add this line to fool the validation logic
SignatureValidator = delegate(string token, TokenValidationParameters parameters)
{
var jwt = new JwtSecurityToken(token);
return jwt;
},
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
};
result.RequireSignedTokens = false;
enter code here
return result;
}

Categories

Resources