API call hangs after authentication

I have implemented a custom jwt provider that validates bearer tokens from Azure AD v2.0. The pre authenticate method gets called and validates the token just fine. But the rest of the call never completes, it never makes it to the actual implementation for the request. Now when I remove to the [Authenticate] tag, the call works works just fine. The funny thing is, I have nearly this exact same setup in another application and it works just fine.

Here is my custom auth provider.

namespace APIDemo.AuthPlugin
{
    public class AzureJWTAuthentication : AuthProvider, IAuthWithRequest, IAuthPlugin
    {

        private string aadInstance;
        private string tenant;
        private string audience;

        public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
        {
            //this should not get called directly.
            throw new NotImplementedException("JWT Authenticate() should not be called directly");
        }

        public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
        {
            return session.FromToken && session.IsAuthenticated;
        }

        public void PreAuthenticate(IRequest req, IResponse res)
        {
            try
            {
                var token = GetJwtToken(req);

                var jwt = (JwtSecurityToken)ValidateToken(token);

                var sessionid = Guid.NewGuid().ToString();
                var session = SessionFeature.CreateNewSession(req, sessionid);

                session.UserAuthId = jwt.Payload["oid"].ToString();

                session.IsAuthenticated = true;
                session.FromToken = true;

                req.Items[Keywords.Session] = session;

            }
            catch (Exception ex)
            {
                throw HttpError.Forbidden("Invalid Token");
            }
        }

        public void Register(IAppHost appHost, AuthFeature feature)
        {
            aadInstance = appHost.AppSettings.Get<string>("aadInstance");
            tenant = appHost.AppSettings.Get<string>("tenant");
            audience = appHost.AppSettings.Get<string>("audience");
        }

        private SecurityToken ValidateToken(string token)
        {
            var authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
            var configEndpoint = string.Concat(authority, "/", ".well-known/openid-configuration");

            ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(configEndpoint, new OpenIdConnectConfigurationRetriever());

            var config = configManager.GetConfigurationAsync().Result;

            SecurityToken jwt;
            var tokenHandler = new JwtSecurityTokenHandler();
            tokenHandler.ValidateToken(token, GetValidationParameters(config.SigningKeys, config.Issuer, audience), out jwt);

            return jwt;
        }

        private TokenValidationParameters GetValidationParameters(IEnumerable<SecurityKey> securityKeys, string issuer, string audience)
        {
            Contract.Requires(null != securityKeys);
            Contract.Requires(!string.IsNullOrWhiteSpace(issuer));

            return new TokenValidationParameters()
            {
                ValidAudience = audience,
                ValidIssuer = issuer,
                IssuerSigningKeys = securityKeys,
                ValidateLifetime = true,
                ValidateIssuer = true,
                ValidateIssuerSigningKey = true
            };
        }
        
        private string GetJwtToken(IRequest req)
        {
            var auth = req.Headers[HttpHeaders.Authorization];
            if (string.IsNullOrEmpty(auth))
                return null;

            var pos = auth.IndexOf(' ');
            if (pos < 0)
                return null;

            return auth.Substring(0, pos).EqualsIgnoreCase("Bearer") ? auth.Substring(pos + 1) : null;
        }



    }
}

Here is my app host:

namespace APIDemo
{
    public class AppHost : AppHostBase
    {
        public AppHost() : base("Hello Web Services", typeof(HelloService).Assembly)
        {
            typeof(Authenticate).AddAttributes(new ExcludeMetadataAttribute());
        }

        public override void Configure(Container container)
        {
            AuthFeature auth = new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { new AzureJWTAuthentication() });
            auth.IncludeAssignRoleServices = false;
            auth.IncludeRegistrationService = false;

            Plugins.Add(auth);
        }
    }
}

Here is the service implementation, as you will see it is a very simple hello world sort of thing.

namespace ApiDemo.ServiceModel
{
    [Route("/hello")]
    [Route("/hello/{Name}")]
    public class Hello
    {
        public string Name { get; set; }
    }

    public class HelloResponse
    {
        public string Result { get; set; }
    }
}

namespace APIDemo.ServiceInterface
{
    [Authenticate]
    public class HelloService : IService
    {
        public object Any(Hello request)
        {
            return new HelloResponse { Result = "Hello, " + request.Name };
        }

    }
}

That’s because the [Authenticate] Attribute is what calls your Auth Providers PreAuthenticate() method. Which just basically indicates the issue is in your Custom Auth Provider.

We’re not going to be able to repro this with just the code provided but one thing that sticks out is blocking on Async APIs which can cause deadlocks:

var config = configManager.GetConfigurationAsync().Result;

Which you basically should never do. I’m surprised validating JWT’s needs to do any I/O since one of the primary benefits of them is that they’re encapsulated and stateless. If you don’t have a sync API available you can call then consider caching the configuration so no async I/O calls are needed.

That code in question works. The request makes it all the way through the PreAuthenticate() method. It creates the session and everything, it just fails to get to the method I am calling. Which is highly frustrating, as I am using this same setup in another application and it is working as expected.

Something isn’t right and blocking on async code stands out as the primary culprit as it’s something you should never do unless you know the code is synchronous. The first thing I would be doing is testing without any async I/O so you can definitely rule it out. Hard code it if you have to.

You can try copying the AuthenticateAttribute and using your local impl instead so you can debug to try identify the issue.

I copied the AuthenticateAttribute and then loaded all the symbols and methodically stepped through the code until the error was found.

At some point in the call, after all the authentication stuff has been completed, I get the following error.

Value cannot be null. Parameter: provider.

at ServiceStack.Auth.AuthenticateService.GetAuthProvider(String provider)\r\n   at ServiceStack.AuthUserSession.IsAuthorized(String provider)\r\n   at APIDemo.ServiceInterface.LocalAuthenticateAttribute.<>c__DisplayClass12_0.<ExecuteAsync>b__0(IAuthProvider x) in C:\\Users\\cgipson\\Desktop\\CRMDemo\\APIDemo.ServiceInterface\\LocalAuthenticateAttribute.cs:line 73\r\n   at APIDemo.ServiceInterface.LocalAuthenticateAttribute.<ExecuteAsync>d__12.MoveNext() in C:\\Users\\cgipson\\Desktop\\CRMDemo\\APIDemo.ServiceInterface\\LocalAuthenticateAttribute.cs:line 73"

So that leads me to believe, that I need to provide a provider name somewhere. Any ideas???

You help so far has been invaluable.

I defined the provider name in the AppHost when I configure the plugin. Now everything is working as expected.

Once again, thanks for all the help. I am a big advocate for using service stack.

1 Like