maxcountryman / axum-login Goto Github PK
View Code? Open in Web Editor NEW๐ชช User identification, authentication, and authorization for Axum.
License: MIT License
๐ชช User identification, authentication, and authorization for Axum.
License: MIT License
hi there,
i've been trying to axum_login
working with my postgres DB. i have the postgres
feature flag enabled and I am using sqlx
. I've changed my user table to users
and made sure it has an id
column to line up with what axum_login
is expecting, but I am now getting the following error:
select
*
from
users
where
id = $1
2023-02-10T04:44:24.236910Z ERROR axum_login::auth: Could not get user: error returned from database: operator does not exist: bigint = text
Caused by:
operator does not exist: bigint = text
I have been able to use my postgres DB just fine up to this point with axum/sqlx. my user model (that i implementAuthUser
over) is defined as follows:
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
pub struct User {
pub id: i64,
pub username: String,
pub pw_hash: String
}
the type of users.id
is declared bigserial
.
when i first got this error, i had the type of id
as an uuid (both in postgres and on my struct) and thought maybe that was the issue but that clearly wasn't the case.
what am i doing wrong?
As demonstrated at:
https://github.com/OvermindDL1/issue_axum_session_streaming.git
I have a large'ish app using graphql and a liveview-like thing and wanted to toss in axum-login and axum-sessions for obvious usefulness, however attempting to use it in either of these things, which are streaming handlers, panics with:
thread 'tokio-runtime-worker' panicked at 'Session handle still has owners.: RwLock { mr: 536870911, s: Semaphore { permits: 536870911 }, c: UnsafeCell { .. } }'
I reduced the issue to the SSCCE in the above git repo to strip all external things and just keep to axum and future_utils. Given this handler:
// Doesn't work, axum-login is blowing up
async fn route_streaming_login(mut auth: AuthContext) -> StreamBody<impl Stream<Item = std::io::Result<&'static str>>> {
let stream = stream::once(async move {
// performing some long operation
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
// And need to access auth 'later' in the stream
auth.login(&User { id: 1 }).await.unwrap();
Ok("logged in")
});
StreamBody::new(stream)
}
When it hits the auth.login(&User { id: 1 }).await.unwrap();
it panics, not because of the unwrap but internally (it never returns).
It looks like internally it's clearing, resetting, something the session from the handler, but that shouldn't be done because it's still being processed axum/hyper to finish processing and sending information mid-stream.
What should 'probably' be done is that AuthContext
is a fully owned type that resets itself in the store internally when it is dropped (which yes it would likely need to hold an Arc to the store internally in that case unless some re-architecting is done first), rather than when the handler returns (which is definitely not when the work is complete in axum/hyper). This will match what other things in the axum ecosystem do and will allow it to work within an axum streaming context like other things do as well.
Rustqlite seems like a good alternative for the sqlx
sqlite one since it supports sqlite
on a far better level. I believe it would be beneficial to support it separately.
Currently AuthUser
requires the Clone
trait to be implemented on the User
struct. This is not bad per-say but is not good in terms of secret management hygiene.
I suggest the AuthUser
should handle the password hash very carefully and require the password field to be Zeroizable
, so that the password is not cloned nor left in memory. Returning a &str
instead would possibly also be beneficial rather than String
.
This is possibly a breaking change.
Idea: AuthUser
can return a SecretString
of the password.
I'm trying to implement a /login
and /logout
routes as well as some protected routes. However, after logging in, it seems like the log in isn't saved. When I ping /login
it adds the user to a session, but by the time I try to access a protected route, the session and user are gone. I got the examples to work, but my repo based off of them just doesn't. Every protected route always returns forbidden and even the logout route says the current_user
is None
.
I inserted tracing everywhere to figure out what was happening and noticed that between each request a new session is created. I don't know if this is what's supposed to happen, but all of the data stored in the session is lost.
My SessionLayer
is built with a MemoryStore
and my AuthLayer
is built with an SqliteStore
. I'm using sqlx
and sqlite as a database. I made sure my users are stored in a table called users
in my database.
If it helps, here's my setup for the layers:
let session_store = MemoryStore::new();
let session_layer = SessionLayer::new(session_store, &config.session_secret);
let user_store =
SqliteStore::<User, Role>::new(SqlitePool::connect(&config.database_file).await?);
let auth_layer = AuthLayer::new(user_store, &config.session_secret);
config.session_secret
is a 64 byte array, and config.database_file
is the path to my sqlite db.
type RequireAuth = RequireAuthorizationLayer<User, Role>;
let app = Router::new()
.route(
"/lists",
get(handlers::fetch_user_lists).layer(RequireAuth::login()),
)
.route(
"/list",
get(handlers::fetch_list).layer(RequireAuth::login()),
)
.route(
"/get-lists",
get(handlers::get_lists).layer(RequireAuth::login_with_role(Role::Admin..)),
)
.route(
"/get-users",
get(handlers::get_users).layer(RequireAuth::login_with_role(Role::Admin..)),
)
.route("/status", get(|| async { StatusCode::OK }))
.route("/login", get(handlers::login))
.route("/logout", get(handlers::logout))
.layer(auth_layer)
.layer(session_layer)
.with_state(app_state);
The app_state
contains a sqlx pool for the database. The enum Role
type looks like:
#[derive(
Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, sqlx::Type,
)]
#[repr(i32)]
pub enum Role {
/// Regular user access
#[default]
User = 0,
/// Full access to the database
Admin = 1,
}
I'm new to axum-login
, so sorry for the information dump, I just wanted to put anything that might be useful.
The /login
route takes a json body which describes the user to log into, a name and password. The handler looks like this:
pub type AuthContext = axum_login::extractors::AuthContext<User, SqliteStore<User, Role>, Role>;
#[debug_handler]
pub async fn login(
State(app): State<AppState>,
mut auth: AuthContext,
Json(user): Json<LoginUser>,
) -> AppResult<()> {
debug!("logging in user: {user:?}");
let user: User = sqlx::query_as("SELECT * FROM users WHERE name = ? AND password_hash = ?")
.bind(&user.name)
.bind(&user.password_hash)
.fetch_one(&mut app.conn.acquire().await?)
.await?;
info!("user: {user:?}");
auth.login(&user).await?;
Ok(())
}
Inside the /login
handler I can check auth.current_user
and it is set correctly, but as soon as another route is called that is gone. For example on one run of the server:
Directly before calling auth.login(&user).await?
Session {
id: "jGomHmRqscfQGcLIpZPLzQvN8lDqlQ5qQJMA0GHlamQ=",
expiry: Some(2023-02-10T02:20:06.301437200Z),
data: RwLock {
data: {},
poisoned: false,
..
},
cookie_value: Some("A/MJXP4FMV+rWiRmZMg0X5frpm9Gzt8dqSBCDW/YdVwroQTlmyocXT+X5FnMM5te4Y4PMnKmUwJT5kpBCXa+oQ=="),
data_changed: false,
destroy: false
}
Directly after:
Session {
id: "jGomHmRqscfQGcLIpZPLzQvN8lDqlQ5qQJMA0GHlamQ=",
expiry: Some(2023-02-10T02:20:06.301437200Z),
data: RwLock {
data: {
"_user_id": "\"3\"",
"_auth_id": "\"0qL/ZztGgEixRWFrMorDFNUsauceo4vmDcHcohY18PLWUUxTVc0/0SSSYMYeAaut45ZgEXbPJa1xeQBpyKP86w==\""
},
poisoned: false,
..
},
cookie_value: Some("A/MJXP4FMV+rWiRmZMg0X5frpm9Gzt8dqSBCDW/YdVwroQTlmyocXT+X5FnMM5te4Y4PMnKmUwJT5kpBCXa+oQ=="),
data_changed: true,
destroy: false
}
You can see that the user_id
has been added to the session. But, on the next request to /lists
(or any route, it's all the same) we have:
Session {
id: "txK5yTK9y5Hyu6wxcSF6SwK079pmdSI7n/YV7RPg/HQ=",
expiry: Some(2023-02-10T02:20:13.404553800Z),
data: RwLock {
data: {},
poisoned: false,
..
},
cookie_value: Some("29tPyCptmKMlBSJ8D08lYs1zFdz7NXaHouUiSt6u1o9JZvCI/OzmvZntzUSB6iMqFfI37YCkmKqZaKjQ8DsXjg=="),
data_changed: false,
destroy: false
}
All of a sudden we have a brand new session with the old data gone. I've been struggling for hours trying to find out what I'm doing wrong, and at this point I'm pretty sure it's just some tiny thing I'm missing. Or of course, I'm just misunderstanding how I should be using the library. If so, please let me know. I'd really appreciate if anyone could take a look and see if they notice anything out of order. Also, if you want more information just let me know. Thanks in advance
Is there a way to disable this? I'm trying to use axum-login in an existing tokio/axum project and enabling either of the sqlite/sqlx features produces an error:
06:17:48 โฏ cargo build
Compiling sqlx-rt v0.6.2
error: only one of ['runtime-actix-native-tls', 'runtime-async-std-native-tls', 'runtime-tokio-native-tls', 'runtime-actix-rustls', 'runtime-async-std-rustls', 'runtime-tokio-rustls'] can be enabled
--> /Users/yaleman/.cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-rt-0.6.2/src/lib.rs:23:1
|
23 | / compile_error!(
24 | | "only one of ['runtime-actix-native-tls', 'runtime-async-std-native-tls', \
25 | | 'runtime-tokio-native-tls', 'runtime-actix-rustls', 'runtime-async-std-rustls', \
26 | | 'runtime-tokio-rustls'] can be enabled"
27 | | );
| |_^
error: could not compile `sqlx-rt` due to previous error
warning: build failed, waiting for other jobs to finish...
error: could not compile `sqlx-rt` due to previous error
examples/sqlite/src/main.rs
file the database should be in the path sqlite/user_store.db
, but it actually is in the examples root folder.user_store.db-shm
and user_store.db-wal
files, as these are temporary files.I propose fixing this issue by moving the user_store.db
file into a sqlite
folder and removing the user_store.db-shm
and user_store.db-wal
files.
I already made these changes on a fork. I you allow I will create a Pull Request.
SqlX docs:
Bind parameters in the SQL string are specific to the database backend:
Postgres: $N where N is the 1-based positional argument index
MySQL: ? which matches arguments in order that it appears in the query
Hello, thanks for this great crate.
I was using axum-sessions to store some stuff, and added axum-login afterwards. I added the auth context argument in the request handlers and tried to use auth.login(&..).await
etc. But because I had the session as an argument, there was already an owned RwLock guard (either readable or writable)
pub async fn login_post(
session: ReadableSession,
mut auth: AuthContext,
Form(payload): Form<_>,
) {
let some_session_stuff = session.get::<_>(key);
let user = /* authenticate user */;
/* this will deadlock because `session` is still alive: */
// auth.login(&user).await.unwrap();
/* this will not deadlock: */
drop(session);
auth.login(&user).await.unwrap();
}
, so in AuthContext::login() in extractors.rs, the write() call will deadlock:
axum-login/axum-login/src/extractors.rs
Lines 101 to 107 in 6d2f2ce
This can be fixed by dropping the requests session handle before calling any auth context method that uses the session. This should probably be documented. Would it be possible to introduce multiple session buckets/slots in axum-sessions and separate the user's session from axum-login's?
there were breaking changes in tower-http release 0.4: tower-rs/tower-http#290
it would be great if we could update Axum-login based on these so that updating Axum in app projects using Axum-login becomes possible again.
I noticed this:
axum-login/axum-login/src/auth.rs
Lines 159 to 165 in 7e8f0ef
This returns true if a user's role is None
which means this check will authorize the user:
axum-login/axum-login/src/auth.rs
Line 212 in 7e8f0ef
Would benefit from an example using Postgres.
Firstly, thank you for creating this library!
I'm implementing an OAuth flow where the provider redirects back to our server, we exchange the code for a token, login the user, then redirect to another internal route e.g.:
async fn oauth_callback(mut auth: AuthContext) -> impl IntoResponse {
// I've omitted the oauth details such as code exchange for the token
let user = find_user_somehow();
// login the user
auth.login(&user).await.unwrap();
// This redirect causes the session to be lost, I think we need to pass along the headers that contain the cookie?
Redirect::to("/")
}
// in main
let app = Router::new()
.route("/auth/google/callback", get(oauth_callback))
Axum has an example of the OAuth flow, which looks like this:
// Create a new session filled with user data
let mut session = Session::new();
session.insert("user", &user).unwrap();
// Store session and get corresponding cookie
let cookie = store.store_session(session).await.unwrap().unwrap();
// Build the cookie
let cookie = format!("{}={}; SameSite=Lax; Path=/", COOKIE_NAME, cookie);
// Set cookie
let mut headers = HeaderMap::new();
headers.insert(SET_COOKIE, cookie.parse().unwrap());
// Redirect with headers
(headers, Redirect::to("/"))
How can we redirect and preserve the session? Can we get the headers after the auth.login(...)
call?
Pretty much the title :)
With the axum-sessions
crate migrating to tower-sessions
, are there any plans to update this crate to use tower-sessions
instead?
can we get a new release cut?
I'm pretty sure this is the right place to put this, but if not then feel free to tell me.
Like the examples, I randomly generate a secret every time my server restarts:
let secret = {
let mut rng = thread_rng();
let mut v = Vec::with_capacity(64);
v.append(&mut rng.gen::<[u8; 32]>().to_vec()); //can't get rand to do a [u8; 64] so fun times ensue
v.append(&mut rng.gen::<[u8; 32]>().to_vec());
v
};
```
However, because my server restarts often, that key changes often and logs everybody out when I use a `MemoryStore`.
I'm now using a persistent postgres-based store, but every time I restart, the secret regenerates and the auth layer picks a new ID for each user and everyone is logged out.
Is there an intended way to fix this - storing the secret in the db doesn't seem intuitive, so I was wondering if I was doing something wrong.
My Postgres solution is almost identical to [aysnc-sqlx-session](https://lib.rs/crates/async-sqlx-session)'s postgres one.
Thanks!
The layer should not only check the login and password, but also if the user has the right role.
@maxcountryman I understand that your idea is a RequireAuthorizarionLayer
separate from the normal login
one - the difference being it would also check the role of the user?
Hello @maxcountryman,
While reading the documentation for the RequireAuthorizarionLayer
I noticed this crate does not use it according to its assumed use-case.
The documentation for it states that it's a:
Middleware that authorizes all requests using the Authorization header.
Correct me if I'm wrong, but neither this crate, nor the underlying axum_sessions
uses the standard Authorization header to keep track of the user, but uses cookie-stored sessions instead.
Since they introduced a breaking change in the 0.4.0 version, making the layer async, maybe it's a good time to migrate away from it?
Currently tracing
is provided but only sparsely. Furthermore it might be something that folks should opt into rather than be opted into.
Hello @maxcountryman,
In connection with our discussion under #73 I'd like to propose a roadmap for changes required to this crate to support multiple different database/store backends in a way that they won't conflict with one another.
axum-login-core
sub-crate in this repository. The MemoryStore
can either be left in the axum-login-core
sub-crate or extracted to a separate sub-crate (e.g. axum-login-memory
) to serve as an example for further store implementations. I'd like your decision for this point @maxcountryman.sqlx
related code to a axum-login-sqlx
sub-crate.axum-login-tests
sub-crates will be divided per store sub-crate (currently most of them will go to axum-login-sqlx
).examples
sub-crate shall be left as-is without any major structural changes (except imports).axum-login-core
and axum-login-sqlx
crates separately on crates.io
.axum-login
crate should probably be yanked from crates.io
with information how to migrate to the new crates.In the future all the new store implementations shall be created as sub-crates in the axum-login
workspace.
Additional info: The crate naming I proposed follows the same structure that axum
has.
use axum::async_trait;
use axum_login::{secrecy::SecretVec, AuthUser};
#[derive(Clone, Debug, PartialEq, PartialOrd, sqlx::Type)]
pub enum UserRole {
Standard,
Admin,
}
#[derive(Clone, Debug, sqlx::FromRow, sqlx::Decode)]
pub struct User {
id: i32,
username: String,
password_hash: String,
role: UserRole,
}
impl AuthUser<UserRole> for User {
fn get_id(&self) -> String {
format!("{}", self.id)
}
fn get_password_hash(&self) -> SecretVec<u8> {
SecretVec::new(self.password_hash.clone().into())
}
fn get_role(&self) -> Option<UserRole> {
Some(self.role.clone())
}
}
#[async_trait]
impl axum_login::UserStore<UserRole> for axum_login::PostgresStore<User> {
type User = User;
async fn load_user(&self, user_id: &str) -> Result<Option<Self::User>, eyre::Report> {
let mut connection = self.pool.acquire() ...
}
}
When I try to use self.pool.acquire in the load_user, I get error that self.pool is private. How can I implement load_user without access to the database pool
I upgraded to latest but it's not working:
error[E0599]: the method `login` exists for struct `AuthContext<Uuid, User, SqlxStore<Pool<Postgres>, User>>`, but its trait bounds were not satisfied
--> src/lib/routes/account_api.rs:116:22
|
116 | auth.login(&user).await?;
| ^^^^^ method cannot be called due to unsatisfied trait bounds
|
::: /Users/kings/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-login-0.6.0/src/sqlx_store.rs:43:1
|
43 | pub struct SqlxStore<Pool, User, Role = (), QueryProvider: DefaultQueryProvider = SqlxQueryProvider>
| ----------------------------------------------------------------------------------------------------
| |
| doesn't satisfy `<_ as UserStore<Uuid, ()>>::User = User`
| doesn't satisfy `_: UserStore<Uuid, ()>`
|
= note: the following trait bounds were not satisfied:
`<SqlxStore<sqlx_core::pool::Pool<sqlx_core::postgres::database::Postgres>, User> as UserStore<uuid::Uuid, ()>>::User = User`
`SqlxStore<sqlx_core::pool::Pool<sqlx_core::postgres::database::Postgres>, User>: UserStore<uuid::Uuid, ()>````
I'm seeing this trace while trying to use the User extension as described in the docs: tower_http::trace::on_failure: response failed classification=Status code: 500 Internal Server Error
I believe that auth_cx.current_user should only be inserted if it exists. I can create a pull request if this solution makes sense.
Currently the AuthUser
requires the std::fmt::Debug
trait to be implemented. I believe it's a leftover from some debugging implementation and should not be required. In reality it can even be considered a security issue due to the fact it forces the structs implementing this trait to also implement Debug
and possibly print the password hash to an insecure channel like the console, logs or API error response.
Calling the AuthContext::login function doesn't seem to be checking whether the supplied user has the correct password.
This is fine in lots of cases since the next request checks the password hash, sees that they don't match and logs the user out. I found this slightly misleading, as I then tried to use the current_user
field assuming that the password hashes had been validated.
I think the current behaviour is fine, but it might be worth documenting somewhere pretty obviously that the AuthContext::login function does not actually validate that the supplied user details are correct and just adds them to the user's session and current_user
field, and that validating the supplied user is left up to the developer.
Hi, Im looking to use the crate with an Ldap backend, however, there is mismatch with the current design.
As I understand it a UserStore expects to do a lookup using the user_id which provides the User struct with the password hash, which then gets compared by the login function.
Ldap works differently , the username and password are sent to the server and tested against the stored password server side and a response is returned.
Im not an LDAP expert but if I remember correctly there is a way to configure the Ldap server to return passwords, however, I believe it is not a recommended practice, also this means you have to connect to the server as a privileged user, not a good idea.
changing UserStore to have the function something like
async fn load_user(&self, user_id: &str, password: Option<&str>)
Would there be a way to support this different way of authentication?
Code sample using the ldap3 crate
ldap.simple_bind(&dn, &password).await
this then returns a result code (int) and a possibly empty result string.
let ldap_url=std::env::var("LDAP")
.unwrap_or("ldap://localhost:3389".to_string());
//ldaps 3636
let connect= LdapConnAsync::new(&ldap_url).await;
match connect {
Ok((conn, mut ldap)) => {
ldap3::drive!(conn);
let dn = format!("uid={},ou=people,dc=example,dc=com", username);
match ldap.simple_bind(&dn, &password).await {
Ok(res) => {
if res.rc==0 {
println!("Login Success");
println!("Code {} Message {}", res.rc, res.text.len());
} else {
//Code 49 bad user or password
//Code 53 account inactivated
println!("Code {} Message {}", res.rc, res.text);
}
}
Err(err) => {
info!("Ldap Search Error {:?}",err);
}
}
},
Err(err) => {
info!("Ldap Connect Error {:?}",err);
}
}
I am trying to use axum-login in my project.
The structure is below
โโโ routers
โ โโโ auth.rs (use axum-login here)
โ โโโ mod.rs
โ โโโ patient.rs
โ โโโ router.rs
โ โโโ sign_up.rs
In router.rs
, I tried to merge all router together,
let route = Router::new()
.route("/admin", get(admin_handler))
.route_layer(RequireAuthorizationLayer::<User, Role>::login_with_role(
Role::Admin..,
))
.route("/", get(protected_handler))
.route_layer(RequireAuthorizationLayer::<User, Role>::login());
route
.merge(sign_up::create_router().await)
.merge(patient::create_router().await)
.merge(auth::create_router().await)
In auth.rs
Router::new()
.route("/auth/login", post(login_handler))
.route("/auth/logout", get(logout_handler))
.layer(auth_layer)
.layer(session_layer)
When I ran the program, the server could not find extention. And showed
thread 'tokio-runtime-worker' panicked at 'Auth extension missing. Is the auth layer installed?',
Should I use other functions, instead of merge
? Does anyone give me some advice? Thanks.
Currently there's a solitary test which shows that the library functions at some basic level. It would be nice to extend test coverage to ensure other parts of the library behave as we'd like. For example, testing the new role functionality might be good and no doubt there are other places we could introduce testing to help increase confidence in the code.
Hello,
Currently the library always returns a specified set of responses on errors. We can see it here and here.
In some cases the users may want to modify the returned values e.g. to implement RFC 7807. The library should possibly allow this kind of use-case the same way as the axum Json
extractor allows to specify a rejection response.
@maxcountryman any thoughts?
I trust this message finds you well. I wanted to share an idea that I believe could significantly enrich our product.
At present, axum-login relies on SQL database systems to store and manage user information. While these systems are powerful and have been useful so far, I propose that we also include support for NoSQL databases, specifically MongoDB and Redis. Incorporating these could provide significant benefits in terms of scalability, performance, and flexibility.
NoSQL databases are known for their high scalability and flexibility, allowing for a more efficient handling of large data sets and complex data structures. MongoDB, for example, uses a document-oriented model that can store vast amounts of data in varied formats, and its distributed architecture ensures high availability and scalability. On the other hand, Redis, an in-memory data structure store, can serve as a database, cache, and message broker, providing exceptional performance for real-time applications.
By supporting NoSQL databases, we can make our system more versatile, catering to a wider range of use cases and potential clients. This could also make our system more robust and future-proof, as NoSQL databases are increasingly being adopted in the industry.
I look forward to hearing your thoughts on this matter.
I'm using Sea-ORM for a project of mine and I wanted use axum-login to handle authentication. I'm using postgres as the database so I know I can use the postgres feature. But I was wondering if I can create my own store using Sea-ORM. If so, how would I go about doing that?
I tried implementing it on my own using memory_store.rs and sql_store.rs as examples but the compiler keeps complaining about AuthUser not being satisfied for my User struct (even though I believe I've implemented it). Can you give me some pointers?
Thanks.
Hi, thanks for all your efforts making axum-login, it's extremely useful!
I have found an issue when creating an API using CORS, where any attempt to put a WebSocket endpoint behind RequireAuthorizationLayer::login()
causes it to unconditionally return 401 Unauthorized
, whether or not the user is logged in. I have traced this issue back as far as I'm able, in authorize() the user is None. Changing CORS or Cookie settings do not appear to have any effect.
Steps to Reproduce:
Thanks again, if I can do anything to help track this down I'm happy to help!
I have been hoping to replace my custom/fragile middleware I built around axum-sessions
with axum-login
. One of the things I cannot figure out with this library is how to redirect an unauthenticated user to the login page.
Right now, because of how "requries_auth" attempts to extract a user, there is no way to redirect when there is no user available.
Do you have a recommendation or am I going about this all wrong?
PartialOrd
on the Role
login_with_role
to support a range of rolesI recently came across Sled when I searched for other possibilities for persistent application state. I think it would be nice to support it as an alternative for sqlx
.
Hello @maxcountryman,
As mentioned in the title, my local development frontend server is on port localhost:9000
, my backend is on localhost:8080
and the session id changes constantly.
When using the same port localhost:8080
both for the frontend as well as the backend everything works as expected, the session id remains the same. Do you have any idea what the issue may be?
I tried setting the with_same_site_policy(SameSite::None)
flag on the SessionLayer
but it doesn't change anything.
I've tried to implement custom UserStore
, but the return type of method load_user
depends on eyre
's Report
which I'm not using in my application. It'd probably make more sense to give implementors more freedom and require std::error::Error
instead.
Trying to return sqlx_store::SqlxStore
in a init function to bootstrap application.
Hi, sorry for long title.
I switched my graphql api on my personal project to a rest api and wrote this for describing authorization using AND/OR logic like graphql. It's not very ergonomic, also I use BoxFuture because I couldn't quite figure out how to write the future by hand. But, I was wondering if you were interested in a similar feature for axum-login or if one already exists. I think it would be good for the ecosystem to have one broad axum auth toolset, that let users describe authorization in a varied way.
https://github.com/sjud/axum_guard_logic
Warm regards, thank you for your FOSS contribution
As mentioned in #8 either an example or additional support traits or methods for RBAC would benefit the project.
@maxcountryman do you have some ideas on how this would look like?
What I imagine is a get_role() -> Option<R>
method on AuthUser
where R
is some enum
defining the possible roles for a user.
Additionally some (generic?) extractor (or maybe a macro to generate such an extractor) to allow only users with at least one of the roles specified in the extractor parameters, would be perfect.
This can possibly be implemented in a backwards-compatible way by providing a default implementation for the new method, which would return None
.
As suggested in #85 it would be a nice idea to have an example for situations where one would like to authenticate a user with two different secrets - a token a and a password.
axum-login/examples/sqlite/src/main.rs
Line 55 in 0b87c32
Here, can only search user=1 from the database. If I want to login some user else, what should I do.
Can you add a more general example? Thank you very much.
Running this code I get a type error on the last line. The issue seems to be that axum-login is using sqlx_core's
implementation of PgPool.
use sqlx::{Pool, Postgres, PgPool};
pub async fn new_app() -> Option<Router> {
let pool = PgPoolOptions::new()
.max_connections(5)
.connect("postgres://moose:test@localhost/test")
.await
.unwrap();
let pg_pool: PgPool = pool.clone();
let mut secret = [0u8; 64];
thread_rng().fill(&mut secret);
let session_store = UserSessionStore::new(&pool);
let session_layer = SessionLayer::new(session_store, &secret);
let user_store = PostgresStore::<User, Role>::new(pg_pool);
mismatched types [E0308] expected `sqlx_core::postgres::PgPool`, found `PgPool`
An issue for posterity
There seems to be a problem when using async_sqlx_session.
When using jbr/async_sqlx_session, the sqlx version is up to date but one can only use async_std whereas axum uses tokio.
When using CyrusAlbright/async_sqlx_session, it's possible to use tokio but the sqlx version is not up to date and for instance the PostgresSessionStore cannot be instanciated through the from_client() function.
Do you know if there is a way to solve this problem ?
Thanks a lot for your answer and for your awesome work.
Sea-ORM is a popular async ORM that uses sqlx
under the hood. There is a discussion about creating a store for it, but it would be awesome if axum-login
supports this out of the box.
Hi! I was evaluating this crate for use in my project and found it a little bit opinionated. I will describe my issue and would love to hear your feedback!
AuthUser
trait is synchronousIn my project, User
struct has an id
, role
and optional foreign key profile_id
. There are 3 roles: guest
, normal
and admin
. If user is a guest
, then there's no related profile and profile_id
is set to None
. Profile
struct has an email
(which is the primary key), password_hash
and some other data.
So, now I'm implementing the AuthUser
trait. Getting a role is easy. But getting password_hash
is not: for a guest
user I can just return an empty Vec
, but for normal
and admin
user I need to fetch related Profile
, which would be an async operation inside a sync function. Surely, I can try to implement UserStore
and fetch the profile eagerly. But this will complicate my type signature. Or I can move email
and password_hash
from Profile
to User
, but it won't make sense for a guest
user.
Making the trait async would be a far better option. Surely, it is a breaking change, but the migration is very straightforward: just add #[async_trait]
to the trait impl and async
modifier before methods.
AuthUser
expects password hashBut what if I want to use a passwordless authentication? For example, a magic-link. In this case, for every signed user I would store some kind of token, and use it instead of the password hash. Maybe this particular example is not ideal, but you probably get the idea.
How to deal with it? Just rename get_password_hash
to get_secret
and specify password hash as an example for such a secret in the docs.
A new example in the examples
directory, which would show how to require not only a logged in user, but a user with a given "permission" (like a field is_admin
set to true
) would be very helpful for beginners.
Maybe creating a generic extractor trait to check some permission (which later can be specified to a RequireAdmin
extractor or some other permission) by the end-user would also be helpful.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.