1 unstable release
Uses new Rust 2024
0.1.0 | Apr 5, 2025 |
---|
#14 in #jwt-auth
127 downloads per month
56KB
951 lines
axum-gate
Fully customizable role based JWT auth for axum, applicable for single nodes or distributed systems.
axum-gate uses composition of different services to enable maximum flexibility for any specific use case.
lib.rs
:
Fully customizable role based JWT cookie auth for axum, applicable for single nodes or distributed systems.
axum-gate
uses composition of different services to enable maximum flexibility
for any specific use case.
Examples
These examples aim to give you a basic overview about the possibilities that axum-gate offers.
Prerequisites to protect your application
To protect your application with axum-gate
you need to use storages that implement
CredentialsStorageService,
CredentialsVerifierService and
PassportStorageService. It is possible to implement
all on the same storage if it is responsible
for Passport
as well as the
Credentials
of a user.
In case of the pre-defined PassportMemoryStorage and CredentialsMemoryStorage (implements both, CredentialsStorageService and CredentialsVerifierService) , the following steps are required during the setup of your app. The pre-defined storages use the memory to store the information.
// We create a hasher that protects the secret in the persistent storage.
let hasher = Arc::new(Argon2Hasher::default());
// We first need to create the credentials.
// For demonstration purpose only, your application should provide another way to add
// credentials.
let user_creds = Credentials::new(
"user@example.com".to_string(),
"user_password".to_string(),
)
// The secret should always be hashed when persisting. For maximum control, this is not done
// automatically.
.hash_secret(&*hasher)
.unwrap();
// Then a credentials storage is created.
let creds_storage = CredentialsMemoryStorage::from(vec![user_creds.clone()]);
// Same for the passport which provides details about the user.
// The ID is used to create a connection between the storage entries.
let user_passport = BasicPassport::new(&user_creds.id, &["user"], &[BasicRole::User])
.expect("Creating passport failed.");
let passport_storage = PassportMemoryStorage::from(vec![user_passport]);
Protecting your application
The actual protection of your application is pretty simple. All possibilities presented below can also be combined so you are not limited to choosing one.
Limit access to a specific role
You can limit the access of a route to one or multiple specific role(s).
let cookie_template = axum_gate::cookie::CookieBuilder::new("axum-gate", "").secure(true);
// let app = Router::new() is enough in the real world, this long type is to satisfy compiler.
let app = Router::<Gate<BasicPassport, JsonWebToken<BasicPassport>>>::new()
.route(
"/admin",
// Please note, that the layer is applied directly to the route handler.
get(admin).layer(
Gate::new(Arc::clone(&jwt_codec))
.with_cookie_template(cookie_template)
.grant_role(BasicRole::Admin)
.grant_role(BasicRole::User)
)
);
Grant access to a specific role and all its supervisors
If your role implements [AccessHierarchy], you can limit the access of a route to a specific role but at the same time allow it to all supervisor of this role. This is also possible for multiple roles, although this does not make much sense in a real world application.
let cookie_template = axum_gate::cookie::CookieBuilder::new("axum-gate", "").secure(true);
// let app = Router::new() is enough in the real world, this long type is to satisfy compiler.
let app = Router::<Gate<BasicPassport, JsonWebToken<BasicPassport>>>::new()
.route("/user", get(user))
// In contrast to granting access to user only, this layer is applied to the route.
.layer(
Gate::new(Arc::clone(&jwt_codec))
.with_cookie_template(cookie_template)
.grant_role_and_supervisor(BasicRole::User)
);
Grant access to a group of users
You can limit the access of a route to one or more specific group(s).
let cookie_template = axum_gate::cookie::CookieBuilder::new("axum-gate", "").secure(true);
// let app = Router::new() is enough in the real world, this long type is to satisfy compiler.
let app = Router::<Gate<BasicPassport, JsonWebToken<BasicPassport>>>::new()
.route(
"/group-scope",
// Please note, that the layer is applied directly to the route handler.
get(group_handler).layer(
Gate::new(Arc::clone(&jwt_codec))
.with_cookie_template(cookie_template)
.grant_group(BasicGroup::new("my-group"))
.grant_group(BasicGroup::new("another-group"))
)
);
Using Passport
details in your route handler
axum-gate
provides two Extensions to the handler.
The first one contains the RegisteredClaims, the second
your custom claims. In this pre-defined case it is the
BasicPassport
.
You can use them like any other extension:
async fn reporter(Extension(user): Extension<BasicPassport>) -> Result<String, ()> {
Ok(format!(
"Hello {}, your roles are {:?} and you are member of groups {:?}!",
user.id, user.roles, user.groups
))
}
Enable login and logout for your application
axum-gate
provides pre-defined route_handler for login and logout
using Credentials.
Login
To enable a login, you only need to add a custom route with the login handler.
let cookie_template = axum_gate::cookie::CookieBuilder::new("axum-gate", "").secure(true);
// let app = Router::new() is enough in the real world, this long type is to satisfy compiler.
let app = Router::<Gate<BasicPassport, JsonWebToken<BasicPassport>>>::new()
.route(
"/login",
post({
let registered_claims = RegisteredClaims::default();
let credentials_verifier = Arc::clone(&creds_storage);
let credentials_hasher = Arc::clone(&hasher);
let passport_storage = Arc::clone(&passport_storage);
let jwt_codec = Arc::clone(&jwt_codec);
let cookie_template = cookie_template.clone();
move |cookie_jar, request_credentials: Json<Credentials<String, String>>| {
axum_gate::route_handlers::login(
cookie_jar,
request_credentials,
registered_claims,
credentials_verifier,
credentials_hasher,
passport_storage,
jwt_codec,
cookie_template,
)
}
}),
);
Logout
Because axum-gate
is using a cookie to store the information, you can easily create a logout
route:
let cookie_template = axum_gate::cookie::CookieBuilder::new("axum-gate", "").secure(true);
let app = Router::new()
.get({
move |cookie_jar| {
axum_gate::route_handlers::logout(cookie_jar, cookie_template)
}
});
Internal examples
- A pre-defined implementation of SecretsHashingService can be found at Argon2Hasher that is used to hash credentials before persisting it using CredentialsStorageService
- An example for a CredentialsStorageService / CredentialsVerifierService used for authentication can be found at CredentialsMemoryStorage
Dependencies
~9–20MB
~288K SLoC