Auth0 Home Blog Docs

API Calls Suddenly Throws Algorithm RS256 Error

rs256
error
c
webapi
algorithm

#1

Hi,

I am quite unsure where to resolve this. Basically, when I call my web api authorized via Auth0, it is showing me this error:

For algorithm RS256 please create custom factory by implementing IAlgorithmFactory

“Message”: “An error has occurred.”,
“ExceptionMessage”: “For algorithm RS256 please create custom factory by
implementing IAlgorithmFactory”,
“ExceptionType”: “System.NotSupportedException”,
“StackTrace”: " at JWT.Algorithms.HMACSHAAlgorithmFactory.Create(JwtHashAlgorithm
algorithm)\r\n at
JWT.JwtDecoder.Validate(String
payload, String payloadJson, String]
parts, Byte] key)\r\n at
JWT.JwtDecoder.Decode(String token,
Byte] key, Boolean verify)\r\n at
Auth0API.App_Start.JsonWebToken.ValidateToken(String
token, String secretKey, String
audience, Boolean checkExpiration,
String issuer, Boolean
isSecretBase64Encoded) in
C:\Auth0TestProject\Auth0API\App_Start\JsonWebToken.cs:line
33\r\n at
Auth0API.App_Start.JsonWebTokenValidationHandler.SendAsync(HttpRequestMessage
request, CancellationToken
cancellationToken) in
C:\Auth0TestProject\Auth0API\App_Start\JsonWebTokenValidationHandler.cs:line
59"

Here’s my JsonWebTokenValidationHandler.cs class

namespace Auth0API.App_Start
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web;
    
    public class JsonWebTokenValidationHandler : DelegatingHandler
    {
        public string SymmetricKey { get; set; }

        public string Audience { get; set; }
        
        public string Issuer { get; set; }

        public bool IsSecretBase64Encoded { get; set; }

        public JsonWebTokenValidationHandler()
        {
            IsSecretBase64Encoded = true;
        }

        private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
        {
            token = null;
            IEnumerable<string> authzHeaders;

            if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
            {
                // Fail if no Authorization header or more than one Authorization headers  
                // are found in the HTTP request  
                return false;
            }

            // Remove the bearer token scheme prefix and return the rest as ACS token  
            var bearerToken = authzHeaders.ElementAt(0);
            token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;

            return true;
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            string token;
            HttpResponseMessage errorResponse = null;

            if (TryRetrieveToken(request, out token))
            {
                try
                {
                    var secret = this.SymmetricKey;
                    if (IsSecretBase64Encoded)
                        secret = secret.Replace('-', '+').Replace('_', '/');

                    Thread.CurrentPrincipal = JsonWebToken.ValidateToken(
                        token,
                        secret,
                        audience: this.Audience,
                        checkExpiration: true,
                        issuer: this.Issuer,
                        isSecretBase64Encoded: IsSecretBase64Encoded);
                    
                    if (HttpContext.Current != null)
                    {
                        HttpContext.Current.User = Thread.CurrentPrincipal;
                    }
                }
                catch (JWT.SignatureVerificationException ex)
                {
                    errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex);
                }
                catch (JsonWebToken.TokenValidationException ex)
                {
                    errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex);
                }
                catch (Exception ex)
                {
                    errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
                }
            }

            return errorResponse != null ?
                Task.FromResult(errorResponse) :
                base.SendAsync(request, cancellationToken);
        }
    }
}

Here’s my JsonWebToken.cs class

namespace Auth0API.App_Start
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using Newtonsoft.Json.Linq;
    using System.Text;

    public static class JsonWebToken
    {
        private const string NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
        private const string RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
        private const string ActorClaimType = "http://schemas.xmlsoap.org/ws/2009/09/identity/claims/actor";
        private const string DefaultIssuer = "LOCAL AUTHORITY";
        private const string StringClaimValueType = "http://www.w3.org/2001/XMLSchema#string";

        // sort claim types by relevance
        private static IEnumerable<string> claimTypesForUserName = new] { "name", "email", "user_id", "sub" };
        private static ISet<string> claimsToExclude = new HashSet<string>(new] { "iss", "sub", "aud", "exp", "iat", "identities" });

        private static DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        public static ClaimsPrincipal ValidateToken(string token, string secretKey, string audience = null, bool checkExpiration = false, string issuer = null, bool isSecretBase64Encoded = true)
        {
            byte] secret;
            if (isSecretBase64Encoded)
                secret = Convert.FromBase64String(secretKey);
            else
                secret = Encoding.UTF8.GetBytes(secretKey);

            var payloadJson = JWT.JsonWebToken.Decode(token, secret, verify: true);
            var payloadData = JObject.Parse(payloadJson).ToObject<Dictionary<string, object>>();

            // audience check
            object aud;
            if (!string.IsNullOrEmpty(audience) && payloadData.TryGetValue("aud", out aud))
            {
                if (!aud.ToString().Equals(audience, StringComparison.Ordinal))
                {
                    throw new TokenValidationException(string.Format("Audience mismatch. Expected: '{0}' and got: '{1}'", audience, aud));
                }
            }

            // expiration check
            object exp;
            if (checkExpiration && payloadData.TryGetValue("exp", out exp))
            {
                DateTime validTo = FromUnixTime(long.Parse(exp.ToString()));
                if (DateTime.Compare(validTo, DateTime.UtcNow) <= 0)
                {
                    throw new TokenValidationException(
                        string.Format("Token is expired. Expiration: '{0}'. Current: '{1}'", validTo, DateTime.UtcNow));
                }
            }

            // issuer check
            object iss;
            if (payloadData.TryGetValue("iss", out iss))
            {
                if (!string.IsNullOrEmpty(issuer))
                {
                    if (!iss.ToString().Equals(issuer, StringComparison.Ordinal))
                    {
                        throw new TokenValidationException(string.Format("Token issuer mismatch. Expected: '{0}' and got: '{1}'", issuer, iss));
                    }
                }
                else
                {
                    // if issuer is not specified, set issuer with jwt[iss]
                    issuer = iss.ToString();
                }
            }

            return new ClaimsPrincipal(ClaimsIdentityFromJwt(payloadData, issuer));
        }

        private static ICollection<Claim> ClaimsFromJwt(IDictionary<string, object> jwtData, string issuer)
        {
            issuer = issuer ?? DefaultIssuer;

            var list = jwtData.Where(p => !claimsToExclude.Contains(p.Key)) // don't include specific claims
                              .Select(p => new { Key = p.Key, Values = p.Value as JArray ?? new JArray { p.Value } }) // p.Value is either claim value of ArrayList of values
                              .SelectMany(p => p.Values.Cast<object>()
                                                .Select(v => new Claim(p.Key, v.ToString(), StringClaimValueType, issuer, issuer)))
                              .ToList();

            // set claim for user name
            // use original jwtData because claimsToExclude filter has sub and otherwise it wouldn't be used
            var userNameClaimType = claimTypesForUserName.FirstOrDefault(ct => jwtData.ContainsKey(ct));
            if (userNameClaimType != null)
            {
                list.Add(new Claim(NameClaimType, jwtData[userNameClaimType].ToString(), StringClaimValueType, issuer, issuer));
            }

            // set claims for roles array
            list.Where(c => c.Type == "roles").ToList().ForEach(r =>
            {
                list.Add(new Claim(RoleClaimType, r.Value, StringClaimValueType, issuer, issuer));
            });

            list.RemoveAll(c => c.Type == "roles");

            return list;
        }

        private static ClaimsIdentity ClaimsIdentityFromJwt(IDictionary<string, object> jwtData, string issuer)
        {
            var subject = new ClaimsIdentity("Federation", NameClaimType, RoleClaimType);
            var claims = ClaimsFromJwt(jwtData, issuer);

            foreach (Claim claim in claims)
            {
                var type = claim.Type;
                if (type == ActorClaimType)
                {
                    if (subject.Actor != null)
                    {
                        throw new InvalidOperationException(string.Format(
                            "Jwt10401: Only a single 'Actor' is supported. Found second claim of type: '{0}', value: '{1}'", new object] { "actor", claim.Value }));
                    }

                    subject.AddClaim(new Claim(type, claim.Value, claim.ValueType, issuer, issuer, subject));

                    continue;
                }

                subject.AddClaim(new Claim(type, claim.Value, claim.ValueType, issuer, issuer, subject));
            }

            return subject;
        }

        private static DateTime FromUnixTime(long unixTime)
        {
            return unixEpoch.AddSeconds(unixTime);
        }

        public class TokenValidationException : Exception
        {
            public TokenValidationException(string message)
                : base(message)
            {
            }
        }
    }
}

I also see on this line on my JsonWebToken.cs file:

var payloadJson = JWT.JsonWebToken.Decode(token, secret, verify: true);

JWT.JsonWebToken is underlined and it says:

[Deprecated]: JsonWebToken is
obsolete: Static API is obsolete as of
version 2.0 and will be removed in
future versions.

I am unsure whether I need to simply update my nuget package to something.
Appreciate any input. Thanks.


#2

Facing exactly the same issue. Any solution you got @athinksoftware


#3

In relation to the error itself and based on the stacktrace you seem to be using jwt-dotnet/jwt library. You can read more about the error in this GH issue but it seems that RS256 support requires some different usage of the library.

I never used this library so I don’t have any knowledge on what exactly it would take to get this working, however, any reason for not using Microsoft provided libraries? If you want to consider it, you can check this blog post that talks about validating RS256 token with the Microsoft libraries.

Finally, for the reason why you started to get the error in the first place, the root cause it’s likely that the token in question started to use RS256. If you didn’t do any configuration change that could have caused this, the only thing I’m aware that could cause is that for public clients using OIDC compliant authentication I believe we force the use of RS256 instead of HS256 on the ID token.


#4