Open brendt opened 1 year ago
Hey @brendt, since you don't want to tie the authentication to the DB, I was thinking about and I had an idea.
This is just a high-overview idea, but if you feel it's something we can explore I can think more about it and start drafting a PR.
What if we had something like this?
final class AuthConfig
{
public function __construct(
public readonly CredentialsResolver $credentials,
public readonly SessionDriver $driver,
) {
}
}
interface CredentialsResolver
{
public function identifier(): string; // This would return for example an username or email
public function secret(): string; // This would return for example a password or API key
}
Then we could have some implementations for the CredentialsResolver
like DatabaseCredentials
that would receive an Authenticable
parameter that could be applied to any model class.
final readonly DatabaseCredentials implements CredentialsResolver
{
public function __construct(
Authenticable $authenticable,
) {
}
}
We could have an OAuthCredentials
that would wrap the common logic of an OAuth flow.
Other examples would be:
SocialCredentials
that would provide out-of-the-box some social login logic.LdapCredentials
that would provide an LDAP login logic.I think I like where this is going. What's still missing is the public facing API. How would framework users interact with this auth layer? Did you have some thoughts about that?
Yes, but I think I sent some wrong names in my first comment, the idea would be to have something like:
interface Authenticable
{
public function identifier(): string; // This would return for example an username or email
public function secret(): string; // This would return for example a password or API key
}
Then in the configuration, it would have something like this:
final class AuthConfig
{
public function __construct(
public readonly Authenticator $authenticator,
public readonly SessionDriver $driver,
) {
}
}
Then we would have a common contract on how to handle authentication and that would be the public one. I thought about a really minimalist public API.
interface Authenticator
{
public function authenticate(Authenticable $authenticable): void; // It would throw an exception if the authentication fails
public function invalidate(): void;
public function user(): Authenticable;
}
Then we would have the different authenticator like DatabaseAuthenticator
, OAuthAuthenticator
, SocialAuthenticator
, LdapAuthenticator
.
For a Database login for example we would have something like:
final class User implements Authenticable
{
// Model definition here.
public function identifier(): string
{
return $this->email;
}
public function secret(): string
{
return $this->password;
}
}
final readonly class AuthController
{
public function __construct(
private Authenticator $authenticator,
) {
}
public function login(Request $request): Response
{
$user = ... // get the user with the data from request
$this->authenticator->authenticate($user);
// return response
}
public function logout(): Response
{
$this->authenticator->invalidate();
// return response
}
public function loggedUser(): Response
{
$user = $this->authenticator->user();
// return response with user data
}
}
final readonly class AuthController
{
public function __construct(
private Authenticator $authenticator,
) {
}
public function login(Request $request): Response
{
$user = ... // get the user with the data from request
$this->authenticator->authenticate($user);
// return response
}
public function logout(): Response
{
$this->authenticator->invalidate();
// return response
}
public function loggedUser(): Response
{
$user = $this->authenticator->user();
// return response with user data
}
}
I like this! A couple of thoughts:
authenticate
and invalidate
should be called login
and logout
Authenticable
as a singleton in the container? Authenticable
is a bit of a weird name, but I don't know a better one right nowI agree on renaming the methods to login an logout and I feel that’s a great idea to register the Authenticable as a singleton!
I didn’t think on a better name for it as well, I feel the name is weird, but IDK how to call it 😅
Can I start drafting an initial implementation for this idea, @brendt?
@WendellAdriel Yes go for it! 👍
Yay!!! I’ll start working on this to prepare a draft PR then!
We need an abstraction to handle user logins. I don't want it to be tied directly to the database though, so we probably need to carefully think about how we design it.