123 lines
3.4 KiB
Markdown
123 lines
3.4 KiB
Markdown
|
General purpose JWT session validator for actix_web
|
|||
|
|
|||
|
It’s designed to extract session using middleware and validate path simply by using extractors.
|
|||
|
|
|||
|
Examples usage:
|
|||
|
|
|||
|
```rust
|
|||
|
use std::boxed::Box;
|
|||
|
use std::sync::Arc;
|
|||
|
use actix_jwt_session::*;
|
|||
|
use actix_web::get;
|
|||
|
use actix_web::web::Data;
|
|||
|
use actix_web::{HttpResponse, App, HttpServer};
|
|||
|
use ring::rand::SystemRandom;
|
|||
|
use ring::signature::{Ed25519KeyPair, KeyPair};
|
|||
|
use jsonwebtoken::*;
|
|||
|
use serde::{Serialize, Deserialize};
|
|||
|
|
|||
|
#[tokio::main]
|
|||
|
async fn main() {
|
|||
|
let redis = {
|
|||
|
use redis_async_pool::{RedisConnectionManager, RedisPool};
|
|||
|
RedisPool::new(
|
|||
|
RedisConnectionManager::new(
|
|||
|
redis::Client::open("redis://localhost:6379").expect("Fail to connect to redis"),
|
|||
|
true,
|
|||
|
None,
|
|||
|
),
|
|||
|
5,
|
|||
|
)
|
|||
|
};
|
|||
|
|
|||
|
let keys = JwtSigningKeys::generate().unwrap();
|
|||
|
let factory = RedisMiddlewareFactory::<AppClaims>::new(
|
|||
|
Arc::new(keys.encoding_key),
|
|||
|
Arc::new(keys.decoding_key),
|
|||
|
Algorithm::EdDSA,
|
|||
|
redis.clone(),
|
|||
|
vec![
|
|||
|
// Check if header "Authorization" exists and contains Bearer with encoded JWT
|
|||
|
Box::new(HeaderExtractor::new("Authorization")),
|
|||
|
// Check if cookie "jwt" exists and contains encoded JWT
|
|||
|
Box::new(CookieExtractor::new("jwt")),
|
|||
|
]
|
|||
|
);
|
|||
|
|
|||
|
HttpServer::new(move || {
|
|||
|
let factory = factory.clone();
|
|||
|
App::new()
|
|||
|
.app_data(Data::new(factory.storage()))
|
|||
|
.wrap(factory)
|
|||
|
.app_data(Data::new(redis.clone()))
|
|||
|
.service(storage_access)
|
|||
|
.service(must_be_signed_in)
|
|||
|
.service(may_be_signed_in)
|
|||
|
})
|
|||
|
.bind(("0.0.0.0", 8080)).unwrap()
|
|||
|
.run()
|
|||
|
.await.unwrap();
|
|||
|
}
|
|||
|
|
|||
|
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
|||
|
pub struct AppClaims {
|
|||
|
id: uuid::Uuid,
|
|||
|
subject: String,
|
|||
|
}
|
|||
|
|
|||
|
impl Claims for AppClaims {
|
|||
|
fn jti(&self) -> uuid::Uuid { self.id }
|
|||
|
fn subject(&self) -> &str { &self.subject }
|
|||
|
}
|
|||
|
|
|||
|
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
|||
|
pub struct SessionData {
|
|||
|
id: uuid::Uuid,
|
|||
|
subject: String,
|
|||
|
}
|
|||
|
|
|||
|
#[actix_web::post("/access-storage")]
|
|||
|
async fn storage_access(
|
|||
|
session_store: Data<SessionStorage<AppClaims>>,
|
|||
|
p: actix_web::web::Json<SessionData>,
|
|||
|
) -> HttpResponse {
|
|||
|
let p = p.into_inner();
|
|||
|
session_store.store(AppClaims {
|
|||
|
id: p.id,
|
|||
|
subject: p.subject,
|
|||
|
}, std::time::Duration::from_secs(60 * 60 * 24 * 14) ).await.unwrap();
|
|||
|
HttpResponse::Ok().body("")
|
|||
|
}
|
|||
|
|
|||
|
#[get("/authorized")]
|
|||
|
async fn must_be_signed_in(session: Authenticated<AppClaims>) -> HttpResponse {
|
|||
|
let jit = session.jti();
|
|||
|
HttpResponse::Ok().body("")
|
|||
|
}
|
|||
|
|
|||
|
#[get("/maybe-authorized")]
|
|||
|
async fn may_be_signed_in(session: MaybeAuthenticated<AppClaims>) -> HttpResponse {
|
|||
|
if let Some(session) = session.into_option() {
|
|||
|
}
|
|||
|
HttpResponse::Ok().body("")
|
|||
|
}
|
|||
|
|
|||
|
pub struct JwtSigningKeys {
|
|||
|
encoding_key: EncodingKey,
|
|||
|
decoding_key: DecodingKey,
|
|||
|
}
|
|||
|
|
|||
|
impl JwtSigningKeys {
|
|||
|
fn generate() -> Result<Self, Box<dyn std::error::Error>> {
|
|||
|
let doc = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new())?;
|
|||
|
let keypair = Ed25519KeyPair::from_pkcs8(doc.as_ref())?;
|
|||
|
let encoding_key = EncodingKey::from_ed_der(doc.as_ref());
|
|||
|
let decoding_key = DecodingKey::from_ed_der(keypair.public_key().as_ref());
|
|||
|
Ok(JwtSigningKeys {
|
|||
|
encoding_key,
|
|||
|
decoding_key,
|
|||
|
})
|
|||
|
}
|
|||
|
}
|
|||
|
```
|