pub struct Verifier { /* private fields */ }Expand description
Challenges + verifies incoming requests against one configured
Credentials (RFC 7235 origin-side auth) — see the module docs.
Implementations§
Source§impl Verifier
impl Verifier
Sourcepub fn new(credentials: Credentials, realm: impl Into<String>) -> Self
pub fn new(credentials: Credentials, realm: impl Into<String>) -> Self
Builds a verifier for credentials, using realm for the
WWW-Authenticate challenge (Basic/Digest only — RFC 6750 Bearer has
no realm parameter in this crate’s minimal challenge, see
Self::challenge).
For Credentials::Digest, a fresh random server nonce is generated
now and held for this verifier’s whole lifetime (see the module
docs’ nonce-handling caveat).
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}
80
81/// Bearer (RFC 6750): no challenge round-trip needed, but still an
82/// accept/reject pair — a wrong token must not verify.
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}Sourcepub fn forwarded(
user_header: impl Into<String>,
forwarded_for_header: Option<String>,
) -> Self
pub fn forwarded( user_header: impl Into<String>, forwarded_for_header: Option<String>, ) -> Self
Builds a verifier for the reverse-proxy forwarded-auth scheme (see the module docs’ trust assumption — read it before using this).
user_header (conventionally X-Forwarded-User) is the header whose
presence (non-empty) Self::verify treats as “the proxy already
authenticated this caller”. forwarded_for_header (conventionally
Some("X-Forwarded-For".to_string())), if configured, is read back by
Self::forwarded_for for observability only — this crate makes no
trust decision based on it.
Examples found in repository?
103fn forwarded() {
104 let verifier = Verifier::forwarded("X-Forwarded-User", Some("X-Forwarded-For".to_string()));
105 println!("[forwarded] challenge: {}", verifier.challenge());
106
107 let headers: &[(&str, &str)] = &[("X-Forwarded-User", "alice")];
108 let ctx = RequestContext::new("GET", "/stream/media.m3u8").with_headers(headers);
109 assert_eq!(verifier.verify(&ctx), AuthResult::Ok);
110 println!("[forwarded] header present -> Ok");
111
112 let ctx_no_header = RequestContext::new("GET", "/stream/media.m3u8");
113 assert_eq!(verifier.verify(&ctx_no_header), AuthResult::Unauthorized);
114 println!("[forwarded] header absent -> Unauthorized");
115}Sourcepub fn challenge(&self) -> String
pub fn challenge(&self) -> String
The WWW-Authenticate header value to send on a 401 in response to
a missing/failed Self::verify call.
Forwarded (built via Self::forwarded) has no real RFC 7235
challenge (a direct client cannot answer it — see the module docs);
this just names the scheme for diagnostics.
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}
80
81/// Bearer (RFC 6750): no challenge round-trip needed, but still an
82/// accept/reject pair — a wrong token must not verify.
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}
97
98/// Reverse-proxy forwarded-auth (`Verifier::forwarded`): no credential at
99/// all — authenticated iff the proxy-set user header is present and
100/// non-empty. See `Verifier::forwarded`'s doc for the trust assumption this
101/// scheme relies on (only safe behind a proxy that strips client-supplied
102/// copies of the header).
103fn forwarded() {
104 let verifier = Verifier::forwarded("X-Forwarded-User", Some("X-Forwarded-For".to_string()));
105 println!("[forwarded] challenge: {}", verifier.challenge());
106
107 let headers: &[(&str, &str)] = &[("X-Forwarded-User", "alice")];
108 let ctx = RequestContext::new("GET", "/stream/media.m3u8").with_headers(headers);
109 assert_eq!(verifier.verify(&ctx), AuthResult::Ok);
110 println!("[forwarded] header present -> Ok");
111
112 let ctx_no_header = RequestContext::new("GET", "/stream/media.m3u8");
113 assert_eq!(verifier.verify(&ctx_no_header), AuthResult::Unauthorized);
114 println!("[forwarded] header absent -> Unauthorized");
115}Sourcepub fn verify(&self, ctx: &RequestContext<'_>) -> AuthResult
pub fn verify(&self, ctx: &RequestContext<'_>) -> AuthResult
Verifies an incoming request against this verifier’s configured scheme.
Basic/Digest/Bearer read ctx’s Authorization header
(RequestContext::header, case-insensitive) — missing entirely is
Unauthorized, same as before this took a full RequestContext.
ctx.method feeds Digest’s HA2 directly; ctx.uri is the request
URI the client’s claimed uri field is matched against (RFC 7616
§3.4.1’s SHOULD, accepting either origin-form or absolute-form —
unused for Basic/Bearer.
Forwarded reads ctx’s configured user header instead — see the
module docs.
A pathologically large Digest Authorization header is rejected
outright rather than parsed (see MAX_DIGEST_FIELDS) — this bounds
the per-request allocation cost, but is not a substitute for a
transport-level cap on header size, which callers should also enforce.
Examples found in repository?
103fn forwarded() {
104 let verifier = Verifier::forwarded("X-Forwarded-User", Some("X-Forwarded-For".to_string()));
105 println!("[forwarded] challenge: {}", verifier.challenge());
106
107 let headers: &[(&str, &str)] = &[("X-Forwarded-User", "alice")];
108 let ctx = RequestContext::new("GET", "/stream/media.m3u8").with_headers(headers);
109 assert_eq!(verifier.verify(&ctx), AuthResult::Ok);
110 println!("[forwarded] header present -> Ok");
111
112 let ctx_no_header = RequestContext::new("GET", "/stream/media.m3u8");
113 assert_eq!(verifier.verify(&ctx_no_header), AuthResult::Unauthorized);
114 println!("[forwarded] header absent -> Unauthorized");
115}
116
117/// Builds a request context carrying `authorization` as the `Authorization`
118/// header, then verifies it against `verifier`.
119fn verify(verifier: &Verifier, authorization: &str, ctx: &RequestContext<'_>) -> AuthResult {
120 let headers: &[(&str, &str)] = &[("authorization", authorization)];
121 let ctx_with_auth = RequestContext::new(ctx.method, ctx.uri).with_headers(headers);
122 verifier.verify(&ctx_with_auth)
123}Sourcepub fn forwarded_for<'a>(&self, ctx: &RequestContext<'a>) -> Option<&'a str>
pub fn forwarded_for<'a>(&self, ctx: &RequestContext<'a>) -> Option<&'a str>
For a Self::forwarded verifier with a configured
forwarded_for_header, returns that header’s value from ctx — for
tracing/observability only; this crate makes no trust decision with
it (the module docs’ trust assumption is what actually matters).
None for any other verifier, or when no such header is
configured/present in ctx.