I’m having difficulty getting both my custom AuthProvider and the BasicAuthProvider
working together at the same time.
I’m trying to authenticate against the properties of another model not UserAuth
. I’ve tried to duplicate the logic within BasicAuthProvider
but with an extra check on the format of the provided username as this will be a guid not a normal email address.
If I exclude the BasicAuthProvider
from the AuthFeature
plugin my unit tests for the custom provider pass but understandably the basic auth test fails. Including both providers the custom test fails with the exception using ErrorMessages.InvalidUsernameOrPassword
thrown by the CredentialsAuthProvider
when TryAuthenticate
fails. I’ve also got the CredentialsAuthProvider
configured and the tests for this pass in both scenarios.
Is it possible to create and include the auth provider like this? I’ve had a look at the source and there doesn’t appear to be anything in the base constructor needed. Is there something extra I need to do in OnAuthenticated
to identify the fact authentication has succeeded? I’ve called the base.OnAuthenticated
so I can see that is being hit in the custom provider.
The code for the ClientKeyAuthProvider
is;
public class ClientKeyAuthProvider : BasicAuthProvider
{
public new static string Name = "clientkey";
public new static string Realm = "/auth/clientkey";
public ClientKeyAuthProvider()
{
Provider = Name;
AuthRealm = Realm;
}
public static bool IsClientUserName(string userName)
{
return Guid.TryParse(userName, out var _);
}
public static bool IsClientSession(IAuthSession session)
{
return IsClientUserName(session.UserAuthName) && session.AuthProvider.Equals(Name) && session.UserAuthId.Equals("0");
}
public void ValidateClientKey(ClientKey clientKey)
{
if (clientKey == null)
throw HttpError.NotFound("Client key not found.");
if (string.IsNullOrEmpty(clientKey.Subdomain) || string.IsNullOrEmpty(clientKey.Id) || !IsClientUserName(clientKey.Id))
throw HttpError.Forbidden("Client identifiers missing.");
if (string.IsNullOrEmpty(clientKey.Key))
throw HttpError.Forbidden("Client key missing.");
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var clientService = HostContext.TryResolve<ClientService>();
var client = clientService.GetClient(authService.Request.GetSubdomain());
if (client == null)
throw HttpError.Forbidden("Client invalid.");
var clientKey = new ClientKey
{
Subdomain = client.Subdomain,
Id = string.IsNullOrEmpty(userName) ? client.ClientId : userName,
Key = password
};
ValidateClientKey(clientKey);
if (!clientService.IsValidClientKey(clientKey))
throw HttpError.Forbidden("Client key invalid.");
var session = authService.GetSession();
session.DisplayName = client.Name;
session.Email = client.BccEmail;
session.AuthProvider = Name;
session.UserAuthName = userName;
session.UserAuthId = "0";
return true;
}
public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
public override void PreAuthenticate(IRequest req, IResponse res)
{
var userAuth = req.GetBasicAuthUserAndPassword();
if (IsClientUserName(userAuth?.Key) && !string.IsNullOrEmpty(userAuth?.Value))
{
//Need to run SessionFeature filter since its not executed before this attribute (Priority -100)
SessionFeature.AddSessionIdToRequestFilter(req, res, null); //Required to get req.GetSessionId()
using (var authService = HostContext.ResolveService<AuthenticateService>(req))
{
var response = authService.Post(new Authenticate
{
provider = Name,
UserName = userAuth?.Key,
Password = userAuth?.Value
});
}
}
}
}
I’m registering the providers using;
plugins.Add(new AuthFeature(() => new ReaderSession(),
new IAuthProvider[] {
new ClientKeyAuthProvider(),
new BasicAuthProvider(settings),
new CredentialsAuthProvider(settings),
})
{
HtmlRedirect = "/",
IncludeAssignRoleServices = false,
IncludeRegistrationService = true,
ValidateUniqueEmails = true
});
The unit tests I have are are as follows;
[TestMethod]
public void Test_Auth_Basic()
{
UserService.CreateCompanyUser(MockData.TestUser, MockData.TestCompany.Id, MockData.TEST_USER_PSWD);
using(var client = new JsonServiceClient(BaseUrl))
{
client.SetCredentials(MockData.TestUser.Email, MockData.TEST_USER_PSWD);
var response = client.Post(new Authenticate());
Assert.IsNotNull(response.SessionId);
Assert.AreEqual(MockData.TestUser.DisplayName, response.DisplayName);
Assert.AreEqual(MockData.TestUser.Email, response.UserName);
Assert.AreEqual(MockData.TestUser.Id.ToString(), response.UserId);
}
}
[TestMethod]
public void Test_Auth_ClientKey()
{
using(var client = new JsonServiceClient(BaseUrl))
{
// set header so request location not used
client.AddHeader(Host.HostConfig.AUTH_DOMAIN_HEADER, MockData.TestCompany.Subdomain);
client.SetCredentials(MockData.TestCompany.ClientId, MockData.TestCompany.ClientKey);
var response = client.Post(new Authenticate());
Assert.IsNotNull(response.SessionId);
Assert.AreEqual("0", response.UserId);
}
}
I’ve also tried to explicitly specify the provider in the Authenticate
request dto but that didn’t seem to make a difference;
var response = client.Post(new Authenticate() { provider = ClientKeyAuthProvider.Name });
I’m having trouble debugging with SourceLink as I think the release dlls have been optimised so the source doesn’t match and the locals are optimised away. I’ve tried to copy over a compiled debug version to my tests and library projects but they don’t seem to be loaded by Visual Studio.
I’ve been looking at this for a while not and there must be something I’m missing about how the providers work together.