server/lib/tests/a34_oidc_bearer_never_falls_through.rs
//! ★ **A34-1, at holger's own door** (the identity review, 2026-10-26): a
//! JWT that holger cannot verify is REFUSED. Before nornir-auth-oidc 0.2 a
//! token whose key fetch failed went to a guessed `{issuer}/userinfo`, which
//! checks neither issuer nor audience, so a token minted for ANY app of the
//! tenant authenticated to holger.
//!
//! This drives the real dispatch holger uses (`nornir_auth_policy::validate_request`
//! over the RON `auth` block) against an in-process IdP that answers
//! `/userinfo` for anything, with a `jwks_uri` that yields no keys.
#![cfg(all(feature = "auth-oidc", feature = "online"))]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use nornir_auth_oidc::test_support;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
/// An IdP whose `/userinfo` says `sub=alice` to ANY bearer, counting hits.
async fn permissive_userinfo(hits: Arc<AtomicUsize>) -> String {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
while let Ok((mut sock, _)) = listener.accept().await {
hits.fetch_add(1, Ordering::SeqCst);
let mut buf = [0u8; 2048];
let _ = sock.read(&mut buf).await;
let body = br#"{"sub":"alice"}"#;
let head = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
let _ = sock.write_all(head.as_bytes()).await;
let _ = sock.write_all(body).await;
}
});
format!("http://{addr}")
}
fn auth_block(issuer: &str) -> nornir_auth_policy::AuthConfig {
let ron = format!(
r#"(
methods: [ Oidc(
issuer_url: "{issuer}",
audience: Some("holger"),
jwks_uri: Some("http://127.0.0.1:1/jwks"),
) ],
require_auth_for_reads: true,
)"#
);
ron::from_str(&ron).expect("the auth block parses")
}
#[tokio::test]
async fn a34_1_a_token_for_another_app_never_authenticates_through_userinfo() {
let hits = Arc::new(AtomicUsize::new(0));
let issuer = permissive_userinfo(hits.clone()).await;
let cfg = auth_block(&issuer);
for (what, token) in [
(
"another audience",
test_support::token_for(&issuer, "another-app", "alice", 3600),
),
(
"another issuer",
test_support::token_for("https://evil.example", "holger", "alice", 3600),
),
(
"cold key cache",
test_support::token_for(&issuer, "holger", "alice", 3600),
),
("opaque", "opaque-token-a34".to_owned()),
] {
let got = nornir_auth_policy::validate_request(&cfg, Some(&token), None).await;
assert!(
!matches!(got, Ok(Some(_))),
"{what}: holger authenticated a token it could not verify: {:?}",
got.map(|o| o.map(|i| i.subject))
);
}
assert_eq!(
hits.load(Ordering::SeqCst),
0,
"the guessed /userinfo was called"
);
}