mirror of
https://github.com/TECHNOFAB11/jwt-authorizer.git
synced 2025-12-12 16:10:06 +01:00
feat: claims
This commit is contained in:
parent
93325dce96
commit
d3fc883006
6 changed files with 318 additions and 125 deletions
|
|
@ -11,6 +11,7 @@ keywords = ["jwt","axum","authorisation","jwks"]
|
|||
|
||||
[dependencies]
|
||||
axum = { version = "0.6", features = ["headers"] }
|
||||
chrono = "0.4"
|
||||
futures-util = "0.3"
|
||||
futures-core = "0.3"
|
||||
headers = "0.3"
|
||||
|
|
|
|||
|
|
@ -19,20 +19,15 @@ JWT authoriser Layer for Axum and Tonic.
|
|||
## Usage Example
|
||||
|
||||
```rust
|
||||
# use jwt_authorizer::{AuthError, JwtAuthorizer, JwtClaims};
|
||||
# use jwt_authorizer::{AuthError, JwtAuthorizer, JwtClaims, RegisteredClaims};
|
||||
# use axum::{routing::get, Router};
|
||||
# use serde::Deserialize;
|
||||
|
||||
# async {
|
||||
|
||||
// struct representing the authorized caller, deserializable from JWT claims
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct User {
|
||||
sub: String,
|
||||
}
|
||||
|
||||
// let's create an authorizer builder from a JWKS Endpoint
|
||||
let jwt_auth: JwtAuthorizer<User> =
|
||||
// (a serializable struct can be used to represent jwt claims, JwtAuthorizer<RegisteredClaims> is the default)
|
||||
let jwt_auth: JwtAuthorizer =
|
||||
JwtAuthorizer::from_jwks_url("http://localhost:3000/oidc/jwks");
|
||||
|
||||
// adding the authorization layer
|
||||
|
|
@ -40,9 +35,9 @@ JWT authoriser Layer for Axum and Tonic.
|
|||
.layer(jwt_auth.layer().await.unwrap());
|
||||
|
||||
// proteced handler with user injection (mapping some jwt claims)
|
||||
async fn protected(JwtClaims(user): JwtClaims<User>) -> Result<String, AuthError> {
|
||||
async fn protected(JwtClaims(user): JwtClaims<RegisteredClaims>) -> Result<String, AuthError> {
|
||||
// Send the protected data to the user
|
||||
Ok(format!("Welcome: {}", user.sub))
|
||||
Ok(format!("Welcome: {:?}", user.sub))
|
||||
}
|
||||
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
|
|
|
|||
105
jwt-authorizer/src/claims.rs
Normal file
105
jwt-authorizer/src/claims.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use std::fmt;
|
||||
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
|
||||
/// Seconds since the epoch
|
||||
#[derive(Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct NumericDate(i64);
|
||||
|
||||
impl From<NumericDate> for DateTime<Utc> {
|
||||
fn from(t: NumericDate) -> Self {
|
||||
Utc.timestamp_opt(t.0, 0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct StringList(Vec<String>);
|
||||
|
||||
/// Claims mentioned in the JWT specifications.
|
||||
///
|
||||
/// https://www.rfc-editor.org/rfc/rfc7519#section-4.1
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct RegisteredClaims {
|
||||
pub iss: Option<String>,
|
||||
pub sub: Option<String>,
|
||||
pub aud: Option<StringList>,
|
||||
pub exp: Option<NumericDate>,
|
||||
pub nbf: Option<NumericDate>,
|
||||
pub iat: Option<NumericDate>,
|
||||
pub jti: Option<String>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for StringList {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct StringListVisitor;
|
||||
impl<'de> de::Visitor<'de> for StringListVisitor {
|
||||
type Value = StringList;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(formatter, "a space seperated strings")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
let auds: Vec<String> = v.split(' ').map(|s| s.to_owned()).collect();
|
||||
Ok(StringList(auds))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_string(StringListVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::claims::{NumericDate, RegisteredClaims, StringList};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TestStruct {
|
||||
v: StringList,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rfc_claims_aud() {
|
||||
let a: TestStruct = serde_json::from_str(r#"{"v":"a b"}"#).unwrap();
|
||||
assert_eq!(a.v, StringList(vec!["a".to_owned(), "b".to_owned()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_numeric_date() {
|
||||
let exp: DateTime<Utc> = NumericDate(1516239022).into();
|
||||
assert_eq!(exp, Utc.timestamp_opt(1516239022, 0).unwrap());
|
||||
assert_eq!(exp, DateTime::parse_from_rfc3339("2018-01-18T01:30:22.000Z").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rfc_claims() {
|
||||
let jwt_json = json!({
|
||||
"iss": "http://localhost:3001",
|
||||
"aud": "aud1 aud2",
|
||||
"sub": "bob",
|
||||
"exp": 1516240122,
|
||||
"iat": 1516239022,
|
||||
}
|
||||
);
|
||||
|
||||
let claims: RegisteredClaims = serde_json::from_value(jwt_json).expect("Failed RfcClaims deserialisation");
|
||||
assert_eq!(claims.iss.unwrap(), "http://localhost:3001");
|
||||
assert_eq!(claims.aud.unwrap(), StringList(vec!["aud1".to_owned(), "aud2".to_owned()]));
|
||||
assert_eq!(claims.exp.unwrap(), NumericDate(1516240122));
|
||||
|
||||
let dt: DateTime<Utc> = claims.iat.unwrap().into();
|
||||
assert_eq!(dt, Utc.timestamp_opt(1516239022, 0).unwrap());
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ use tower_layer::Layer;
|
|||
use tower_service::Service;
|
||||
|
||||
use crate::authorizer::{Authorizer, FnClaimsChecker, KeySourceType};
|
||||
use crate::claims::RegisteredClaims;
|
||||
use crate::error::InitError;
|
||||
use crate::jwks::key_store_manager::Refresh;
|
||||
use crate::validation::Validation;
|
||||
|
|
@ -22,7 +23,7 @@ use crate::{layer, AuthError, RefreshStrategy};
|
|||
///
|
||||
/// - initialisation of the Authorizer from jwks, rsa, ed, ec or secret
|
||||
/// - can define a checker (jwt claims check)
|
||||
pub struct JwtAuthorizer<C>
|
||||
pub struct JwtAuthorizer<C = RegisteredClaims>
|
||||
where
|
||||
C: Clone + DeserializeOwned,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ use jsonwebtoken::TokenData;
|
|||
use serde::de::DeserializeOwned;
|
||||
|
||||
pub use self::error::AuthError;
|
||||
pub use claims::{NumericDate, RegisteredClaims, StringList};
|
||||
pub use jwks::key_store_manager::{Refresh, RefreshStrategy};
|
||||
pub use layer::JwtAuthorizer;
|
||||
pub use validation::Validation;
|
||||
|
||||
pub mod authorizer;
|
||||
pub mod claims;
|
||||
pub mod error;
|
||||
pub mod jwks;
|
||||
pub mod layer;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue