I'm building an HTTP API database adapter that has an authentication component. Users can authenticate using password, federated login such as OAUTH, and JWT.
My initial design is something like the following:
pub enum AuthType {
Password,
Federated,
JWT,
}
pub struct Adapter {
pub client: reqwest::Client,
pub username: String,
pub password: Option<String>,
pub token: Option<String>,
pub auth_type: AuthType
}
impl Adapter
pub fn authenticate(&mut self) {
match self.auth_type {
AuthType::Federated => authenticate_with_sso(self)
AuthType::JWT => authenticate_with_token(self)
AuthType::Password => authenticate_with_pw(self)
}
}
Each of these functions would contain the specific logic necessary to return an authenticated connection.
Where I'm hung up is whether this authentication scheme should something more like a trait with struct implementations. It's unlikely that there will be any user-defined implementations of these authentication schemes which led me to the above, but I'm not sure which is a cleaner approach.
The other could be something like
pub trait Authenticator {
fn authenticate(&mut Adapter) -> &mut Adapter;
}
struct FederatedAuth {
pub sso_url: string,
pub saml_token: String
}
impl Authenticator for FederatedAuth {
pub fn authenticate(adap: &mut Adapter) -> &mut Adapter {
// do things to get the OAuth token and update the adapter
}
}
pub struct Adapter<A: Authenticator> {
// credentials etc,
pub authenticator: A: Authenticator,
// or Box<dyn Authenticator>
}
This seems more cumbersome, but perhaps more flexible? I'm not unsure whether it'd be preferable to use static or dynamic dispatch in the adapter above as carrying generics around the code can become a burden. As an aside, users will know their authentication type when instantiating an adapter.
Some areas where I'm hung up are:
- Is
Authenticator
justified in being its own thing, and if so, should it also be part of theAdapter
, or a standalone component? - Is the
Adapter
responsible for its own authentication? i.e. shouldauthenticate
be a method, or a standalone function that returns a new, authenticatedAdapter
?