I'm trying to implement AuthnBackend
for the axum-login
crate, and I can't figure out why the compiler is giving errors with this code. I've tried to stay really close to the sqlite example provided in the docs, except that I'm using the rusqlite
crate in place of sqlx
.
I can tell from the compiler errors that it's a lifetime error, but I'm not sure how to resolve it. The linked example doesn't explicitly state any lifetimes, and I'm not sure if I should need to here or how.
app/src/users.rs:
use std::{fmt, sync::Arc};
use axum_login::{AuthUser, AuthnBackend, UserId};
use password_auth::verify_password;
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use serde_cbor::from_slice;
use tokio::sync::Mutex;
#[derive(Clone, Deserialize, Serialize)]
pub struct User {
pub id: u64,
pub username: String,
pub pw_hash: String,
pub role: UserRole,
}
impl fmt::Display for User {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.username)
}
}
impl fmt::Debug for User {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{} (id {}, role {:?})",
self.username, self.id, self.role
)
}
}
impl AuthUser for User {
type Id = u64;
fn id(&self) -> Self::Id {
return self.id;
}
fn session_auth_hash(&self) -> &[u8] {
return self.pw_hash.as_bytes();
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum UserRole {
Admin = 2,
User = 1,
Guest = 0,
}
/// Extracted from authentication forms
#[derive(Debug, Clone, Deserialize)]
pub struct Credentials {
pub username: String,
pub password: String,
pub next: Option<String>,
}
#[derive(Clone, Debug)]
pub struct Backend {
db_con: Arc<Mutex<Connection>>,
}
impl Backend {
pub fn new(db_con: Arc<Mutex<Connection>>) -> Self {
return Backend { db_con };
}
}
#[derive(Debug, thiserror::Error)]
pub enum UserBackendError {
#[error(transparent)]
Rusqlite(#[from] rusqlite::Error),
#[error(transparent)]
PasswordVerify(#[from] password_auth::VerifyError),
}
impl AuthnBackend for Backend {
type User = User;
type Credentials = Credentials;
type Error = UserBackendError;
async fn authenticate(
&self,
creds: Self::Credentials,
) -> Result<Option<Self::User>, Self::Error> {
// Connect to db
let conn = self.db_con.lock().await;
// Load user data from db
let user_data = match conn.query_row(
"SELECT data FROM users WHERE username = ?1",
[&creds.username],
|row| row.get::<usize, Vec<u8>>(0),
) {
Ok(data) => data,
Err(err) => match err {
rusqlite::Error::QueryReturnedNoRows => {
return Ok(None);
}
_ => return Err(self::UserBackendError::Rusqlite(err)),
},
};
// Deserialize user data
let user: User = from_slice(&user_data).unwrap();
// Verify password
match verify_password(creds.password, &user.pw_hash) {
Ok(_) => return Ok(Some(user)),
Err(err) => return Err(self::UserBackendError::PasswordVerify(err)),
};
}
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
// Connect to db
let conn = self.db_con.lock().await;
// Load user data from db
let user_data =
match conn.query_row("SELECT data FROM users WHERE id = ?1", [user_id], |row| {
row.get::<usize, Vec<u8>>(0)
}) {
Ok(data) => data,
Err(err) => match err {
rusqlite::Error::QueryReturnedNoRows => {
return Ok(None);
}
_ => return Err(self::UserBackendError::Rusqlite(err)),
},
};
// Deserialize user data
let user: User = from_slice(&user_data).unwrap();
return Ok(Some(user));
}
}
Compiler errors:
error[E0195]: lifetime parameters or bounds on method `authenticate` do not match the trait declaration
--> app/src/users.rs:86:26
|
86 | async fn authenticate(
| ^ lifetimes do not match method in trait
error[E0195]: lifetime parameters or bounds on method `get_user` do not match the trait declaration
--> app/src/users.rs:118:22
|
118 | async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
| ^ lifetimes do not match method in trait
AuthnBackend
uses async-trait. For a trait defined with #[async_trait]
, you need to use #[async_trait]
on the implementation as well (or at least the desugared form). This is the case even though Rust now supports async
in traits since the technique used by #[async_trait]
(i.e. boxed futures) is different than native async
trait methods (opaque types).
So you should add it to your Backend
implementation:
[dependencies]
async-trait = "0.1.88"
use async_trait::async_trait;
#[async_trait]
impl AuthnBackend for Backend {
For completeness, the docs show the desugared version looks like this:
fn authenticate<'life0, 'async_trait>(
&'life0 self,
creds: Self::Credentials,
) -> Pin<Box<dyn Future<Output = Result<Option<Self::User>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
The example you link to doesn't have #[async_trait]
since it appears the main
branch has converted to native(-ish) async
trait methods. The same example from the v0.17.0 branch - the latest published version - still uses #[async_trait]
.