@@ -7,6 +7,8 @@ the MAuth protocol, and verify the responses. Usage example:
77release any code to Production or deploy in a Client-accessible environment without getting
88approval for the full stack used through the Architecture and Security groups.
99
10+ ## Outgoing Requests
11+
1012``` no_run
1113use mauth_client::MAuthInfo;
1214use reqwest::Client;
@@ -49,9 +51,136 @@ match client.get("https://www.example.com/").send().await {
4951# }
5052` ` `
5153
54+ # # Incoming Requests
55+
5256The optional `axum-service` feature provides for a Tower Layer and Service that will
5357authenticate incoming requests via MAuth V2 or V1 and provide to the lower layers a
54- validated app_uuid from the request via the ValidatedRequestDetails struct.
58+ validated app_uuid from the request via the `ValidatedRequestDetails` struct. Note that
59+ this feature now includes a `RequiredMAuthValidationLayer`, which will reject any
60+ requests without a valid signature before they reach lower layers, and also a
61+ ` OptionalMAuthValidationLayer` , which lets all requests through, but only attaches a
62+ ` ValidatedRequestDetails` extension struct if there is a valid signature. When using this
63+ layer, it is the responsiblity of the request handler to check for the extension and
64+ reject requests that are not properly authorized.
65+
66+ Note that `ValidatedRequestDetails` implements Axum's `FromRequestParts`, so you can
67+ specify it bare in a request handler. This implementation includes returning a 401
68+ Unauthorized status code if the extension is not present. If you would like to return
69+ a different response, or respond to the lack of the extension in another way, you can
70+ use a more manual mechanism to check for the extension and decide how to proceed if it
71+ is not present.
72+
73+ # ## Examples for `RequiredMAuthValidationLayer`
74+
75+ ` ` ` no_run
76+ # async fn run_server() {
77+ use mauth_client::{
78+ axum_service::RequiredMAuthValidationLayer,
79+ validate_incoming::ValidatedRequestDetails,
80+ };
81+ use axum::{http::StatusCode, Router, routing::get, serve};
82+ use tokio::net::TcpListener;
83+
84+ // If there is not a valid mauth signature, this function will never run at all, and
85+ // the request will return an empty 401 Unauthorized
86+ async fn foo() -> StatusCode {
87+ StatusCode::OK
88+ }
89+
90+ // In addition to returning a 401 Unauthorized without running if there is not a valid
91+ // MAuth signature, this also makes the validated requesting app UUID available to
92+ // the function
93+ async fn bar(details: ValidatedRequestDetails) -> StatusCode {
94+ println!("Got a request from app with UUID: {}", details.app_uuid);
95+ StatusCode::OK
96+ }
97+
98+ // This function will run regardless of whether or not there is a mauth signature
99+ async fn baz() -> StatusCode {
100+ StatusCode::OK
101+ }
102+
103+ // Attaching the baz route handler after the layer means the layer is not run for
104+ // requests to that path, so no mauth checking will be performed for that route and
105+ // any other routes attached after the layer
106+ let router = Router::new()
107+ .route("/foo", get(foo))
108+ .route("/bar", get(bar))
109+ .layer(RequiredMAuthValidationLayer::from_default_file().unwrap())
110+ .route("/baz", get(baz));
111+ let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
112+ serve(listener, router).await.unwrap();
113+ # }
114+ ` ` `
115+
116+ # ## Examples for `OptionalMAuthValidationLayer`
117+
118+ ` ` ` no_run
119+ # async fn run_server() {
120+ use mauth_client::{
121+ axum_service::OptionalMAuthValidationLayer,
122+ validate_incoming::ValidatedRequestDetails,
123+ };
124+ use axum::{http::StatusCode, Router, routing::get, serve};
125+ use tokio::net::TcpListener;
126+
127+ // This request will run no matter what the authorization status is
128+ async fn foo() -> StatusCode {
129+ StatusCode::OK
130+ }
131+
132+ // If there is not a valid mauth signature, this function will never run at all, and
133+ // the request will return an empty 401 Unauthorized
134+ async fn bar(_: ValidatedRequestDetails) -> StatusCode {
135+ StatusCode::OK
136+ }
137+
138+ // In addition to returning a 401 Unauthorized without running if there is not a valid
139+ // MAuth signature, this also makes the validated requesting app UUID available to
140+ // the function
141+ async fn baz(details: ValidatedRequestDetails) -> StatusCode {
142+ println!("Got a request from app with UUID: {}", details.app_uuid);
143+ StatusCode::OK
144+ }
145+
146+ // This request will run whether or not there is a valid mauth signature, but the Option
147+ // provided can be used to tell you whether there was a valid signature, so you can
148+ // implement things like multiple possible types of authentication or behavior other than
149+ // a 401 return if there is no authentication
150+ async fn bam(optional_details: Option<ValidatedRequestDetails>) -> StatusCode {
151+ match optional_details {
152+ Some(details) => println!("Got a request from app with UUID: {}", details.app_uuid),
153+ None => println!("Got a request without a valid mauth signature"),
154+ }
155+ StatusCode::OK
156+ }
157+
158+ let router = Router::new()
159+ .route("/foo", get(foo))
160+ .route("/bar", get(bar))
161+ .route("/baz", get(baz))
162+ .route("/bam", get(bam))
163+ .layer(OptionalMAuthValidationLayer::from_default_file().unwrap());
164+ let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
165+ serve(listener, router).await.unwrap();
166+ # }
167+ ` ` `
168+
169+ # ## Error Handling
170+
171+ Both the `RequiredMAuthValidationLayer` and the `OptionalMAuthValidationLayer` layers will
172+ log errors encountered via `tracing` under the `mauth_client::validate_incoming` target.
173+
174+ The Required layer returns the 401 response immediately, so there is no convenient way to
175+ retrieve the error in order to do anything more sophisticated with it.
176+
177+ The Optional layer, in addition to loging the error, will also add the `MAuthValidationError`
178+ to the request extensions. If desired, any request handlers or middlewares can retrieve it
179+ from there in order to take further actions based on the error type. This error type also
180+ implements Axum's `OptionalFromRequestParts`, so you can more easily retrieve it using
181+ ` Option<MAuthValidationError>` anywhere that supports extractors.
182+
183+ # ## OpenTelemetry Integration
55184
56185There are also optional features `tracing-otel-26` and `tracing-otel-27` that pair with
57186the `axum-service` feature to ensure that any outgoing requests for credentials that take
0 commit comments