use chrono::{Duration, Utc};
#[cfg(feature = "rotation")]
use jsonwebtoken::decode_header;
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode};
use uuid::Uuid;
use crate::claims::StandardClaims;
use crate::error::JwtError;
#[cfg(feature = "revocation")]
use crate::revocation::TokenRevocationStore;
pub const DEFAULT_LEEWAY_SECS: u64 = 60;
pub const DEFAULT_REQUIRED_CLAIMS: [&str; 2] = ["exp", "iss"];
pub const DEFAULT_REVOCATION_MAX_ENTRIES: usize = 100_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JwtAlgorithm {
HS256,
HS384,
HS512,
ES256,
ES384,
RS256,
RS384,
RS512,
PS256,
PS384,
PS512,
EdDSA,
}
impl From<JwtAlgorithm> for jsonwebtoken::Algorithm {
fn from(alg: JwtAlgorithm) -> Self {
match alg {
JwtAlgorithm::HS256 => jsonwebtoken::Algorithm::HS256,
JwtAlgorithm::HS384 => jsonwebtoken::Algorithm::HS384,
JwtAlgorithm::HS512 => jsonwebtoken::Algorithm::HS512,
JwtAlgorithm::ES256 => jsonwebtoken::Algorithm::ES256,
JwtAlgorithm::ES384 => jsonwebtoken::Algorithm::ES384,
JwtAlgorithm::RS256 => jsonwebtoken::Algorithm::RS256,
JwtAlgorithm::RS384 => jsonwebtoken::Algorithm::RS384,
JwtAlgorithm::RS512 => jsonwebtoken::Algorithm::RS512,
JwtAlgorithm::PS256 => jsonwebtoken::Algorithm::PS256,
JwtAlgorithm::PS384 => jsonwebtoken::Algorithm::PS384,
JwtAlgorithm::PS512 => jsonwebtoken::Algorithm::PS512,
JwtAlgorithm::EdDSA => jsonwebtoken::Algorithm::EdDSA,
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct RotationKey {
pub secret: String,
pub key_id: Option<String>,
pub public_key: Option<String>,
pub der_public_key: Option<Vec<u8>>,
}
impl RotationKey {
pub fn new(secret: impl Into<String>, key_id: Option<String>) -> Self {
Self {
secret: secret.into(),
key_id,
public_key: None,
der_public_key: None,
}
}
}
impl std::fmt::Debug for RotationKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RotationKey")
.field("key_id", &self.key_id)
.field("secret", &"<redacted>")
.field("public_key", &self.public_key.as_ref().map(|_| "<pem>"))
.field("der_public_key", &self.der_public_key.is_some())
.finish()
}
}
#[derive(Clone)]
pub struct JwtConfig {
pub algorithm: JwtAlgorithm,
pub secret: String,
pub der_private_key: Option<Vec<u8>>,
pub public_key: Option<String>,
pub der_public_key: Option<Vec<u8>>,
pub issuer: Option<String>,
pub audience: Option<String>,
pub audiences: Vec<String>,
pub leeway: Option<u64>,
pub required_claims: Vec<String>,
pub validate_exp: bool,
pub validate_nbf: bool,
pub access_token_ttl: i64,
pub refresh_token_ttl: i64,
#[cfg(feature = "rotation")]
pub key_id: Option<String>,
#[cfg(feature = "rotation")]
pub rotation_keys: Vec<RotationKey>,
}
impl std::fmt::Debug for JwtConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut builder = f.debug_struct("JwtConfig");
builder
.field("algorithm", &self.config_alg_name())
.field("secret", &"<redacted>")
.field(
"der_private_key",
&self.der_private_key.as_ref().map(|_| "<redacted>"),
)
.field("public_key", &self.public_key.as_ref().map(|_| "<pem>"))
.field("der_public_key", &self.der_public_key.is_some())
.field("issuer", &self.issuer)
.field("audience", &self.audience)
.field("audiences", &self.audiences)
.field("leeway", &self.leeway)
.field("required_claims", &self.required_claims)
.field("validate_exp", &self.validate_exp)
.field("validate_nbf", &self.validate_nbf)
.field("access_token_ttl", &self.access_token_ttl)
.field("refresh_token_ttl", &self.refresh_token_ttl);
#[cfg(feature = "rotation")]
{
builder
.field("key_id", &self.key_id)
.field("rotation_keys", &self.rotation_keys.len());
}
builder.finish()
}
}
impl JwtConfig {
fn config_alg_name(&self) -> &'static str {
match self.algorithm {
JwtAlgorithm::HS256 => "HS256",
JwtAlgorithm::HS384 => "HS384",
JwtAlgorithm::HS512 => "HS512",
JwtAlgorithm::ES256 => "ES256",
JwtAlgorithm::ES384 => "ES384",
JwtAlgorithm::RS256 => "RS256",
JwtAlgorithm::RS384 => "RS384",
JwtAlgorithm::RS512 => "RS512",
JwtAlgorithm::PS256 => "PS256",
JwtAlgorithm::PS384 => "PS384",
JwtAlgorithm::PS512 => "PS512",
JwtAlgorithm::EdDSA => "EdDSA",
}
}
#[cfg(feature = "rotation")]
pub fn with_rotation_secrets(algorithm: JwtAlgorithm, secrets: Vec<String>) -> Self {
let keys: Vec<RotationKey> = secrets
.into_iter()
.map(|secret| RotationKey::new(secret, None))
.collect();
Self::with_rotation_keys(algorithm, keys)
}
#[cfg(feature = "rotation")]
pub fn with_rotation_keys(algorithm: JwtAlgorithm, keys: Vec<RotationKey>) -> Self {
let primary = keys.first().cloned().unwrap_or_else(|| RotationKey {
secret: String::new(),
key_id: None,
public_key: None,
der_public_key: None,
});
Self {
algorithm,
secret: primary.secret,
der_private_key: None,
public_key: None,
der_public_key: None,
issuer: None,
audience: None,
audiences: Vec::new(),
leeway: None,
required_claims: default_required_claims(),
validate_exp: true,
validate_nbf: false,
access_token_ttl: 3600,
refresh_token_ttl: 604800,
key_id: primary.key_id,
rotation_keys: keys,
}
}
pub fn from_ec_pem(algorithm: JwtAlgorithm, pem: impl Into<String>) -> Self {
Self {
algorithm,
secret: pem.into(),
..Default::default()
}
}
pub fn from_ed_pem(pem: impl Into<String>) -> Self {
Self {
algorithm: JwtAlgorithm::EdDSA,
secret: pem.into(),
..Default::default()
}
}
pub fn from_ed_der(der: impl Into<Vec<u8>>) -> Self {
Self {
algorithm: JwtAlgorithm::EdDSA,
der_private_key: Some(der.into()),
..Default::default()
}
}
pub fn with_public_key(mut self, pem: impl Into<String>) -> Self {
self.public_key = Some(pem.into());
self
}
pub fn with_der_public_key(mut self, der: impl Into<Vec<u8>>) -> Self {
self.der_public_key = Some(der.into());
self
}
pub fn with_leeway(mut self, seconds: u64) -> Self {
self.leeway = Some(seconds);
self
}
pub fn with_audiences(mut self, audiences: Vec<String>) -> Self {
self.audiences = audiences;
self
}
pub fn with_required_claims(mut self, claims: Vec<String>) -> Self {
self.required_claims = claims;
self
}
pub fn with_validate_exp(mut self, validate: bool) -> Self {
self.validate_exp = validate;
self
}
pub fn with_validate_nbf(mut self, validate: bool) -> Self {
self.validate_nbf = validate;
self
}
}
fn default_required_claims() -> Vec<String> {
DEFAULT_REQUIRED_CLAIMS
.iter()
.map(|c| (*c).to_string())
.collect()
}
impl Default for JwtConfig {
fn default() -> Self {
Self {
algorithm: JwtAlgorithm::HS256,
secret: String::new(),
der_private_key: None,
public_key: None,
der_public_key: None,
issuer: None,
audience: None,
audiences: Vec::new(),
leeway: None,
required_claims: default_required_claims(),
validate_exp: true,
validate_nbf: false,
access_token_ttl: 3600,
refresh_token_ttl: 604800,
#[cfg(feature = "rotation")]
key_id: None,
#[cfg(feature = "rotation")]
rotation_keys: Vec::new(),
}
}
}
pub struct JwtService {
config: JwtConfig,
#[cfg(feature = "revocation")]
revocation: Option<Box<dyn TokenRevocationStore>>,
}
impl JwtService {
pub fn new(config: JwtConfig) -> Self {
Self {
config,
#[cfg(feature = "revocation")]
revocation: None,
}
}
#[cfg(feature = "revocation")]
pub fn with_revocation(mut self, store: Box<dyn TokenRevocationStore>) -> Self {
self.revocation = Some(store);
self
}
pub fn encode<T: serde::Serialize>(&self, claims: &T) -> Result<String, JwtError> {
#[cfg(feature = "rotation")]
let header = {
let mut h = Header::new(self.config.algorithm.into());
h.kid = self.config.key_id.clone();
h
};
#[cfg(not(feature = "rotation"))]
let header = Header::new(self.config.algorithm.into());
let key = self.encoding_key()?;
encode(&header, claims, &key).map_err(|_| JwtError::EncodingFailed)
}
fn build_validation(&self) -> Validation {
let mut validation = Validation::new(self.config.algorithm.into());
validation.leeway = self.config.leeway.unwrap_or(DEFAULT_LEEWAY_SECS);
validation.validate_exp = self.config.validate_exp;
validation.validate_nbf = self.config.validate_nbf;
validation.set_required_spec_claims(self.config.required_claims.as_slice());
if let Some(ref issuer) = self.config.issuer {
validation.set_issuer(std::slice::from_ref(issuer));
}
let mut audiences = self.config.audiences.clone();
if let Some(ref audience) = self.config.audience
&& !audiences.contains(audience)
{
audiences.push(audience.clone());
}
if !audiences.is_empty() {
validation.set_audience(audiences.as_slice());
}
validation
}
pub fn decode<T: serde::de::DeserializeOwned>(&self, token: &str) -> Result<T, JwtError> {
let validation = self.build_validation();
#[cfg(feature = "rotation")]
{
self.decode_with_rotation(token, validation)
}
#[cfg(not(feature = "rotation"))]
{
let key = self.decoding_key()?;
decode::<T>(token, &key, &validation)
.map(|data| data.claims)
.map_err(|e| JwtError::DecodingFailed(e.to_string()))
}
}
#[cfg(feature = "rotation")]
fn decode_with_rotation<T: serde::de::DeserializeOwned>(
&self,
token: &str,
validation: Validation,
) -> Result<T, JwtError> {
let candidates = self.decoding_keys()?;
if let Ok(header) = decode_header(token)
&& let Some(kid) = header.kid.as_deref()
{
if let Some((_, key)) = candidates.iter().find(|(k, _)| k.as_deref() == Some(kid)) {
return decode::<T>(token, key, &validation)
.map(|data| data.claims)
.map_err(|e| JwtError::DecodingFailed(e.to_string()));
}
return Err(JwtError::DecodingFailed(format!(
"token references unknown key id `{kid}`"
)));
}
let mut last_err = JwtError::DecodingFailed("no keys configured".to_string());
for (_, key) in &candidates {
match decode::<T>(token, key, &validation) {
Ok(data) => return Ok(data.claims),
Err(e) => last_err = JwtError::DecodingFailed(e.to_string()),
}
}
Err(last_err)
}
fn stamped_standard(&self, mut claims: StandardClaims, ttl_secs: i64) -> StandardClaims {
let now = Utc::now();
if claims.iat.is_none() {
claims.iat = Some(now);
}
if claims.exp.is_none() {
claims.exp = Some(now + Duration::seconds(ttl_secs));
}
if claims.iss.is_none() {
claims.iss = self.config.issuer.clone();
}
if claims.aud.is_none() {
let mut audiences = self.config.audiences.clone();
if let Some(ref audience) = self.config.audience
&& !audiences.contains(audience)
{
audiences.push(audience.clone());
}
claims.aud = audiences.into_iter().next();
}
if claims.jti.is_none() {
claims.jti = Some(Uuid::new_v4().to_string());
}
claims
}
pub fn encode_standard(&self, claims: StandardClaims) -> Result<String, JwtError> {
let claims = self.stamped_standard(claims, self.config.access_token_ttl);
self.encode(&claims)
}
pub fn encode_refresh_standard(&self, claims: StandardClaims) -> Result<String, JwtError> {
let claims = self.stamped_standard(claims, self.config.refresh_token_ttl);
self.encode(&claims)
}
pub async fn decode_standard(&self, token: &str) -> Result<StandardClaims, JwtError> {
let claims: StandardClaims = self.decode(token)?;
#[cfg(feature = "revocation")]
if let (Some(store), Some(jti)) = (&self.revocation, &claims.jti) {
let revoked = store.is_revoked(jti).await.map_err(|_| JwtError::Revoked)?;
if revoked {
return Err(JwtError::Revoked);
}
}
Ok(claims)
}
pub fn validate(&self, token: &str) -> Result<StandardClaims, JwtError> {
self.decode::<StandardClaims>(token)
}
fn encoding_key(&self) -> Result<EncodingKey, JwtError> {
encoding_key_for(
self.config.algorithm,
&self.config.secret,
self.config.der_private_key.as_deref(),
)
}
#[cfg(not(feature = "rotation"))]
fn decoding_key(&self) -> Result<DecodingKey, JwtError> {
decoding_key_for(
self.config.algorithm,
self.config.public_key.as_deref(),
self.config.der_public_key.as_deref(),
&self.config.secret,
)
}
#[cfg(feature = "rotation")]
fn decoding_keys(&self) -> Result<Vec<(Option<String>, DecodingKey)>, JwtError> {
let mut keys = vec![(
self.config.key_id.clone(),
decoding_key_for(
self.config.algorithm,
self.config.public_key.as_deref(),
self.config.der_public_key.as_deref(),
&self.config.secret,
)?,
)];
for entry in &self.config.rotation_keys {
keys.push((
entry.key_id.clone(),
decoding_key_for(
self.config.algorithm,
entry
.public_key
.as_deref()
.or(self.config.public_key.as_deref()),
entry
.der_public_key
.as_deref()
.or(self.config.der_public_key.as_deref()),
&entry.secret,
)?,
));
}
Ok(keys)
}
}
fn key_load_error(e: impl std::fmt::Display) -> JwtError {
JwtError::KeyLoading(e.to_string())
}
fn encoding_key_for(
algorithm: JwtAlgorithm,
secret: &str,
der: Option<&[u8]>,
) -> Result<EncodingKey, JwtError> {
match algorithm {
JwtAlgorithm::HS256 | JwtAlgorithm::HS384 | JwtAlgorithm::HS512 => {
Ok(EncodingKey::from_secret(secret.as_bytes()))
}
JwtAlgorithm::RS256
| JwtAlgorithm::RS384
| JwtAlgorithm::RS512
| JwtAlgorithm::PS256
| JwtAlgorithm::PS384
| JwtAlgorithm::PS512 => {
EncodingKey::from_rsa_pem(secret.as_bytes()).map_err(key_load_error)
}
JwtAlgorithm::ES256 | JwtAlgorithm::ES384 => {
if let Some(der) = der {
return Ok(EncodingKey::from_ec_der(der));
}
EncodingKey::from_ec_pem(secret.as_bytes()).map_err(key_load_error)
}
JwtAlgorithm::EdDSA => {
if let Some(der) = der {
return Ok(EncodingKey::from_ed_der(der));
}
EncodingKey::from_ed_pem(secret.as_bytes()).map_err(key_load_error)
}
}
}
fn decoding_key_for(
algorithm: JwtAlgorithm,
public_pem: Option<&str>,
public_der: Option<&[u8]>,
secret: &str,
) -> Result<DecodingKey, JwtError> {
match algorithm {
JwtAlgorithm::HS256 | JwtAlgorithm::HS384 | JwtAlgorithm::HS512 => {
Ok(DecodingKey::from_secret(secret.as_bytes()))
}
JwtAlgorithm::RS256
| JwtAlgorithm::RS384
| JwtAlgorithm::RS512
| JwtAlgorithm::PS256
| JwtAlgorithm::PS384
| JwtAlgorithm::PS512 => {
if let Some(der) = public_der {
return Ok(DecodingKey::from_rsa_der(der));
}
let pem = public_pem.unwrap_or(secret);
DecodingKey::from_rsa_pem(pem.as_bytes()).map_err(key_load_error)
}
JwtAlgorithm::ES256 | JwtAlgorithm::ES384 => {
if let Some(der) = public_der {
return Ok(DecodingKey::from_ec_der(der));
}
let pem = public_pem.unwrap_or(secret);
DecodingKey::from_ec_pem(pem.as_bytes()).map_err(key_load_error)
}
JwtAlgorithm::EdDSA => {
if let Some(der) = public_der {
return Ok(DecodingKey::from_ed_der(der));
}
let pem = public_pem.unwrap_or(secret);
DecodingKey::from_ed_pem(pem.as_bytes()).map_err(key_load_error)
}
}
}