Expand description
Tarcza’s an authentication toolkit, which is meant to help you integrate authentication easier, no matter what stack you are using or what are your needs.
It’s not a fully fledged “Authentication Framework”, rather a toolkit to simply integrate authentication yourself.
Tarcza offers 3 main functionalities:
- Storing user details in backend of your choosing
- Authenticating user from their ID and password
- Handling user sessions with JWT
Everything happen via StoreHandler trait and Authenticator struct,
which seaminglesly handle: password hashing (using Argon2), JWT allocation, storing user info.
API is meant to be simple and flexible, giving you full freedom to implement:
- How is user info (ID and password hash) stored in the backend (via
StoreHandlertrait) - How is user represented internally (again, via
StoreHandlerwhich can return anyUsertype you want, after successful authentication) - How strong is hashing (via
Argon2Presetenum, which includes fine defaults, but you can also define your own parameters if you wish) - JWT handling (
JwtHandlerstruct, likeArgon2Preset, includes default options for quickly creating JWT handlers, while also giving you option to select any Algorithm you want)
Tarcza is meant to be framework/storage/stack agnostic, letting you use whatever you want, while handling authentication needs for you
§Example usage:
use std::{
collections::HashMap,
sync::{Arc, Mutex},
time::Duration
};
use tarcza;
// In real code you would probably use an actual Database/Storage, but you can also do this I'm not your mom
// In this example we just keep HashMap storing user ID and password hash
struct ExampleDB {
users: Arc<Mutex<HashMap<String, String>>>
}
impl ExampleDB {
fn new() -> Self {
let db = Arc::new(Mutex::new(HashMap::new()));
ExampleDB { users: db }
}
}
// Internal representation of authenticating User
// In this example it's just gonna keep it's own ID here;
type User = String;
#[async_trait::async_trait]
impl tarcza::StoreHandler<User> for ExampleDB {
async fn store_user(&self, id: &str, password_hash: &str) -> Result<(), tarcza::AuthenterError> {
self.users.lock().map_err(|e| tarcza::AuthenterError::BackendError(e.to_string().into()))?.insert(id.to_string(), password_hash.to_string());
Ok(())
}
async fn get_password_hash(&self, id: &str) -> Result<String, tarcza::AuthenterError> {
self.users.lock().map_err(|e| tarcza::AuthenterError::BackendError(e.to_string().into()))?.get(id).cloned().ok_or(tarcza::AuthenterError::NoUser)
}
async fn get_user(&self, id: &str) -> Result<User, tarcza::AuthenterError> {
Ok(id.to_string())
}
}
#[tokio::main]
async fn main() {
let db = Arc::new(ExampleDB::new());
// Here you declare JwtHandler which is used to allocate tokens
let jwt = tarcza::JwtHandler::new_hmac("super unique secure".as_bytes());
// `tarcza::Argon2Preset` is an Enum containing either: one of the default presets, or your own Argon2 instance if you want to set your own parameters
// `Duration` here declares for how long are JWT tokens valid
let auth = tarcza::Authenticator::new(jwt, tarcza::Argon2Preset::Low, db, Duration::from_secs(60));
let user_id = "user";
// Tarcza requires to pass user password as `SecretString` (from `Secrecy` crate, re-imported for convenience)
// It enforces that plaintext password is properly cleaned after usage (unless you copy it before wrapping, you can do it even tho I wouldn't recommend)
let password: tarcza::SecretString = "password".into();
// Let's register an user!
assert!(auth.register_user(&user_id, password).await.is_ok());
// Since `auth.register_user` returned Ok, it means that user is now registered, let's try to log in.
// Our password has been consumed, so we have to declare a new one
let password: tarcza::SecretString = "password".into();
let token = auth.login(&user_id, password).await;
assert!(token.is_ok());
// Login is also Ok! It means that we are logged in, and `Authenticator` returned a JWT which we can now use to get our `User`!
assert!(auth.login_jwt(&token.unwrap()).await.is_ok_and(|v| v == user_id));
// Since in our example our `User` is just a String containing user_id, we can see that we are successfuly authenticated!
}This example shows basic workflow of registering user, receiving JWT token for authentication, and using said JWT to get our internal representation of User type.
Re-exports§
pub use argon2presets::Argon2Preset;pub use jwt::JwtHandler;
Modules§
Structs§
- Authenticator
- Handles authorization pipeline
Enums§
- Authenter
Error - Represents errors that can be caused by backend
- Authenticator
Error
Traits§
- Store
Handler - Trait over backend for persisting user data
Type Aliases§
- Secret
String - Secret string type.