pub struct Authenticator { /* private fields */ }Expand description
Negotiates a challenge once, then answers every subsequent request in the
session (RFC 7235 WWW-Authenticate/Authorization; RFC 2326 §14 for
RTSP; RFC 6750 for Bearer).
Construct with Authenticator::from_challenge on the first 401/407,
then call Authenticator::authorization for every outgoing request —
including the immediate retry. Call from_challenge again if the server
re-challenges with a fresh nonce (stale=true) to pick it up.
Implementations§
Source§impl Authenticator
impl Authenticator
Sourcepub fn from_challenge(
www_authenticate: &str,
credentials: Credentials,
) -> Result<Self>
pub fn from_challenge( www_authenticate: &str, credentials: Credentials, ) -> Result<Self>
Builds an authenticator from a WWW-Authenticate challenge value and
the caller’s credentials.
For Credentials::Basic/Digest, the challenge is parsed by
http_auth, which itself decides the wire scheme (Basic or Digest)
from the challenge content — not from which Credentials variant was
passed in. For Credentials::Bearer, the challenge value is not
inspected: RFC 6750 needs no challenge round-trip, so the token is
used as-is.
Examples found in repository?
18fn main() {
19 // --- Basic (RFC 7617): a one-shot respond(), no session state needed.
20 let value = respond(
21 "Basic realm=\"cameras\"",
22 &RequestContext::new("GET", "/stream/media.m3u8"),
23 Credentials::new("admin", "hunter2"),
24 )
25 .expect("Basic responds to any challenge shape");
26 println!("[basic] Authorization: {value}");
27 assert!(value.starts_with("Basic "));
28
29 // --- Digest (RFC 7616): parses the server's nonce/realm/qop out of the
30 // challenge, then computes HA1/HA2/response. Demonstrated with an
31 // Authenticator (not the one-shot respond()) since a real session reuses
32 // it across requests so the nonce count (`nc`) advances correctly.
33 let digest_challenge = "Digest realm=\"cameras\", \
34 nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", qop=\"auth\", algorithm=MD5";
35 let mut auth =
36 Authenticator::from_challenge(digest_challenge, Credentials::new("admin", "hunter2"))
37 .expect("challenge parses");
38 let value = auth
39 .authorization(&RequestContext::new(
40 "DESCRIBE",
41 "rtsp://camera.example.com/live",
42 ))
43 .expect("computes a Digest Authorization value");
44 println!("[digest] Authorization: {value}");
45 assert!(value.starts_with("Digest "));
46
47 // A second request on the same Authenticator advances `nc` — the
48 // computed value differs even though nothing else about the request
49 // changed (RFC 7616 §3.3 requires a fresh `nc` per request).
50 let second = auth
51 .authorization(&RequestContext::new(
52 "DESCRIBE",
53 "rtsp://camera.example.com/live",
54 ))
55 .expect("computes a second Digest Authorization value");
56 assert_ne!(value, second, "nc must advance across requests");
57 println!("[digest] Authorization (2nd request, nc advanced): {second}");
58
59 // --- Bearer (RFC 6750): no challenge round-trip needed at all — the
60 // challenge value is ignored, the token is sent verbatim.
61 let value = respond(
62 "ignored — Bearer needs no challenge round-trip",
63 &RequestContext::new("GET", "/stream/media.m3u8"),
64 Credentials::bearer("mytoken123"),
65 )
66 .expect("Bearer always responds");
67 println!("[bearer] Authorization: {value}");
68 assert_eq!(value, "Bearer mytoken123");
69}Computes the Authorization header value for ctx.
For Basic/Digest this advances the Digest nonce count on every call
(RFC 7616 §3.3); for Bearer it always returns Bearer <token> (RFC
6750).
Examples found in repository?
18fn main() {
19 // --- Basic (RFC 7617): a one-shot respond(), no session state needed.
20 let value = respond(
21 "Basic realm=\"cameras\"",
22 &RequestContext::new("GET", "/stream/media.m3u8"),
23 Credentials::new("admin", "hunter2"),
24 )
25 .expect("Basic responds to any challenge shape");
26 println!("[basic] Authorization: {value}");
27 assert!(value.starts_with("Basic "));
28
29 // --- Digest (RFC 7616): parses the server's nonce/realm/qop out of the
30 // challenge, then computes HA1/HA2/response. Demonstrated with an
31 // Authenticator (not the one-shot respond()) since a real session reuses
32 // it across requests so the nonce count (`nc`) advances correctly.
33 let digest_challenge = "Digest realm=\"cameras\", \
34 nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", qop=\"auth\", algorithm=MD5";
35 let mut auth =
36 Authenticator::from_challenge(digest_challenge, Credentials::new("admin", "hunter2"))
37 .expect("challenge parses");
38 let value = auth
39 .authorization(&RequestContext::new(
40 "DESCRIBE",
41 "rtsp://camera.example.com/live",
42 ))
43 .expect("computes a Digest Authorization value");
44 println!("[digest] Authorization: {value}");
45 assert!(value.starts_with("Digest "));
46
47 // A second request on the same Authenticator advances `nc` — the
48 // computed value differs even though nothing else about the request
49 // changed (RFC 7616 §3.3 requires a fresh `nc` per request).
50 let second = auth
51 .authorization(&RequestContext::new(
52 "DESCRIBE",
53 "rtsp://camera.example.com/live",
54 ))
55 .expect("computes a second Digest Authorization value");
56 assert_ne!(value, second, "nc must advance across requests");
57 println!("[digest] Authorization (2nd request, nc advanced): {second}");
58
59 // --- Bearer (RFC 6750): no challenge round-trip needed at all — the
60 // challenge value is ignored, the token is sent verbatim.
61 let value = respond(
62 "ignored — Bearer needs no challenge round-trip",
63 &RequestContext::new("GET", "/stream/media.m3u8"),
64 Credentials::bearer("mytoken123"),
65 )
66 .expect("Bearer always responds");
67 println!("[bearer] Authorization: {value}");
68 assert_eq!(value, "Bearer mytoken123");
69}