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}