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

IdentityModel extensions for .Net
MIT License
1.05k stars 396 forks source link

JWT Signature validation fails in .NET Core app targetting .NET Framework 4.7.1 #877

Closed Tratcher closed 5 years ago

Tratcher commented 6 years ago

From @fabiodaniele on February 13, 2018 15:55

Hi, I was having an issue trying to authenticate users to a .NET Core WebAPI using a JWT bearer token generated by a WSO2 Identity Server.

The project targets .NET Framework 4.7.1 and references Microsoft.AspNetCore.Authentication.JwtBearerToken.

At first, I thought it was an issue related to my WSO2 IS configuration.

Then, I found this article: https://www.jerriepelser.com/blog/manually-validating-rs256-jwt-dotnet/ and tried to execute the same code found there in a new .NET Core Console app: it worked!

So, I thought: "it maybe an issue of my WebAPI project".

I then made a few tries with two different brand new .NET Core WebAPI projects, one targetting .NET Core 2.0 and the other one targetting .NET Framework 4.7.1, using the same startup code in both.

Here is the code:

      // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{Configuration["OpenId:Authority"]}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
            OpenIdConnectConfiguration openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).Result;

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.IncludeErrorDetails = true;
                    options.TokenValidationParameters.ValidateIssuer = true;
                    options.TokenValidationParameters.ValidateAudience = true;
                    options.TokenValidationParameters.ValidateIssuerSigningKey = true;
                    options.TokenValidationParameters.ValidIssuer = Configuration["OpenId:Issuer"];
                    options.TokenValidationParameters.ValidAudiences = new[] { Configuration["OpenId:Audience"] };
                    options.TokenValidationParameters.IssuerSigningKeys = openIdConfig.SigningKeys;
                });

            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseAuthentication();

            app.UseMvc();
        }

I decorated the default ValuesController with the Authorize attribute and tried to invoke it via Postman, with a new JWT token obtained from the WSO2 IS.

The results are different:

So the question is: is this the expected behavior or is it a bug?

Thanks in advance.

Copied from original issue: aspnet/Security#1649

allevyMS commented 6 years ago

I can confirm the same issue.

I have created a minimal repro for this as a console app targeting either .net 4.7 or .net core 2.0 I have removed all aspnet dependencies and narrowed the issue down to the System.IdentityModel.Tokens.Jwt 5.2.1 package.

The following works using .net core and fails when targeting 4.7:

        var jwtHandler = new JwtSecurityTokenHandler();

        var byteCert = Convert.FromBase64String(@"CERTPLACEHOLDER");

        var cert = new X509Certificate2(byteCert);

        var rsaSecurityKey = new RsaSecurityKey(cert.GetRSAPublicKey())
        {
            KeyId = "KEYID"
        };

        SecurityToken token;
        jwtHandler.ValidateToken(@"TOKENPLACEHOLDER",
        new TokenValidationParameters
        {
            RequireSignedTokens = true,
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            IssuerSigningKey = rsaSecurityKey
        },
        out token);
        Console.WriteLine(token);
    }

Exception in 4.7:

Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException occurred HResult=0x80131500 Message=IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey , KeyId: KEYID '. Exceptions caught: 'System.NotSupportedException: IDX10641: Key is not supported: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey'. at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider.ResolveAsymmetricAlgorithm(SecurityKey key, String algorithm, Boolean willCreateSignatures) at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider..ctor(SecurityKey key, String algorithm, Boolean willCreateSignatures) at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures) at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters) '. token: StackTrace: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken) at jwt2.Program.Main(String[] args) in C:\Users\allevy\Source\Repos\jwt2\jwt2\Program.cs:line 36

Alon

brentschmaltz commented 6 years ago

@allevyMS I'm on vacation and can't look at this for a week or so, but should be able to work around this by plugging in a custom Cryptofactory using TokenValidationParameters

cvocvo commented 6 years ago

Is this patched in the 5.2.2 release? Seeing the same issue running v5.2.1 in an ASP.NET project targeting 4.6.2.

manish13oct commented 6 years ago

Hi,

I have a Security Token Service running on 4.5.2 and I have a requirement to support CNG certificates based tokens. What should be recommended approach for this?

For now I am experimenting with migrating from framework 4.5.2 to 4.6.2, but I am running into similar error as posted above in the thread while executing the following code.

Would this be resolved by the solution that you are working on. By when I can expect the resolution and which .net framework version? Is there any work around that I can adopt?

` The exception is thrown at this line -> handler.WriteToken(secToken);

            var expires = DateTime.UtcNow.AddMinutes(10);

            var certName = ConfigurationManager.AppSettings["CertName"];

            X509Certificate2 certificate = GetCertificate(StoreName.My, StoreLocation.LocalMachine, certName);

            IdentityModelEventSource.ShowPII = true;

            var alg = certificate.GetRSAPrivateKey();

            Microsoft.IdentityModel.Tokens.RsaSecurityKey  securityKey = new Microsoft.IdentityModel.Tokens.RsaSecurityKey(alg);

            var value = certificate.SignatureAlgorithm.FriendlyName;

            var SigningCredentials1 =
                new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey,
                    SecurityAlgorithms.RsaSha256Signature);

            var header = new JwtHeader(SigningCredentials1);

            var now = DateTime.UtcNow;

            var payload = new JwtPayload
            {
                {"iss", "a5fgde64-e84d-485a-be51-56e293d09a69"},
                {"scope", "https://example.com/ws"},
                {"aud", "https://example.com/oauth2/v1"},
                {"iat", now},
            };

            var secToken = new JwtSecurityToken(header, payload);

            var handler = new JwtSecurityTokenHandler();

            var tokenString = handler.WriteToken(secToken);`

Exception: IDX10641: Key is not supported: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey'. Stack trace: at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider.ResolveAsymmetricAlgorithm(SecurityKey key, String algorithm, Boolean willCreateSignatures) at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider..ctor(SecurityKey key, String algorithm, Boolean willCreateSignatures) at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures) at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateEncodedSignature(String input, SigningCredentials signingCredentials) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.WriteToken(SecurityToken token) at CNGCertificateSupport.Program.Main(String[] args) in c:\OSI\SAF\Spikes\CNG\CNGCertificateSupport\CNGCertificateSupport\Program.cs:line 131

Thanks in advance, Manish

brentschmaltz commented 6 years ago

@manish13oct @cvocvo this dropped off our radar. What happened was a change to .Net between 4.5.1 and 4.6 where X509Certificate2.GetRSAPrivateKey returns a different type than RSACryptoServiceProvider, it returns RsaCng. ResolveRsaAlgorithm returns as RSACryptoServiceProvider, which is null. Then the runtime throws.

The NetStandard1_4 will work. We are looking at a fix.

brentschmaltz commented 6 years ago

@Tratcher @manish13oct @cvocvo with this commit, this should now work. 5.2.3 will have this fix.

In 5.2.4 we plan on introducing a 4.6.0 or 4.6.1 target, that will solve the issue for applications that can use that target. As we plan on supporting 4.5.1 and 4.5.2 for a while, we need this fix as it has nice SHA2 support. We plan on extending this adapter to use CNG for certs and ecdsa.

cvocvo commented 6 years ago

@brentschmaltz Thanks! -- What's the ETA for 5.2.3's release?

Edit: Nevermind looks like 5.2.3's name was dropped in favor of 5.2.4; I see 5.2.4 on nuget (https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/) but not on github releases (https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)

brentschmaltz commented 6 years ago

@cvocvo currently we only release on nuget.org. Yea, 5.2.5 should have a 4.6.1 or 4.6.0 target.

cvocvo commented 6 years ago

Perfect; hopefully that 4.6.x target will resolve the issue for us. I rolled out 5.2.4 earlier today into production and our logging caught a couple of the error reoccurring. Do you know (approximately) what the timeline on 5.2.5 is? (I couldn't find a way to subscribe to nuget for alerts on releases)

brentschmaltz commented 6 years ago

@cvocvo been on vacation, back now. 5.2.5 targets Aug 10th.

We keep our milestone info here: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/milestones

cvocvo commented 6 years ago

@brentschmaltz No worries; your great support on this is much appreciated!

cvocvo commented 6 years ago

@brentschmaltz Just circling back; any updates on the 5.2.5 release targeting .NET 4.6.0+? Any plans to target 4.7.0 down the road too (not sure if even needed if there's a 4.6.0 target) ?

brentschmaltz commented 5 years ago

@cvocvo our next release is 5.3.0 (was 5.2.5) and we are just waiting for approval for our package id's. Can you try our nightly packages (5.3.1.xyz) on myget here: https://www.myget.org/gallery/azureadwebstacknightly

cvocvo commented 5 years ago

@brentschmaltz Apologies -- missed this message. I just checked and see 5.3.0 is out now. I'm rolling that out to our staging environment for further testing. Thank you! (I'll edit post when I have test results)

brentschmaltz commented 5 years ago

@cvocvo I'll close this out, if you hit issues please reopen. We added a 4.6.1 target, there is not much more in 4.7, from a crypto point of view that will add a lot of value, so we didn't add that target.

RubenDelange commented 5 years ago

@brentschmaltz & @Tratcher : I ended up here because I got the same problem as initially reported by @fabiodaniele and @allevyMS : ASP.NET Core web application targetting .NET 4.7.2 with the Microsoft.AspNetCore.Authentication.JwtBearer package that references System.IdentityModel.Tokens.Jwt v5.2.0.

I am implementing the OpenID Connect Authorization Code flow, but I'm stuck with the following error when validating the JWT signature:

IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.JsonWebKey , KeyId: <my kid>
'.
Exceptions caught:
 ''.
token: <decoded JWT>

I'm 100% sure the JWT is valid and I'm using the correct JsonWebKey. If I manually validate the signature using the PEM in jwt.io it shows "signature verified".

Consoleapp targetting .NET Core 2.1 and using System.IdentityModel.Tokens.Jwt v5.3.0 to validate my JWT (JwtSecurityTokenHandler) using the same RSA key succeeds without any exception. Consoleapp targetting .NET 4.7.2 (or lower) and using System.IdentityModel.Tokens.Jwt v5.3.0 (or lower) to validate my JWT (JwtSecurityTokenHandler) using the same RSA key gives me the same IDX10503 exception.

I'm not using Dispose() or a using block as mentioned here: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/wiki/SecurityTokenInvalidSignatureException

Is there any way to get around this issue? It's becoming a showstopper for the project I'm working on.

Thanks in advance!

brentschmaltz commented 5 years ago

@RubenDelange I reopened this issue. Can you post or send me the JWT and the public key that can be used to validate?

RubenDelange commented 5 years ago

@brentschmaltz : code I'm using for both console apps:

static void Main(string[] args)
{
    var publicJwk = new JsonWebKey
    {
        KeyId = "xneUh4tPkUPsVH/okK+VMrXirVA=",
        Kid = "xneUh4tPkUPsVH/okK+VMrXirVA=",
        N = "ALq7LDOgZRirsfc308DI_hSwSRZsYpPhiD69WyA65s-wRpG276x5SoWeyMOZJMi4qz2CG6K51_mHov0ZFmdN3sXARQPOgvepwQ1hY2OPRVmWzitdAP3b_UDuJR_rBIsSXVjnaDOdF_y9vefmVWanDh3Aef5Dk_0TzPexMod_WbEgcnibIgg4aZMSUeFsAViYkYSfgXrF16nZm2A3QJTbSuFTGpr9VPVLdD7mRvBgNXhm117OTg5OIBzRUnLrKlcqIg41uPdFHxNCJI3ukfyw9hvoHb1qCdAO3I9L66ZXdXTjY6uhoqgP_OGo6ze3BpQtIcECInNU7qkNd8CJ_MMRqgE",
        Alg = "RS256",
        E = "AQAB",
        Kty = "RSA",
        Use = "sig"
    };

    // JWT to test
    const string testToken = "<JWT>";

    try
    {
        var user = ValidateAndDecode(testToken, publicJwk);

        Console.WriteLine($"Token is validated. User Id {user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value}");
    }
    catch (Exception e)
    {
        Console.WriteLine($"Error occurred while validating token: {e.Message}");
    }

    Console.WriteLine();
    Console.WriteLine("Press ENTER to continue...");
    Console.ReadLine();
}

private static ClaimsPrincipal ValidateAndDecode(string jwt, SecurityKey signingKey)
{
    var validationParameters = new TokenValidationParameters
    {
        IssuerSigningKey = signingKey,
        // We use an expired token for testing purposes => ignore expiration
        RequireExpirationTime = false,
        ValidateLifetime = false,
        //validating audience and issuer not relevant for this test
        ValidateAudience = false,
        ValidateIssuer = false
    };

    try
    {
        var claimsPrincipal = new JwtSecurityTokenHandler().ValidateToken(jwt, validationParameters, out var rawValidatedToken);

        //return (JwtSecurityToken)rawValidatedToken;
        return claimsPrincipal;
    }
    catch (SecurityTokenValidationException stvex)
    {
        // The token failed validation!
        throw new Exception($"Token failed validation: {stvex.Message}");
    }
    catch (ArgumentException argex)
    {
        // The token was not well-formed or was invalid for some other reason.
        throw new Exception($"Token was invalid: {argex.Message}");
    }
}
arieradle commented 5 years ago

@brentschmaltz & @Tratcher : I ended up here because I got the same problem as initially reported by @fabiodaniele and @allevyMS : ASP.NET Core web application targetting .NET 4.7.2 with the Microsoft.AspNetCore.Authentication.JwtBearer package that references System.IdentityModel.Tokens.Jwt v5.2.0.

I am implementing the OpenID Connect Authorization Code flow, but I'm stuck with the following error when validating the JWT signature:

IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.JsonWebKey , KeyId: <my kid>
'.
Exceptions caught:
 ''.
token: <decoded JWT>

I'm 100% sure the JWT is valid and I'm using the correct JsonWebKey. If I manually validate the signature using the PEM in jwt.io it shows "signature verified".

Consoleapp targetting .NET Core 2.1 and using System.IdentityModel.Tokens.Jwt v5.3.0 to validate my JWT (JwtSecurityTokenHandler) using the same RSA key succeeds without any exception. Consoleapp targetting .NET 4.7.2 (or lower) and using System.IdentityModel.Tokens.Jwt v5.3.0 (or lower) to validate my JWT (JwtSecurityTokenHandler) using the same RSA key gives me the same IDX10503 exception.

I'm not using Dispose() or a using block as mentioned here: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/wiki/SecurityTokenInvalidSignatureException

Is there any way to get around this issue? It's becoming a showstopper for the project I'm working on.

Thanks in advance!

Run into exactly same issue

RubenDelange commented 5 years ago

@brentschmaltz any updates regarding this issue?

brentschmaltz commented 5 years ago

@RubenDelange thanks for the ping. I'll poke around today.

brentschmaltz commented 5 years ago

@RubenDelange if you look at the modulus of this key, you will see that the first byte is '0'. This is contrary to JWA here; https://tools.ietf.org/html/rfc7518#section-6.3.1.1

The frustrating thing here is we tried to 'fix' this by removing the first byte if ZERO and byte length == 257, but we started failing in other libraries.

Perhaps we should revisit this issue and put in some type of compatibility flag that one can turn on.

RubenDelange commented 5 years ago

@brentschmaltz knowning the cause is a good starting point :)

I don't know if this explains why there are no problems with the same code in a Consoleapp targetting .NET Core 2.1 as opposed to a Consoleapp targetting .NET 4.7.2 (or lower), but I'm hoping this get's fixed somehow.

In my ASP.NET Core web application targetting .NET 4.7.2 (and System.IdentityModel.Tokens.Jwt v5.2.0), I have to us a dirty workaround to get passed signature validation :

SignatureValidator = delegate (string token, TokenValidationParameters parameters)
{
    return new JwtSecurityToken(token);
}

but I'd like to get rid of it asap.

Can you give some advice for a better and more secure workaround?

brentschmaltz commented 5 years ago

@RubenDelange some versions of Core will account for the null byte. I don't know of any versions of Desktop (4.x) that will.

Your code is actually bypassing signature validation. That is a big security hole. If you tell me how are you obtaining the keys, we can find the simplest extensibility to use.

We could contact the generator of the keys and ask them to not put in the null bite. Since you are not the only person to hit this, I have moved this into 5.3.1 and we will see if we can adjust automatically.

RubenDelange commented 5 years ago

@brentschmaltz : currently the same JWK applies as used in the console app code sample here

I know that not validating the signature leaves a big security hole, but I had to make some progress in development. I'm looking forward to your suggestion to enable signature validation for the given JWK.

brentschmaltz commented 5 years ago

@RubenDelange at the top of this thread, you are setting the keys directly onto TokenValidationParameters. You can do the following:

var N = "ALq7LDOgZRirsfc308DI_hSwSRZsYpPhiD69WyA65s-wRpG276x5SoWeyMOZJMi4qz2CG6K51_mHov0ZFmdN3sXARQPOgvepwQ1hY2OPRVmWzitdAP3b_UDuJR_rBIsSXVjnaDOdF_y9vefmVWanDh3Aef5Dk_0TzPexMod_WbEgcnibIgg4aZMSUeFsAViYkYSfgXrF16nZm2A3QJTbSuFTGpr9VPVLdD7mRvBgNXhm117OTg5OIBzRUnLrKlcqIg41uPdFHxNCJI3ukfyw9hvoHb1qCdAO3I9L66ZXdXTjY6uhoqgP_OGo6ze3BpQtIcECInNU7qkNd8CJ_MMRqgE";
var modulus = Base64UrlEncoder.DecodeBytes(N);
if (modulus.Length == 257 && modulus[0] == 0)
{
    var newModulus = new byte[256];
    Array.Copy(modulus, 1, newModulus, 0, 256);
    N = Base64UrlEncoder.Encode(newModulus);
}

var jsonWebKey = new JsonWebKey
{
    KeyId = "xneUh4tPkUPsVH/okK+VMrXirVA=",
    Kid = "xneUh4tPkUPsVH/okK+VMrXirVA=",
    N = N,
    Alg = "RS256",
    E = "AQAB",
    Kty = "RSA",
    Use = "sig"
};

You could also inform the token issuer, they shouldn't have a 0 byte prefix. They may run into issues with other stacks.

Of course you will need to run through the keys in: openIdConfig.SigningKeys

RubenDelange commented 5 years ago

Thank you @brentschmaltz , I can use JWT signature validation again!

brentschmaltz commented 5 years ago

@RubenDelange glad it worked. I will leave this open to see if we can make a change in 5.3.1.

cvocvo commented 5 years ago

Hi guys (@brentschmaltz) sorry for the long delay on circling back to this (it got back burnered for a while and buried on the to-do list). I would love to see anything/something changed to help this issue in 5.3.1 (we're still seeing issues in our project targeting 4.6.2). After reading the above posts a few times, I'm thinking that we're likely seeing the same problem as @RubenDelange ; could you help us place your modulus checking snippet of code as well?

Our validation code is heavily based on the sample here: https://github.com/Azure-Samples/active-directory-dotnet-webapi-manual-jwt-validation/blob/4b80657c5506c8cb30af67b9f61bb6aa68dfca58/TodoListService-ManualJwt/Global.asax.cs#L54

Here's the full snippet:

public class JwtTokenValidationHandler : DelegatingHandler
    {
        private readonly IConfigManager _configManager;
        public JwtTokenValidationHandler()
        {
            _configManager = new ConfigManager();
        }

        private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
        {
            token = null;
            IEnumerable<string> authheaders;
            if (!request.Headers.TryGetValues("Authorization", out authheaders))
            {
                if (!request.Headers.TryGetValues("X-Authorization-Token", out authheaders))
                {
                    return false;
                }
            }

            var bearerToken = authheaders.FirstOrDefault();
            token = bearerToken.StartsWith("Bearer ", StringComparison.Ordinal) ? bearerToken.Substring(7) : bearerToken;
            return true;
        }

        /// <summary>
        /// Based on: https://github.com/Azure-Samples/active-directory-dotnet-webapi-manual-jwt-validation/blob/4b80657c5506c8cb30af67b9f61bb6aa68dfca58/TodoListService-ManualJwt/Global.asax.cs#L54
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var authHeader = request.Headers.Authorization;
            if (authHeader == null && !request.Headers.Contains("X-Authorization-Token"))
            {
                // missing authorization header
                return await base.SendAsync(request, cancellationToken);
            }

            string userJwtToken;
            if (!TryRetrieveToken(request, out userJwtToken))
            {
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }

            SecurityToken parsedToken;
            try
            {
                var appliesToAddress = _configManager.GetConfigurationKey("JwtAllowedAudience");
                var tokenIssuerName = _configManager.GetConfigurationKey("JwtValidIssuer");
                SecurityKey securityKey = new SymmetricSecurityKey(KeyHolder.SymmetricKey);
                TokenValidationParameters validationParameters = new TokenValidationParameters
                {
                    ValidAudience = appliesToAddress,
                    ValidIssuer = tokenIssuerName,
                    IssuerSigningKey = securityKey
                };

                JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
                // Validate token -- this is where exceptions will originate
                ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(userJwtToken, validationParameters, out parsedToken);

                // Set the ClaimsPrincipal on the current thread.
                Thread.CurrentPrincipal = claimsPrincipal;
                // Set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment.
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.User = claimsPrincipal;
                }
            }
            catch (SecurityTokenInvalidSignatureException ex)
            {
                // the token was probably manipulated before it got to us
                // this could imply an old auth token (expired), bad code or a malicious actor
                Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }
            catch (SecurityTokenValidationException ex)
            {
                // the error is more general, not sure. Someone could be sending us bad data
                Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }
            // don't catch raw exceptions -- let them bubble up naturally

            if(parsedToken == null)
            {
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }

            return await base.SendAsync(request, cancellationToken);
        }
    }

EDIT: 12-13-18 I am rolling out an update to this to enable IdentityModelEventSource.ShowPII = true; in the above try { } catch { } for validation. It should give me the values data to prove/disprove I am experiencing the same problem. Once I have that info then I'll circle back.

UPDATE: 12-14-18 @brentschmaltz I am looking at the debug data and the Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException for us is happening at this line in my above code snippet:

ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(userJwtToken, validationParameters, out parsedToken);

Our token is HS256 (looks like everyone else's is RS256 in this thread). I'm seeing the exceptions specifically from our Android and iOS application users. Interestingly the tokens validate fine with JWT.io and the Android/iOS users are able to login no problem. Maybe they need some encoding on the requests (I wouldn't think so?) or I've got the same zero/null byte issue as above. One other thought; maybe us using HS256 instead of RS256 (like the others) explains why this earlier fix didn't do the trick: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/846/files/d301387cba4151fee63c3970c01620647d0b199b#diff-9640d4f49545372da23377ed19027d0f

mathew-weaver commented 5 years ago

I am running into the same error [IDX10511: Signature validation failed.] when trying to validate a JWT AccessToken obtained from https://login.microsoftonline.com/common. If I obtain the AccessToken via ADAL.NET (Microsoft.IdenityModel.Clients.ActiveDirectory v3.9.30421) then it validates successfully. However, if I obtain the AccessToken via MSAL.NET (Microsoft.Identity.Client v2.6.2) then it fails with the IDX10511 exception.

In both cases, I am using System.IdentityModel.Tokens.Jwt v5.3.0 to validate. Also, in both cases, I am validating using signing keys obtained from https://login.microsoftonline.com/common/.well-known/openid-configuration (aka https://login.microsoftonline.com/common/discovery/keys) using Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.

Any ideas why the ADAL.NET-obtained token validates successfully but the MSAL.NET-obtained token fails signature validation?

[update] This post helped me resolve my issue. As explained in that post by @brentschmaltz, the AccessToken obtained via MSAL.NET has an audience of https://graph.windows.net. Graph access tokens contain a nonce and require special processing during validation. As @brentschmaltz noted, we should not really be trying to validate Graph access tokens anyway. In my use case, I do not need to access Graph; so instead of using the AccessToken (which will not validate), I switch to using the IdToken. The IdToken has an audience of my registered application's clientId (not https://graph.windows.net) and it does validate as expected.

brentschmaltz commented 5 years ago

@mathew-weaver id_tokens are not meant to be used for access. Probably what you are doing is ok. Just to check, are you send the id_token to another service?

mathew-weaver commented 5 years ago

@brentschmaltz - no, I am not sending the id_token to another service; I am using it in my app (which is the audience of the id_token) to identify the user (e.g. preferred_username).

brentschmaltz commented 5 years ago

@mathew-weaver what are you using the id_token for?

mathew-weaver commented 5 years ago

@brentschmaltz - I am using the Implicit Flow. The login page of our application has the "Sign in with Microsoft" button. If the user clicks that button, it uses MSAL.js to login the user with AAD, then AAD redirects back to our application and passes the id_token. We then use the id_token to identify the user and verify that they are a valid user within our application.

brentschmaltz commented 5 years ago

@mathew-weaver ok, so you are obtaining the token using 'fragment'?

mathew-weaver commented 5 years ago

@brentschmaltz Correct - we are obtaining the id_token from the hash fragment of the redirect URI.

kl4072 commented 5 years ago

In .net core 2.2 when i containerize the app i get a Bearer error="invalid_token", error_description="The signature is invalid"

It is working fine when i host it on windows using IIS/IIS express.

My code -- The token generator is IBM API Connect it uses RSA 256 Algorithm to generate the key

 var rsa = new RSACryptoServiceProvider();
 string exponentvalue = "AQAB";
 var e = Base64UrlEncoder.DecodeBytes(exponentvalue);
 var N = "public key  put your value here"
 var modulus = Base64UrlEncoder.DecodeBytes(N);
 rsa.ImportParameters(
     new RSAParameters()
     {
             Modulus = modulus,
             Exponent = e
    });
  var signingKey = new RsaSecurityKey(rsa);
var tokenValidationParameters = new TokenValidationParameters
            {
                // The signing key must match!
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,

                // Validate the JWT Issuer (iss) claim
                ValidateIssuer = false,
                ValidIssuer = issuer,

                // Validate the JWT Audience (aud) claim
                ValidateAudience = false,
                ValidAudience = audience,

                // Validate the token expiry
                //ValidateLifetime = true,

                // If you want to allow a certain amount of clock drift, set that here:
                //ClockSkew = TimeSpan.FromMinutes(1)
            };

Any idea why it wouldn't be working on a container hosted either locally on docker or AKS?

brentschmaltz commented 5 years ago

@loga0060 can you check if the modulus first byte is zero?

kl4072 commented 5 years ago

@brentschmalz I did try the code that was suggested above first byte was 190. I read a post on docker forum saying ms provided nuget packages for jwt uses the fingerprint of public key where as docker expects a different format (lib trust fingerprint ) could this be the case?

kl4072 commented 5 years ago

I figured out my issue full solution is here https://stackoverflow.com/questions/54585148/net-core-2-2-validation-of-jwt-failing-with-401-on-a-container

brentschmaltz commented 5 years ago

nice.

jflord78 commented 4 years ago

For anyone else landing here trying to figure out the "IDX10511: Signature validation failed." error, specifically with a "System.Security.Cryptography.CryptographicException: An internal error occurred" exception. This was the solution for us:

Our 2008R2 hosts needed to be configured to auto-start the "CNG Key Isolation" service.

Ref: https://blogs.msdn.microsoft.com/dsnotes/2012/08/17/cngkey-import-throws-an-exception-saying-an-internal-error-occurred-when-importing-a-key-blob-under-a-non-admin-user-from-a-wcf-web-service/

brentschmaltz commented 4 years ago

@jflord78 thanks for this post, very helpful.

EkwofiePrax commented 4 years ago

@brentschmaltz I'm being faced with similar issue that's been reported in here. I have tried all the suggestions but I'm getting no where.

Am I missing something?

I have added a snapshot of my code

IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey

I'm using MSAL interceptor in Angular 8 code to send access token to the server "C# .NET core 3.1".

//***** public static JwtSecurityToken Validate(string token) {

        string stsDiscoveryEndpoint = "https://login.microsoftonline.com/eddf9e42-9a7f-4639-8042-***********/.well-known/openid-configuration?appid=7391aebb-1a35-4ad7-8166-xxxxxxxxxx"; // https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";

        ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
        OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;

        TokenValidationParameters validationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,
            ValidIssuer = config.Issuer,
            IssuerSigningKeys = config.SigningKeys,
            ValidateIssuerSigningKey = false,
            ValidateIssuer = true,
            ValidateActor = false,
            ValidateLifetime = true
        };

        IdentityModelEventSource.ShowPII = true;
        JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();

        SecurityToken jwt;
        var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
        return jwt as JwtSecurityToken;
    }

/* IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey, KeyId: 'HlC0R12skxNZ1WQwmjOF_6t_tDE', InternalId: '91ea3222-c9ea-4aa4-a515-83f2b195f989'. , KeyId: HlC0R12skxNZ1WQwmjOF_6t_tDE '. kid: 'HlC0R12skxNZ1WQwmjOF_6t_tDE'. Exceptions caught: ''. token: '{"alg":"RS256","typ":"JWT","nonce":"19yszC4xPiqhnbI587HT_08QFjftE7J3qE8F9xw_sqY","x5t":"HlC0R12skxNZ1WQwmjOF_6t_tDE","kid":"HlC0R12skxNZ1WQwmjOF_6t_tDE"}.{"aud":"00000003-0000-0000-c000-000000000000","iss":"https://sts.windows.net/eddf9e42-9a7f-4639-8042-110281cf41a2/","iat":1581413726,"nbf":1581413726,"exp":1581417626,"acct":0,"acr":"1","aio":"42NgYLgWut8sY6dad5urN6/nxuP8P7Z6ZXr1Wwl355xibZu8nxMA","amr":["pwd"],"app_displayname":"SoraxWeb","appid":"7391aebb-1a35-4ad7-8166-xxxxxxxx","appidacr":"0","family_name":"kwofie","given_name":"Ernest","ipaddr":"212.161.81.38","name":"Exxxx kxxxx","oid":"9f9efce3-37e5-4018-8035-7ffc6323e827","onprem_sid":"S-1-5-21-3789744388-407605227-1228871550-7770","platf":"3","puid":"100320003319DDA9","scp":"Mail.Send openid profile User.Read User.ReadWrite email","sub":"LhkC5zL_zJm-MZBjJDYCz5Asjm7Nwg9tHUehKVSH40A","tid":"eddf9e42-9a7f-4639-8042-*****","unique_name":"Exxxx.kxxxx@xxxx.com","upn":"Exxxx.kxxxx@xxxxx.com","uti":"3RsEnJpEokmZb-Y3MwJzAA","ver":"1.0","xms_st":{"sub":"zSJz0JM6iB6SRwxi1ZZm_1F__aYqHhEDtjb5qkege_o"},"xms_tcdt":1443089411}'. */

Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddCors(options => options.AddPolicy(MyAllowSpecificOrigins, builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); })); services.AddControllers(); }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();
        app.UseCors(MyAllowSpecificOrigins);
        app.UseHttpsRedirection();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
bhaktatejas922 commented 3 years ago

@EkwofiePrax did you ever find a solution?

milenasrocha commented 3 years ago

Any updates?

Shashank--Shekhar commented 2 years ago

@EkwofiePrax were you able to find a solution for your problem?

brentschmaltz commented 2 years ago

@milenasrocha @EkwofiePrax @milenasrocha @bhaktatejas922 reviewing token I notice two things.

  1. there is a 'nonce' claim in the header
  2. the token has "00000003-0000-0000-c000-000000000000" as the audience.

    '{"alg":"RS256","typ":"JWT","nonce":"19yszC4xPiqhnbI587HT_08QFjftE7J3qE8F9xw_sqY","x5t":"HlC0R12skxNZ1WQwmjOF_6t_tDE","kid":"HlC0R12skxNZ1WQwmjOF_6t_tDE"}.{"aud":"00000003-0000-0000-c000-000000000000"

this combination indicates this token has a special format internal to Microsoft Graph and a transformation needs to take place before the signature will validate.

specifically the 'nonce' value needs to be replaced with the SHA2 value before the signature will validate. the transformation is a binary in-place replacement, order is important.