Skip to main content

rust_grpc_lib/auth/
interceptor.rs

1//! [`ClientJwtInterceptor`] — outbound tonic interceptor that attaches a JWT
2//! `Authorization: Bearer` header to every request by calling a [`TokenProvider`].
3//!
4//! This is the only type defined in this module. It is used internally by the
5//! `from_endpoint_with_provider` constructor that `#[derive(GrpcClient)]`
6//! generates on every client struct; consumers do not construct it directly.
7
8use rust_auth_lib::TokenProvider;
9use tonic::{Request, Status, service::Interceptor};
10
11/// Generic interceptor that calls any [`TokenProvider`] and attaches the result
12/// as a `Bearer` token on every outbound gRPC request.
13///
14/// Used internally by the `from_endpoint_with_provider` constructor that
15/// `#[derive(GrpcClient)]` generates on every client struct; consumers do not
16/// typically construct this directly.
17///
18/// To forward the caller's token from an incoming request to a downstream
19/// service, use [`crate::auth::extract_token`] to obtain a [`rust_auth_lib::ForwardedToken`]
20/// and pass it as the provider:
21///
22/// ```rust,ignore
23/// #[rust_grpc_lib::keycloak_authenticated_service]
24/// impl Daq for MyDaqService {
25///     async fn get_data(&self, req: Request<GetDataRequest>) -> Result<Response<GetDataResponse>, Status> {
26///         let provider = rust_grpc_lib::auth::extract_token(&req)?;
27///         let client = AlarmCommandsClient::from_endpoint_with_provider(
28///             "http://alarm-host:50051",
29///             provider,
30///         )?;
31///         // ...
32///     }
33/// }
34/// ```
35pub struct ClientJwtInterceptor<P: TokenProvider> {
36    provider: P,
37}
38
39impl<P: TokenProvider> ClientJwtInterceptor<P> {
40    pub fn new(provider: P) -> Self {
41        Self { provider }
42    }
43}
44
45impl<P: TokenProvider> Interceptor for ClientJwtInterceptor<P> {
46    fn call(&mut self, mut req: Request<()>) -> Result<Request<()>, Status> {
47        let token = self
48            .provider
49            .get_token()
50            .map_err(|e| Status::unauthenticated(e.to_string()))?;
51
52        req.metadata_mut().insert(
53            "authorization",
54            format!("Bearer {token}").parse().map_err(|e| {
55                Status::internal(format!("token contains invalid header characters: {e}"))
56            })?,
57        );
58        Ok(req)
59    }
60}