-
Notifications
You must be signed in to change notification settings - Fork 232
JWT unit tests #581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JWT unit tests #581
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds comprehensive unit tests for JWT validation and authentication functionality in the agent gateway. The tests verify JWT claim validation, different authentication modes (Strict, Permissive, Optional), and backend authentication passthrough behavior.
Key Changes
- Added unit tests for JWT claim validation covering various rejection scenarios (invalid audience, issuer, expiration, missing/unknown key IDs)
- Added tests for JWT application behavior across different modes (Strict, Permissive, Optional)
- Added test for backend authentication passthrough functionality
- Added helper method to expose ProxyInputs for testing purposes
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| crates/agentgateway/src/test_helpers/proxymock.rs | Added inputs() helper method to expose ProxyInputs for test assertions |
| crates/agentgateway/src/http/jwt_tests.rs | Added comprehensive JWT validation and mode-specific behavior tests |
| crates/agentgateway/src/http/auth_tests.rs | Added test for backend authentication passthrough with JWT claims |
| crates/agentgateway/src/http/auth.rs | Added test module declaration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn build_unsigned_token(kid: &str, iss: &str, aud: &str, exp: u64) -> String { | ||
| use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; | ||
| let header = json!({ "alg": "ES256", "kid": kid }); | ||
| let payload = json!({ "iss": iss, "aud": aud, "exp": exp }); | ||
| let h = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header).unwrap()); | ||
| let p = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&payload).unwrap()); | ||
| let s = URL_SAFE_NO_PAD.encode(b"sig"); | ||
| format!("{h}.{p}.{s}") | ||
| } | ||
|
|
||
| fn build_unsigned_token_without_kid(iss: &str, aud: &str, exp: u64) -> String { | ||
| use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; | ||
| let header = json!({ "alg": "ES256" }); | ||
| let payload = json!({ "iss": iss, "aud": aud, "exp": exp }); | ||
| let h = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header).unwrap()); | ||
| let p = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&payload).unwrap()); | ||
| let s = URL_SAFE_NO_PAD.encode(b"sig"); | ||
| format!("{h}.{p}.{s}") |
Copilot
AI
Oct 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] These two helper functions contain duplicated logic for building JWT tokens. Consider extracting the common encoding logic into a single function that takes an optional kid parameter, or have build_unsigned_token_without_kid delegate to build_unsigned_token with a default kid value.
| fn build_unsigned_token(kid: &str, iss: &str, aud: &str, exp: u64) -> String { | |
| use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; | |
| let header = json!({ "alg": "ES256", "kid": kid }); | |
| let payload = json!({ "iss": iss, "aud": aud, "exp": exp }); | |
| let h = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header).unwrap()); | |
| let p = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&payload).unwrap()); | |
| let s = URL_SAFE_NO_PAD.encode(b"sig"); | |
| format!("{h}.{p}.{s}") | |
| } | |
| fn build_unsigned_token_without_kid(iss: &str, aud: &str, exp: u64) -> String { | |
| use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; | |
| let header = json!({ "alg": "ES256" }); | |
| let payload = json!({ "iss": iss, "aud": aud, "exp": exp }); | |
| let h = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header).unwrap()); | |
| let p = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&payload).unwrap()); | |
| let s = URL_SAFE_NO_PAD.encode(b"sig"); | |
| format!("{h}.{p}.{s}") | |
| fn build_unsigned_token_common(kid: Option<&str>, iss: &str, aud: &str, exp: u64) -> String { | |
| use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; | |
| let mut header = serde_json::Map::new(); | |
| header.insert("alg".to_string(), json!("ES256")); | |
| if let Some(kid) = kid { | |
| header.insert("kid".to_string(), json!(kid)); | |
| } | |
| let payload = json!({ "iss": iss, "aud": aud, "exp": exp }); | |
| let h = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header).unwrap()); | |
| let p = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&payload).unwrap()); | |
| let s = URL_SAFE_NO_PAD.encode(b"sig"); | |
| format!("{h}.{p}.{s}") | |
| } | |
| fn build_unsigned_token(kid: &str, iss: &str, aud: &str, exp: u64) -> String { | |
| build_unsigned_token_common(Some(kid), iss, aud, exp) | |
| } | |
| fn build_unsigned_token_without_kid(iss: &str, aud: &str, exp: u64) -> String { | |
| build_unsigned_token_common(None, iss, aud, exp) |
| // Header should remain present on failure in permissive mode | ||
| assert!(req.headers().get(crate::http::header::AUTHORIZATION).is_some()); | ||
| assert!(req.extensions().get::<super::Claims>().is_none()); | ||
| let _ = (kid, issuer, allowed_aud); // silence unused |
Copilot
AI
Oct 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Instead of explicitly silencing unused variables with a let binding, prefix the unused parameters with an underscore (e.g., _kid, _issuer, _allowed_aud) in the destructuring assignment on line 226. This is the idiomatic Rust approach for indicating intentionally unused variables.
Head branch was pushed to by a user without write access
Head branch was pushed to by a user without write access
#553
Adds unit tests for JWT.