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 };
}
}
}