pub struct JwtService { /* private fields */ }Expand description
Signs and validates JWTs. Provide this into the DI container so the framework
boundaries (boundary.rs, ws.rs) can auto-populate RequestContext::claims()
on every incoming request.
§Secret rotation
Keys live behind Rotating (an
ArcSwap): the request path pays one atomic pointer load, while
rotate_secret — typically driven by a
SecretSource watcher — swaps in a new bundle with no restart. The
previous key is retained for verification, so live tokens (≤ TTL old)
keep validating through the grace window.
Token signing returns Result<_, JwtSignError> — a malformed key from
a bad rotation payload must surface as a 500 on the affected request,
never as a process panic.
Implementations§
Source§impl JwtService
impl JwtService
Sourcepub fn new(config: JwtConfig) -> Self
pub fn new(config: JwtConfig) -> Self
Build from config. Panics if an asymmetric algorithm is configured
with malformed / missing PEM key material — this is a boot-time
misconfiguration, not a per-request condition. Use
try_new to handle it gracefully.
Sourcepub fn try_new(config: JwtConfig) -> Result<Self, JwtSignError>
pub fn try_new(config: JwtConfig) -> Result<Self, JwtSignError>
Fallible constructor — returns Err instead of panicking on bad key
material (e.g. an ES256 config with an unparseable private-key PEM).
Sourcepub fn rotate_secret(&self, new_secret: &[u8], version: u64)
pub fn rotate_secret(&self, new_secret: &[u8], version: u64)
Hot-swap the signing secret — no restart, no token mass-invalidation.
New tokens sign with the new key immediately; tokens signed with the previous key keep verifying until natural expiry. Versions are monotonic: a stale (≤ current) version is ignored, making concurrent watchers and duplicate delivery harmless.
For HMAC families new_secret is the new shared secret. For asymmetric
families it is the new private-key PEM; the paired public key must be
supplied via rotate_keypair instead — this
method assumes the configured public key still verifies.
Sourcepub fn rotate_keypair(
&self,
new_signing: &[u8],
new_public_pem: Option<&str>,
version: u64,
)
pub fn rotate_keypair( &self, new_signing: &[u8], new_public_pem: Option<&str>, version: u64, )
Rotate an asymmetric key pair (private PEM + public PEM). Retains the previous public key for the verification grace window, exactly like the HMAC path. No-op on stale versions.
Sourcepub fn public_key_pem(&self) -> Option<&str>
pub fn public_key_pem(&self) -> Option<&str>
Publish the current public verification key as a JWKS document (JSON).
Returns None for HMAC families (a shared secret must never be exposed)
or when no public_key_pem was configured. The OIDC provider serves this
at /.well-known/jwks.json. The JWK is derived from the configured
public PEM by the identity crate’s OIDC layer; core stores the raw PEM
and kid so that layer can convert without re-parsing config.
Sourcepub fn algorithm_name(&self) -> &'static str
pub fn algorithm_name(&self) -> &'static str
The signing algorithm name as it appears in a JWK / JOSE header
("HS256", "RS256", "ES256", …).
Sourcepub fn sign(&self, claims: &Value) -> Result<String, JwtSignError>
pub fn sign(&self, claims: &Value) -> Result<String, JwtSignError>
Sign an arbitrary claim set with the current key and configured
algorithm (header carries the kid). Use for tokens the typed helpers
don’t cover — notably OIDC id_tokens, which need aud/iss/nonce.
The caller owns the full claim set (including iat/exp); nothing is
injected. Verify later with decode.
Sourcepub fn unix_now() -> u64
pub fn unix_now() -> u64
Current unix time in seconds — handy for callers building claim sets to
pass to sign.
Sourcepub fn issue_access(
&self,
sub: &str,
role: &str,
email: &str,
) -> Result<String, JwtSignError>
pub fn issue_access( &self, sub: &str, role: &str, email: &str, ) -> Result<String, JwtSignError>
Issue a signed access token.
Claims: sub = user ID, role, email, type = "access", jti, iat, exp.
Signing can only fail on malformed key material (e.g. a bad rotation payload) — propagate the error instead of panicking mid-traffic.
Sourcepub fn issue_access_with_perms(
&self,
sub: &str,
role: &str,
email: &str,
perms: &[String],
) -> Result<String, JwtSignError>
pub fn issue_access_with_perms( &self, sub: &str, role: &str, email: &str, perms: &[String], ) -> Result<String, JwtSignError>
Like Self::issue_access but embeds a perms claim (array of permission strings).
Use this when the app maintains a permission map so that PermissionGuard
can do a zero-latency lookup without hitting the store on each request.
Sourcepub fn issue_access_bound(
&self,
sub: &str,
role: &str,
email: &str,
perms: &[String],
tenant: Option<&str>,
) -> Result<String, JwtSignError>
pub fn issue_access_bound( &self, sub: &str, role: &str, email: &str, perms: &[String], tenant: Option<&str>, ) -> Result<String, JwtSignError>
Like Self::issue_access_with_perms but additionally binds the token to
a tenant via the tenant claim. TenantGuard then enforces that
requests carrying this token resolve to the same tenant — omitting or
forging the tenant header yields 403, so a suspended tenant’s users
cannot ride the fallback pool by dropping the header.
Sourcepub fn issue_access_bound_cnf(
&self,
sub: &str,
role: &str,
email: &str,
perms: &[String],
tenant: Option<&str>,
cnf_jkt: Option<&str>,
) -> Result<String, JwtSignError>
pub fn issue_access_bound_cnf( &self, sub: &str, role: &str, email: &str, perms: &[String], tenant: Option<&str>, cnf_jkt: Option<&str>, ) -> Result<String, JwtSignError>
Like Self::issue_access_bound but additionally sender-constrains
the token (RFC 9449 DPoP): cnf_jkt is the SHA-256 JWK thumbprint of the
client’s proof-of-possession key. A resource server then accepts the
token only when the request carries a matching DPoP proof, so a stolen
bearer token is useless without the client’s private key.
Sourcepub fn issue_refresh(&self, sub: &str) -> Result<(String, String), JwtSignError>
pub fn issue_refresh(&self, sub: &str) -> Result<(String, String), JwtSignError>
Issue a signed refresh token with a unique jti.
Claims: sub, type = "refresh", jti, iat, exp.
The jti is returned alongside the token so the caller can persist it.
Sourcepub fn decode(&self, token: &str) -> Option<Arc<Claims>>
pub fn decode(&self, token: &str) -> Option<Arc<Claims>>
Validate signature + expiry and return the decoded claims as a JSON map.
Returns None for any invalid token (expired, bad signature, malformed).
Does NOT enforce token type — use Self::decode_access at request boundaries.
Sourcepub fn decode_access(&self, token: &str) -> Option<Arc<Claims>>
pub fn decode_access(&self, token: &str) -> Option<Arc<Claims>>
Like decode but additionally requires "type" == "access".
Use this at request boundaries so refresh tokens cannot be passed as access tokens to authenticate protected routes.
Sourcepub fn validate_refresh(&self, token: &str) -> Option<(String, String)>
pub fn validate_refresh(&self, token: &str) -> Option<(String, String)>
Validate a refresh token specifically.
Returns (subject, jti) on success, None otherwise.
Callers must verify that the jti exists in their token store before
issuing a new pair.
Sourcepub fn access_ttl_secs(&self) -> u64
pub fn access_ttl_secs(&self) -> u64
Lifetime of access tokens in seconds (used in TokenResponse.expires_in).
Sourcepub fn refresh_ttl_secs(&self) -> u64
pub fn refresh_ttl_secs(&self) -> u64
Lifetime of refresh tokens (used by token store for TTL).
Auto Trait Implementations§
impl !Freeze for JwtService
impl RefUnwindSafe for JwtService
impl Send for JwtService
impl Sync for JwtService
impl Unpin for JwtService
impl UnsafeUnpin for JwtService
impl UnwindSafe for JwtService
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self>
fn with_context(self, otel_cx: Context) -> WithContext<Self>
Source§fn with_current_context(self) -> WithContext<Self>
fn with_current_context(self) -> WithContext<Self>
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request