Skip to main content

dioxus_clerk/server/
layer.rs

1//! Non-rejecting tower layer that records request credential verification
2//! outcomes for downstream handlers / server functions to gate on.
3//!
4//! This layer never short-circuits with 401: when no token is present (or the
5//! token fails to validate), the request
6//! is forwarded with a [`VerificationOutcome`](crate::core::VerificationOutcome) extension describing the
7//! result. Server functions should use the context reader exposed by
8//! [`crate::server::current_auth`] / [`crate::server::current_auth_opt`]; handlers that need lower-level access can
9//! inspect `VerificationOutcome` directly. This lets public and private routes
10//! coexist under the same layer.
11
12use std::{
13    future::Future,
14    pin::Pin,
15    sync::Arc,
16    task::{Context, Poll},
17};
18
19use super::config::ClerkAuthLayerConfig;
20use super::verification::{VerifiedRequest, Verifier, verify_request};
21use axum::body::Body;
22use axum::http::{Request, Response};
23use tower::{Layer, Service};
24
25#[cfg(not(target_arch = "wasm32"))]
26trait MaybeSend: Send {}
27
28#[cfg(not(target_arch = "wasm32"))]
29impl<T: Send + ?Sized> MaybeSend for T {}
30
31#[cfg(target_arch = "wasm32")]
32trait MaybeSend {}
33
34#[cfg(target_arch = "wasm32")]
35impl<T: ?Sized> MaybeSend for T {}
36
37#[cfg(not(target_arch = "wasm32"))]
38type BoxServiceFuture<E> = Pin<Box<dyn Future<Output = Result<Response<Body>, E>> + Send>>;
39
40#[cfg(target_arch = "wasm32")]
41type BoxServiceFuture<E> = Pin<Box<dyn Future<Output = Result<Response<Body>, E>>>>;
42
43#[cfg(all(target_arch = "wasm32", feature = "worker"))]
44type ServiceFuture<E> = send_wrapper::SendWrapper<BoxServiceFuture<E>>;
45
46#[cfg(not(all(target_arch = "wasm32", feature = "worker")))]
47type ServiceFuture<E> = BoxServiceFuture<E>;
48
49#[cfg(all(target_arch = "wasm32", feature = "worker"))]
50fn service_future<E>(future: BoxServiceFuture<E>) -> ServiceFuture<E> {
51    send_wrapper::SendWrapper::new(future)
52}
53
54#[cfg(not(all(target_arch = "wasm32", feature = "worker")))]
55fn service_future<E>(future: BoxServiceFuture<E>) -> ServiceFuture<E> {
56    future
57}
58
59/// Tower layer that verifies Clerk session JWTs and inserts a
60/// [`VerificationOutcome`](crate::core::VerificationOutcome) into request extensions. Valid bearer tokens (or
61/// `__session` cookies) produce `VerificationOutcome::Valid(auth)`. The
62/// layer is **non-rejecting** for missing or invalid credentials: it records
63/// the outcome and lets the request continue so downstream code can decide
64/// how to handle anonymous requests.
65///
66/// # Restricting accepted tokens
67///
68/// With the default configuration, **any** JWT signed by your Clerk instance
69/// key verifies, including tokens minted from Clerk JWT templates for
70/// third-party integrations, which legitimately reach browsers. If your
71/// instance uses JWT templates, configure
72/// [`ClerkAuthLayerConfig::with_issuers`] and
73/// [`ClerkAuthLayerConfig::with_authorized_parties`] (and audiences where
74/// applicable) via [`ClerkAuthLayer::from_config`] so integration tokens
75/// cannot pass as session tokens.
76///
77/// # Verification model and limitations
78///
79/// Verification is **stateless**: each request is checked purely against the
80/// cached JWKS signing keys, with no call back to Clerk to consult live session
81/// state. Two consequences follow, both inherent to networkless JWT
82/// verification and matching Clerk's own backend model:
83///
84/// - **No revocation window.** A token whose session has since been signed out
85///   or revoked stays accepted until its `exp`. Clerk session tokens are
86///   short-lived (about a minute), so the exposure is bounded by that lifetime
87///   rather than by revocation. Gate anything that must react to revocation
88///   immediately on a fresh check rather than on a still-valid token.
89/// - **Key-rotation lag.** A token signed with a `kid` not in the cached JWKS
90///   triggers at most one refresh per unknown-kid refresh interval (5 minutes);
91///   within that window an unknown `kid` is rejected as invalid. Clerk
92///   pre-publishes new keys before signing with them, so this affects only
93///   rotations faster than the refresh floor, and the floor exists to keep an
94///   attacker from forcing unbounded JWKS refetches.
95///
96/// # `worker` feature
97///
98/// With the `worker` feature (server on wasm), the service future is wrapped
99/// in `SendWrapper` to satisfy Axum's `Send` bound. This assumes a
100/// single-threaded runtime such as Cloudflare Workers: the future must be
101/// polled and dropped on the thread that created it, and doing otherwise
102/// panics deterministically.
103#[derive(Clone)]
104pub struct ClerkAuthLayer {
105    inner: Arc<Inner>,
106}
107
108impl std::fmt::Debug for ClerkAuthLayer {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        f.debug_struct("ClerkAuthLayer").finish_non_exhaustive()
111    }
112}
113
114struct Inner {
115    verifier: Verifier,
116}
117
118impl ClerkAuthLayer {
119    /// Build a layer that verifies tokens against Clerk's live JWKS using
120    /// the given backend secret key. The JWKS is fetched lazily on the
121    /// first valid-looking request and cached in memory.
122    ///
123    /// Uses the default configuration; see the type-level docs on
124    /// [restricting accepted tokens](ClerkAuthLayer#restricting-accepted-tokens)
125    /// when the Clerk instance mints JWT-template tokens.
126    pub fn new(secret_key: impl Into<String>) -> Result<Self, crate::core::ClerkError> {
127        Self::from_config(ClerkAuthLayerConfig::new(secret_key))
128    }
129
130    /// Build a layer from the conventional `CLERK_SECRET_KEY` environment variable.
131    ///
132    /// Uses the default configuration; see the type-level docs on
133    /// [restricting accepted tokens](ClerkAuthLayer#restricting-accepted-tokens)
134    /// when the Clerk instance mints JWT-template tokens.
135    pub fn from_env() -> Result<Self, crate::core::ClerkError> {
136        Self::from_config(ClerkAuthLayerConfig::from_env()?)
137    }
138
139    /// Build a layer from owned verifier configuration.
140    ///
141    /// Use this when an application needs to override Clerk backend settings or
142    /// enable optional claim validation.
143    pub fn from_config(config: ClerkAuthLayerConfig) -> Result<Self, crate::core::ClerkError> {
144        let verifier = Verifier::new(config)?;
145        Ok(Self {
146            inner: Arc::new(Inner { verifier }),
147        })
148    }
149}
150
151impl<S> Layer<S> for ClerkAuthLayer {
152    type Service = ClerkAuthService<S>;
153
154    fn layer(&self, inner: S) -> Self::Service {
155        ClerkAuthService {
156            inner,
157            layer: self.clone(),
158        }
159    }
160}
161
162/// Tower service produced by [`ClerkAuthLayer`].
163#[derive(Clone)]
164pub struct ClerkAuthService<S> {
165    inner: S,
166    layer: ClerkAuthLayer,
167}
168
169impl<S: std::fmt::Debug> std::fmt::Debug for ClerkAuthService<S> {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        f.debug_struct("ClerkAuthService")
172            .field("inner", &self.inner)
173            .finish_non_exhaustive()
174    }
175}
176
177impl<S> Service<Request<Body>> for ClerkAuthService<S>
178where
179    S: Service<Request<Body>, Response = Response<Body>> + Clone + MaybeSend + 'static,
180    S::Future: MaybeSend + 'static,
181{
182    type Response = Response<Body>;
183    type Error = S::Error;
184    type Future = ServiceFuture<S::Error>;
185
186    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
187        self.inner.poll_ready(cx)
188    }
189
190    fn call(&mut self, req: Request<Body>) -> Self::Future {
191        let layer = self.layer.clone();
192        let clone = self.inner.clone();
193        let mut inner = std::mem::replace(&mut self.inner, clone);
194
195        service_future(Box::pin(async move {
196            match verify_request(req, layer.inner.verifier.clone()).await {
197                VerifiedRequest::Forward(req) => inner.call(req).await,
198                VerifiedRequest::Unavailable(response) => Ok(response),
199            }
200        }))
201    }
202}