TODO
- Add more examples
- Improve coverage
Features
- Manages & Orchestrates JWT for user login, logout & renew
- Option 1: DynamicVault (uses dynamic dispatch but requires considerable less boiler-plate code)
- Option 2: DefaultVault (uses static dispatch but requires considerable more boiler-plate code)
- Async ready
- Easy start
- No un-safe code
- Runs on stable rust
- Uses Argon (see video)
- Library approach (Requires no runtime)
- Supports plugable components
- Invalidates old refresh upon new refresh token renewal
- Invalidates old authentication upon new authentication token renewal
- Cross feeding not allowed
- Handles Thundering herd problem upon authentication token expiry
- Works with any web-server, any password hashing, and any backend (here)
- Fully functional
webserver
with actix
and postgres
Quickstart
Prerequisite:
[dependencies]
jwtvault = "*"
$ curl https://raw.githubusercontent.com/sgrust01/jwtvault/master/generate_certificates.sh > ./generate_certificates.sh
$ chmod 700 generate_certificates.sh && ./generate_certificates.sh
Option 1: DynamicVault
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use jwtvault::prelude::*;
fn main() {
let hasher = ArgonPasswordHasher::default();
let user_john = "john_doe";
let password_for_john = "john";
let hashed_password_for_john = hasher.hash_user_password(user_john, password_for_john).unwrap();
let user_jane = "jane_doe";
let password_for_jane = "jane";
let hashed_password_for_jane = hasher.hash_user_password(user_jane, password_for_jane).unwrap();
let mut users = HashMap::new();
users.insert(user_john.to_string(), hashed_password_for_john.to_string());
users.insert(user_jane.to_string(), hashed_password_for_jane.to_string());
let login = LoginInfo::new(users);
let mut vault = DynamicVault::default(Box::new(login));
let token = block_on(vault.login(
user_john,
password_for_john,
None,
None,
));
let token = token.ok().unwrap();
let server_refresh_token = block_on(resolve_session_from_client_authentication_token(
&mut vault,
user_john,
token.authentication(),
));
let server_refresh_token = server_refresh_token.ok().unwrap();
let private_info_about_john = server_refresh_token.server().unwrap();
let key = digest::<_, DefaultHasher>(user_john);
let data_on_server_side = private_info_about_john.get(&key).unwrap();
assert!(server_refresh_token.client().is_none());
println!("[Private] John Info: {}",
String::from_utf8_lossy(data_on_server_side.as_slice()).to_string());
let new_token = block_on(vault.renew(
user_john,
token.refresh(),
None,
));
let new_token = new_token.ok().unwrap();
let result = block_on(resolve_session_from_client_authentication_token(
&mut vault,
user_john,
new_token.as_str(),
));
let _ = result.ok().unwrap();
}
Option 2: DefaultVault
use jwtvault::prelude::*;
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
fn main() {
let mut users = HashMap::new();
let loader = CertificateManger::default();
let user_john = "john_doe";
let password_for_john = "john";
let hashed_password_for_john = hash_password_with_argon(
password_for_john,
loader.password_hashing_secret().as_str(),
).unwrap();
let user_jane = "jane_doe";
let password_for_jane = "jane";
let hashed_password_for_jane = hash_password_with_argon(
password_for_jane,
loader.password_hashing_secret().as_str(),
).unwrap();
users.insert(user_john.to_string(), hashed_password_for_john);
users.insert(user_jane.to_string(), hashed_password_for_jane);
let mut vault = DefaultVault::new(loader, users, false);
let token = block_on(vault.login(
user_john,
password_for_john,
None,
None,
));
let token = token.ok().unwrap();
let server_refresh_token = block_on(resolve_session_from_client_authentication_token(
&mut vault,
user_john,
token.authentication(),
));
let server_refresh_token = server_refresh_token.ok().unwrap();
let private_info_about_john = server_refresh_token.server().unwrap();
let key = digest::<_, DefaultHasher>(user_john);
let data_on_server_side = private_info_about_john.get(&key).unwrap();
assert!(server_refresh_token.client().is_none());
println!("[Private] John Info: {}",
String::from_utf8_lossy(data_on_server_side.as_slice()).to_string());
let new_token = block_on(vault.renew(
user_john,
token.refresh(),
None,
));
let new_token = new_token.ok().unwrap();
let result = block_on(resolve_session_from_client_authentication_token(
&mut vault,
user_john,
new_token.as_str(),
));
let _ = result.ok().unwrap();
}
Workflows
-
To begin use login
with user and password
-
Upon successful login is provides user will be provided with JWT pair (authentication/refresh)
-
Authentication token is then provided to access any resources
-
Refresh token is used to renew an authentication token upon expiry
-
Use resolve_session_from_client_authentication_token
with user and authentication_token to restore user session
-
Use renew
with user and refresh_token to generate new authentication token
-
Use logout
with user and authentication_token will remove all tokens associated with the user
-
Use helper continue_generate_temporary_token
to generate temporary token for user
- Temporary token creates temporary session, and does not corrupt the original session info
- Temporary token cannot be used to login/logout/renew/revoke original session
- Temporary token does not have refresh key (this is intentional), to avoid token refresh
- Note: DynamicVault users can directly use instance method
generate_temporary_token
- Typical use-case: Forgot Password
-
Use helper resolve_temporary_session_from_client_authentication_token
to restore user temporary session
- Temporary session are isolated instance, and thus has no fingerprints of the originals session
- Typical use-case: Reset Password