Skip to content

Conversation

@puertomontt
Copy link
Contributor

@puertomontt puertomontt commented Oct 28, 2025

#553

Adds unit tests for JWT.

  • test Strict/Optional/Permissive mode
  • JWT validation: Audience/issuer/time validation, MissingKeyId, UnknownKeyId
  • Multi-provider
  • BackendAuth::Passthrough basic test case

Copilot AI review requested due to automatic review settings October 28, 2025 16:03
@puertomontt puertomontt requested a review from a team as a code owner October 28, 2025 16:03
Copy link
Contributor

Copilot AI left a 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.

Comment on lines 110 to 127
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}")
Copy link

Copilot AI Oct 28, 2025

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.

Suggested change
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)

Copilot uses AI. Check for mistakes.
// 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
Copy link

Copilot AI Oct 28, 2025

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.

Copilot uses AI. Check for mistakes.
@howardjohn howardjohn enabled auto-merge (squash) November 6, 2025 18:32
auto-merge was automatically disabled November 6, 2025 20:42

Head branch was pushed to by a user without write access

@npolshakova npolshakova enabled auto-merge (squash) November 6, 2025 22:05
auto-merge was automatically disabled November 6, 2025 23:54

Head branch was pushed to by a user without write access

@howardjohn howardjohn enabled auto-merge (squash) November 7, 2025 16:07
@howardjohn howardjohn merged commit 9b9aa29 into agentgateway:main Nov 7, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants