use josekit::{
jwk::Jwk as JosekitJwk,
jws::JwsHeader,
jwt::{self, JwtPayload},
};
use serde_json::json;
use std::collections::HashMap;
use std::sync::Arc;
use time::{Duration, OffsetDateTime};
use xjp_oidc::{Jwk, Jwks, JwtVerifier, MokaCacheImpl, ReqwestHttpClient};
fn create_test_keypair() -> (JosekitJwk, Jwk) {
let alg = josekit::jws::RS256;
let mut jwk = JosekitJwk::generate_rsa_key(2048).unwrap();
jwk.set_key_id("resource-key-1");
jwk.set_algorithm(alg.name());
jwk.set_key_use("sig");
let public_jwk = Jwk {
kty: "RSA".to_string(),
use_: "sig".to_string(),
kid: "resource-key-1".to_string(),
alg: Some("RS256".to_string()),
n: Some(jwk.parameter("n").unwrap().as_str().unwrap().to_string()),
e: Some(jwk.parameter("e").unwrap().as_str().unwrap().to_string()),
x: None,
y: None,
crv: None,
};
(jwk, public_jwk)
}
fn create_signed_token(jwk: &JosekitJwk, claims: serde_json::Value) -> String {
let mut header = JwsHeader::new();
header.set_token_type("JWT");
header.set_algorithm("RS256");
header.set_key_id("resource-key-1");
let payload = JwtPayload::from_map(claims.as_object().unwrap().clone()).unwrap();
let signer = josekit::jws::RS256.signer_from_jwk(jwk).unwrap();
jwt::encode_with_signer(&payload, &header, &signer).unwrap()
}
#[tokio::test]
#[cfg(feature = "verifier")]
async fn test_jwt_verifier_basic() {
let mut server = mockito::Server::new_async().await;
let mock_issuer = server.url();
let (private_key, public_key) = create_test_keypair();
let discovery_metadata = serde_json::json!({
"issuer": mock_issuer,
"authorization_endpoint": format!("{}/authorize", mock_issuer),
"token_endpoint": format!("{}/token", mock_issuer),
"jwks_uri": format!("{}/jwks", mock_issuer),
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["client_secret_basic"],
"id_token_signing_alg_values_supported": ["RS256"],
});
let _discovery_mock = server
.mock("GET", "/.well-known/openid-configuration")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(discovery_metadata.to_string())
.create_async()
.await;
let jwks = Jwks { keys: vec![public_key] };
let _jwks_mock = server
.mock("GET", "/jwks")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&jwks).unwrap())
.expect(1) .create_async()
.await;
let http_client = Arc::new(ReqwestHttpClient::default());
let cache = Arc::new(MokaCacheImpl::new(100));
let mut issuer_map = HashMap::new();
issuer_map.insert("test".to_string(), mock_issuer.clone());
let verifier = JwtVerifier::new(issuer_map, "test-audience".to_string(), http_client, cache);
let now = OffsetDateTime::now_utc();
let claims = json!({
"iss": mock_issuer,
"sub": "user123",
"aud": "test-audience",
"exp": (now + Duration::hours(1)).unix_timestamp(),
"iat": now.unix_timestamp(),
"scope": "read write",
"client_id": "test-client",
});
let access_token = create_signed_token(&private_key, claims);
let result = verifier.verify(&access_token).await;
assert!(result.is_ok(), "Verification failed: {:?}", result.err());
let verified = result.unwrap();
assert_eq!(verified.iss, mock_issuer);
assert_eq!(verified.sub, "user123");
assert_eq!(verified.aud, "test-audience");
assert_eq!(verified.scope.as_ref().unwrap(), "read write");
}
#[tokio::test]
#[cfg(feature = "verifier")]
async fn test_jwt_verifier_multiple_audiences() {
let mut server = mockito::Server::new_async().await;
let mock_issuer = server.url();
let (private_key, public_key) = create_test_keypair();
let discovery_metadata = serde_json::json!({
"issuer": mock_issuer,
"authorization_endpoint": format!("{}/authorize", mock_issuer),
"token_endpoint": format!("{}/token", mock_issuer),
"jwks_uri": format!("{}/jwks", mock_issuer),
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["client_secret_basic"],
"id_token_signing_alg_values_supported": ["RS256"],
});
let _discovery_mock = server
.mock("GET", "/.well-known/openid-configuration")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(discovery_metadata.to_string())
.create_async()
.await;
let jwks = Jwks { keys: vec![public_key] };
let _jwks_mock = server
.mock("GET", "/jwks")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&jwks).unwrap())
.create_async()
.await;
let http_client = Arc::new(ReqwestHttpClient::default());
let cache = Arc::new(MokaCacheImpl::new(100));
let mut issuer_map = HashMap::new();
issuer_map.insert("test".to_string(), mock_issuer.clone());
let verifier = JwtVerifier::new(
issuer_map,
"api2".to_string(), http_client,
cache,
);
let now = OffsetDateTime::now_utc();
let claims = json!({
"iss": mock_issuer,
"sub": "user456",
"aud": "api2",
"exp": (now + Duration::hours(1)).unix_timestamp(),
"iat": now.unix_timestamp(),
});
let access_token = create_signed_token(&private_key, claims);
let result = verifier.verify(&access_token).await;
assert!(result.is_ok(), "Verification failed: {:?}", result.err());
}
#[tokio::test]
#[cfg(feature = "verifier")]
async fn test_jwt_verifier_expired_token() {
let mut server = mockito::Server::new_async().await;
let mock_issuer = server.url();
let (private_key, public_key) = create_test_keypair();
let discovery_metadata = serde_json::json!({
"issuer": mock_issuer,
"authorization_endpoint": format!("{}/authorize", mock_issuer),
"token_endpoint": format!("{}/token", mock_issuer),
"jwks_uri": format!("{}/jwks", mock_issuer),
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["client_secret_basic"],
"id_token_signing_alg_values_supported": ["RS256"],
});
let _discovery_mock = server
.mock("GET", "/.well-known/openid-configuration")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(discovery_metadata.to_string())
.create_async()
.await;
let jwks = Jwks { keys: vec![public_key] };
let _jwks_mock = server
.mock("GET", "/jwks")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&jwks).unwrap())
.create_async()
.await;
let http_client = Arc::new(ReqwestHttpClient::default());
let cache = Arc::new(MokaCacheImpl::new(100));
let mut issuer_map = HashMap::new();
issuer_map.insert("test".to_string(), mock_issuer.clone());
let verifier = JwtVerifier::new(issuer_map, "test-api".to_string(), http_client, cache);
let now = OffsetDateTime::now_utc();
let claims = json!({
"iss": mock_issuer,
"sub": "user789",
"aud": "test-api",
"exp": (now - Duration::hours(1)).unix_timestamp(), "iat": (now - Duration::hours(2)).unix_timestamp(),
});
let access_token = create_signed_token(&private_key, claims);
let result = verifier.verify(&access_token).await;
assert!(result.is_err());
}
#[tokio::test]
#[cfg(feature = "verifier")]
async fn test_jwt_verifier_wrong_issuer() {
let mut server = mockito::Server::new_async().await;
let mock_issuer = server.url();
let (private_key, public_key) = create_test_keypair();
let discovery_metadata = serde_json::json!({
"issuer": mock_issuer,
"authorization_endpoint": format!("{}/authorize", mock_issuer),
"token_endpoint": format!("{}/token", mock_issuer),
"jwks_uri": format!("{}/jwks", mock_issuer),
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["client_secret_basic"],
"id_token_signing_alg_values_supported": ["RS256"],
});
let _discovery_mock = server
.mock("GET", "/.well-known/openid-configuration")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(discovery_metadata.to_string())
.create_async()
.await;
let jwks = Jwks { keys: vec![public_key] };
let _jwks_mock = server
.mock("GET", "/jwks")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&jwks).unwrap())
.create_async()
.await;
let http_client = Arc::new(ReqwestHttpClient::default());
let cache = Arc::new(MokaCacheImpl::new(100));
let mut issuer_map = HashMap::new();
issuer_map.insert("test".to_string(), mock_issuer.clone());
let verifier = JwtVerifier::new(issuer_map, "test-api".to_string(), http_client, cache);
let now = OffsetDateTime::now_utc();
let claims = json!({
"iss": "https://other-issuer.com",
"sub": "user999",
"aud": "test-api",
"exp": (now + Duration::hours(1)).unix_timestamp(),
"iat": now.unix_timestamp(),
});
let access_token = create_signed_token(&private_key, claims);
let result = verifier.verify(&access_token).await;
assert!(result.is_err());
}
#[tokio::test]
#[cfg(feature = "verifier")]
async fn test_jwt_verifier_caching() {
let mut server = mockito::Server::new_async().await;
let mock_issuer = server.url();
let (private_key, public_key) = create_test_keypair();
let discovery_metadata = serde_json::json!({
"issuer": mock_issuer,
"authorization_endpoint": format!("{}/authorize", mock_issuer),
"token_endpoint": format!("{}/token", mock_issuer),
"jwks_uri": format!("{}/jwks", mock_issuer),
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["client_secret_basic"],
"id_token_signing_alg_values_supported": ["RS256"],
});
let _discovery_mock = server
.mock("GET", "/.well-known/openid-configuration")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(discovery_metadata.to_string())
.create_async()
.await;
let jwks = Jwks { keys: vec![public_key] };
let _jwks_mock = server
.mock("GET", "/jwks")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&jwks).unwrap())
.expect(1)
.create_async()
.await;
let http_client = Arc::new(ReqwestHttpClient::default());
let cache = Arc::new(MokaCacheImpl::new(100));
let mut issuer_map = HashMap::new();
issuer_map.insert("test".to_string(), mock_issuer.clone());
let verifier = JwtVerifier::new(issuer_map, "cached-api".to_string(), http_client, cache);
let now = OffsetDateTime::now_utc();
for i in 0..5 {
let claims = json!({
"iss": mock_issuer,
"sub": format!("user{}", i),
"aud": "cached-api",
"exp": (now + Duration::hours(1)).unix_timestamp(),
"iat": now.unix_timestamp(),
});
let token = create_signed_token(&private_key, claims);
let result = verifier.verify(&token).await;
assert!(result.is_ok());
}
}