#[non_exhaustive]pub enum Credentials {
Basic {
username: String,
password: String,
},
Digest {
username: String,
password: String,
},
Bearer {
token: String,
},
}Expand description
Credentials for one of the supported auth schemes.
Basic and Digest both carry a username/password and are treated
identically by crate::respond/crate::Authenticator: which wire
scheme is actually used is decided by the server’s challenge, not by which
variant was constructed (RFC 7235 content negotiation — a server may offer
either, or both, in WWW-Authenticate). Use Credentials::new for the
common password case; construct Basic/Digest directly only when the
caller must pin one scheme.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Basic
Username/password for HTTP Basic auth (RFC 7617) — or RTSP’s reuse of it (RFC 2326 §14/§16).
Digest
Username/password for HTTP Digest auth (RFC 7616) — or RTSP’s reuse of it (RFC 2326 §14).
Bearer
A bearer token (RFC 6750) — sent verbatim as Authorization: Bearer <token> with no challenge round-trip required.
Implementations§
Source§impl Credentials
impl Credentials
Sourcepub fn new(username: impl Into<String>, password: impl Into<String>) -> Self
pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self
Convenience constructor for the common password-based case.
Does not commit to Basic or Digest: crate::respond/
crate::Authenticator::from_challenge answer whichever scheme the
server’s WWW-Authenticate challenge advertises. Internally this
builds a Digest value (the superset — http-auth’s challenge
parser inspects the challenge itself, not this variant, to pick the
wire scheme), so new("u", "p") behaves identically to a
hand-constructed Basic/Digest with the same username/password.
Examples found in repository?
30fn digest() {
31 let verifier = Verifier::new(
32 Credentials::Digest {
33 username: "admin".into(),
34 password: "hunter2".into(),
35 },
36 REALM,
37 );
38 let challenge = verifier.challenge();
39 println!("[digest] challenge: {challenge}");
40
41 let ctx = RequestContext::new("DESCRIBE", "rtsp://cam/live");
42
43 // Correct credential: respond() answers the challenge, verify() accepts.
44 let correct = respond(&challenge, &ctx, Credentials::new("admin", "hunter2"))
45 .expect("respond computes an Authorization value");
46 let outcome = verify(&verifier, &correct, &ctx);
47 println!("[digest] correct password -> {outcome:?}");
48 assert_eq!(outcome, AuthResult::Ok);
49
50 // Wrong credential: same challenge, wrong password -> rejected.
51 let wrong = respond(&challenge, &ctx, Credentials::new("admin", "WRONG"))
52 .expect("respond computes an Authorization value even for a wrong password");
53 let outcome = verify(&verifier, &wrong, &ctx);
54 println!("[digest] wrong password -> {outcome:?}");
55 assert_eq!(outcome, AuthResult::Unauthorized);
56}
57
58/// Basic (RFC 7617): same accept/reject shape, briefly.
59fn basic() {
60 let verifier = Verifier::new(
61 Credentials::Basic {
62 username: "admin".into(),
63 password: "hunter2".into(),
64 },
65 REALM,
66 );
67 let challenge = verifier.challenge();
68 println!("[basic] challenge: {challenge}");
69
70 let ctx = RequestContext::new("GET", "/stream/media.m3u8");
71 let correct =
72 respond(&challenge, &ctx, Credentials::new("admin", "hunter2")).expect("responds");
73 assert_eq!(verify(&verifier, &correct, &ctx), AuthResult::Ok);
74 println!("[basic] correct password -> Ok");
75
76 let wrong = respond(&challenge, &ctx, Credentials::new("admin", "WRONG")).expect("responds");
77 assert_eq!(verify(&verifier, &wrong, &ctx), AuthResult::Unauthorized);
78 println!("[basic] wrong password -> Unauthorized");
79}More examples
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}Sourcepub fn bearer(token: impl Into<String>) -> Self
pub fn bearer(token: impl Into<String>) -> Self
Constructs a bearer-token credential (RFC 6750).
Examples found in repository?
83fn bearer() {
84 let verifier = Verifier::new(Credentials::bearer("right-token"), REALM);
85 let challenge = verifier.challenge();
86 println!("[bearer] challenge: {challenge}");
87
88 let ctx = RequestContext::new("GET", "/stream/media.m3u8");
89 let correct = respond(&challenge, &ctx, Credentials::bearer("right-token")).expect("responds");
90 assert_eq!(verify(&verifier, &correct, &ctx), AuthResult::Ok);
91 println!("[bearer] correct token -> Ok");
92
93 let wrong = respond(&challenge, &ctx, Credentials::bearer("wrong-token")).expect("responds");
94 assert_eq!(verify(&verifier, &wrong, &ctx), AuthResult::Unauthorized);
95 println!("[bearer] wrong token -> Unauthorized");
96}More examples
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}Trait Implementations§
Source§impl Clone for Credentials
impl Clone for Credentials
Source§fn clone(&self) -> Credentials
fn clone(&self) -> Credentials
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for Credentials
Manual Debug (rather than #[derive(Debug)]): every scheme carries a
secret (password/token) that must never render verbatim. Usernames are
not secret and are shown as-is to keep the output useful for diagnostics.
impl Debug for Credentials
Manual Debug (rather than #[derive(Debug)]): every scheme carries a
secret (password/token) that must never render verbatim. Usernames are
not secret and are shown as-is to keep the output useful for diagnostics.