Skip to content

Commit 8121cb0

Browse files
committed
feat: MSRV 1.85, Edition 2024, Remove async-trait
1 parent f46ccff commit 8121cb0

9 files changed

Lines changed: 68 additions & 58 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
[package]
22
name = "axum-jwt-auth"
33
version = "0.6.0"
4-
edition = "2021"
4+
edition = "2024"
5+
rust-version = "1.85"
56
authors = ["Cole MacKenzie"]
67
description = "A simple JWT authentication middleware for Axum"
78
license = "MIT"
89
repository = "https://github.com/cmackenzie1/axum-jwt-auth"
910

1011
[dependencies]
11-
async-trait = "0.1"
1212
axum = { version = "0.8", features = ["macros"] }
1313
axum-extra = { version = "0.12", features = ["typed-header", "cookie"] }
1414
dashmap = "6.1.0"

examples/cloudflare/cloudflare.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use std::sync::Arc;
22

33
use axum::{
4+
Json, Router,
45
extract::FromRef,
56
response::{IntoResponse, Response},
67
routing::get,
7-
Json, Router,
88
};
99
use axum_jwt_auth::{
10-
define_cookie_extractor, define_header_extractor, Claims, CookieTokenExtractor,
11-
HeaderTokenExtractor, JwtDecoderState, RemoteJwksDecoder,
10+
Claims, CookieTokenExtractor, HeaderTokenExtractor, JwtDecoderState, RemoteJwksDecoder,
11+
define_cookie_extractor, define_header_extractor,
1212
};
1313
use jsonwebtoken::{Algorithm, Validation};
1414
use serde::{Deserialize, Serialize};

examples/local/local.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use std::sync::Arc;
22

33
use axum::{
4+
Json, Router,
45
extract::{FromRef, State},
56
response::{IntoResponse, Response},
67
routing::{get, post},
7-
Json, Router,
88
};
99
use axum_jwt_auth::{Claims, JwtDecoderState, LocalDecoder};
1010
use chrono::{Duration, Utc};
11-
use jsonwebtoken::{encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
11+
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, encode};
1212
use serde::{Deserialize, Serialize};
1313

1414
#[derive(Debug, Serialize, Deserialize, Clone)]

examples/remote/remote.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::sync::Arc;
22

3-
use axum::{extract::FromRef, routing::get, Json, Router};
3+
use axum::{Json, Router, extract::FromRef, routing::get};
44
use axum_jwt_auth::{Claims, JwtDecoderState, RemoteJwksDecoder};
55
use jsonwebtoken::{Algorithm, EncodingKey, Header, Validation};
66
use serde::{Deserialize, Serialize};
7-
use serde_json::{json, Value};
7+
use serde_json::{Value, json};
88

99
#[derive(Debug, Serialize, Deserialize, Clone)]
1010
struct CustomClaims {

src/axum.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
use std::marker::PhantomData;
22

3-
use async_trait::async_trait;
3+
use axum::RequestPartsExt;
44
use axum::extract::FromRef;
5-
use axum::http::{header::HeaderName, StatusCode};
5+
use axum::http::{StatusCode, header::HeaderName};
66
use axum::response::Response;
7-
use axum::RequestPartsExt;
87
use axum::{http::request::Parts, response::IntoResponse};
8+
use axum_extra::TypedHeader;
99
use axum_extra::headers::authorization::Bearer;
1010
use axum_extra::headers::{Authorization, Cookie};
11-
use axum_extra::TypedHeader;
1211
use jsonwebtoken::errors::ErrorKind;
1312
use serde::de::DeserializeOwned;
1413

@@ -50,20 +49,20 @@ pub struct Claims<T, E = BearerTokenExtractor> {
5049
///
5150
/// Implement this trait to define custom token extraction strategies.
5251
/// The library provides implementations for common sources via the extractor macros.
53-
#[async_trait]
54-
pub trait TokenExtractor {
52+
pub trait TokenExtractor: Send + Sync {
5553
/// Extracts a JWT token string from the request parts.
5654
///
5755
/// Returns `AuthError::MissingToken` if the token cannot be found or extracted.
58-
async fn extract_token(parts: &mut Parts) -> Result<String, AuthError>;
56+
fn extract_token(
57+
parts: &mut Parts,
58+
) -> impl std::future::Future<Output = Result<String, AuthError>> + Send;
5959
}
6060

6161
/// Extracts JWT tokens from the `Authorization: Bearer <token>` header.
6262
///
6363
/// This is the default extractor used by `Claims<T>` when no extractor is specified.
6464
pub struct BearerTokenExtractor;
6565

66-
#[async_trait]
6766
impl TokenExtractor for BearerTokenExtractor {
6867
async fn extract_token(parts: &mut Parts) -> Result<String, AuthError> {
6968
let auth: TypedHeader<Authorization<Bearer>> =
@@ -77,7 +76,7 @@ impl TokenExtractor for BearerTokenExtractor {
7776
///
7877
/// Implement this trait to specify custom header names or cookie names
7978
/// Typically used with the `define_*_extractor!` macros rather than implemented manually.
80-
pub trait ExtractorConfig {
79+
pub trait ExtractorConfig: Send + Sync {
8180
/// Returns the header name or cookie name to extract from.
8281
fn value() -> &'static str;
8382
}
@@ -147,7 +146,6 @@ macro_rules! define_cookie_extractor {
147146
/// ```
148147
pub struct HeaderTokenExtractor<C: ExtractorConfig>(PhantomData<C>);
149148

150-
#[async_trait]
151149
impl<C: ExtractorConfig> TokenExtractor for HeaderTokenExtractor<C> {
152150
async fn extract_token(parts: &mut Parts) -> Result<String, AuthError> {
153151
let header_name = HeaderName::from_static(C::value());
@@ -176,7 +174,6 @@ impl<C: ExtractorConfig> TokenExtractor for HeaderTokenExtractor<C> {
176174
/// ```
177175
pub struct CookieTokenExtractor<C: ExtractorConfig>(PhantomData<C>);
178176

179-
#[async_trait]
180177
impl<C: ExtractorConfig> TokenExtractor for CookieTokenExtractor<C> {
181178
async fn extract_token(parts: &mut Parts) -> Result<String, AuthError> {
182179
let cookies: TypedHeader<Cookie> =

src/lib.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ mod axum;
6060
mod local;
6161
mod remote;
6262

63+
use std::future::Future;
64+
use std::pin::Pin;
6365
use std::sync::Arc;
6466

65-
use async_trait::async_trait;
6667
use jsonwebtoken::TokenData;
6768
use serde::de::DeserializeOwned;
6869
use thiserror::Error;
@@ -110,8 +111,7 @@ pub enum Error {
110111
///
111112
/// Implemented by [`LocalDecoder`] and [`RemoteJwksDecoder`] to provide
112113
/// a unified interface for JWT validation with different key sources.
113-
#[async_trait]
114-
pub trait JwtDecoder<T>
114+
pub trait JwtDecoder<T>: Send + Sync
115115
where
116116
T: for<'de> DeserializeOwned,
117117
{
@@ -121,7 +121,10 @@ where
121121
///
122122
/// Returns an error if the token is invalid, expired, has an invalid signature,
123123
/// or if the key cannot be found (for remote decoders).
124-
async fn decode(&self, token: &str) -> Result<TokenData<T>, Error>;
124+
fn decode<'a>(
125+
&'a self,
126+
token: &'a str,
127+
) -> Pin<Box<dyn Future<Output = Result<TokenData<T>, Error>> + Send + 'a>>;
125128
}
126129

127130
/// Type alias for a thread-safe, trait-object decoder suitable for Axum state.

src/local.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use async_trait::async_trait;
21
use jsonwebtoken::{DecodingKey, TokenData, Validation};
32
use serde::de::DeserializeOwned;
43

@@ -105,25 +104,30 @@ impl LocalDecoderBuilder {
105104
}
106105
}
107106

108-
#[async_trait]
109107
impl<T> JwtDecoder<T> for LocalDecoder
110108
where
111109
T: for<'de> DeserializeOwned,
112110
{
113-
async fn decode(&self, token: &str) -> Result<TokenData<T>, Error> {
114-
// Try to decode the token with each key in the cache
115-
// If none of them work, return the error from the last one
116-
let mut last_error: Option<Error> = None;
117-
for key in self.keys.iter() {
118-
match jsonwebtoken::decode::<T>(token, key, &self.validation) {
119-
Ok(token_data) => return Ok(token_data),
120-
Err(e) => {
121-
tracing::error!("Error decoding token: {}", e);
122-
last_error = Some(Error::Jwt(e));
111+
fn decode<'a>(
112+
&'a self,
113+
token: &'a str,
114+
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<TokenData<T>, Error>> + Send + 'a>>
115+
{
116+
Box::pin(async move {
117+
// Try to decode the token with each key in the cache
118+
// If none of them work, return the error from the last one
119+
let mut last_error: Option<Error> = None;
120+
for key in self.keys.iter() {
121+
match jsonwebtoken::decode::<T>(token, key, &self.validation) {
122+
Ok(token_data) => return Ok(token_data),
123+
Err(e) => {
124+
tracing::error!("Error decoding token: {}", e);
125+
last_error = Some(Error::Jwt(e));
126+
}
123127
}
124128
}
125-
}
126129

127-
Err(last_error.unwrap_or_else(|| Error::Configuration("No keys available".into())))
130+
Err(last_error.unwrap_or_else(|| Error::Configuration("No keys available".into())))
131+
})
128132
}
129133
}

src/remote.rs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use std::sync::Arc;
22

3-
use async_trait::async_trait;
43
use dashmap::DashMap;
5-
use jsonwebtoken::{jwk::JwkSet, DecodingKey, TokenData, Validation};
4+
use jsonwebtoken::{DecodingKey, TokenData, Validation, jwk::JwkSet};
65
use serde::de::DeserializeOwned;
76
use tokio::sync::Notify;
87

@@ -340,26 +339,33 @@ impl Default for RemoteJwksDecoderBuilder {
340339
}
341340
}
342341

343-
#[async_trait]
344342
impl<T> JwtDecoder<T> for RemoteJwksDecoder
345343
where
346344
T: for<'de> DeserializeOwned,
347345
{
348-
async fn decode(&self, token: &str) -> Result<TokenData<T>, Error> {
349-
self.ensure_initialized().await;
350-
let header = jsonwebtoken::decode_header(token)?;
351-
let target_kid = header.kid;
352-
353-
if let Some(ref kid) = target_kid {
354-
if let Some(key) = self.keys_cache.get(kid) {
355-
return Ok(jsonwebtoken::decode::<T>(
356-
token,
357-
key.value(),
358-
&self.validation,
359-
)?);
346+
fn decode<'a>(
347+
&'a self,
348+
token: &'a str,
349+
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<TokenData<T>, Error>> + Send + 'a>>
350+
{
351+
Box::pin(async move {
352+
self.ensure_initialized().await;
353+
let header = jsonwebtoken::decode_header(token)?;
354+
let target_kid = header.kid;
355+
356+
if let Some(ref kid) = target_kid {
357+
if let Some(key) = self.keys_cache.get(kid) {
358+
Ok(jsonwebtoken::decode::<T>(
359+
token,
360+
key.value(),
361+
&self.validation,
362+
)?)
363+
} else {
364+
Err(Error::KeyNotFound(Some(kid.clone())))
365+
}
366+
} else {
367+
Err(Error::KeyNotFound(None))
360368
}
361-
return Err(Error::KeyNotFound(Some(kid.clone())));
362-
}
363-
return Err(Error::KeyNotFound(None));
369+
})
364370
}
365371
}

tests/integration_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
use std::sync::Arc;
22

33
use axum::{
4+
Json, Router,
45
extract::FromRef,
56
response::IntoResponse,
67
routing::{get, post},
7-
Json, Router,
88
};
99

1010
use axum_jwt_auth::{
1111
Claims, Decoder, JwtDecoder, JwtDecoderState, LocalDecoder, RemoteJwksDecoder,
1212
RemoteJwksDecoderConfig,
1313
};
1414
use chrono::{Duration, Utc};
15-
use jsonwebtoken::{encode, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
15+
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation, encode};
1616
use serde::{Deserialize, Serialize};
1717
use serde_json::json;
1818

0 commit comments

Comments
 (0)