Skip to main content

rustauth_plugins/jwt/
sign.rs

1use josekit::jws::alg::ecdsa::EcdsaJwsAlgorithm::{Es256, Es512};
2use josekit::jws::alg::eddsa::EddsaJwsAlgorithm::Eddsa;
3use josekit::jws::alg::rsassa::RsassaJwsAlgorithm::Rs256;
4use josekit::jws::alg::rsassa_pss::RsassaPssJwsAlgorithm::Ps256;
5use josekit::jws::JwsHeader;
6use josekit::jwt::{self, JwtPayload};
7use rustauth_core::context::AuthContext;
8use rustauth_core::error::RustAuthError;
9use serde_json::Value;
10
11use super::claims::{claims_with_defaults, JwtClaims};
12use super::keys::{generate_jwk, JwkAlgorithm};
13use super::{adapter, crypto, JwtOptions};
14
15pub async fn sign_jwt(
16    context: &AuthContext,
17    claims: JwtClaims,
18    override_options: Option<JwtOptions>,
19) -> Result<String, RustAuthError> {
20    let options = override_options.unwrap_or_default();
21    sign_jwt_with_options(context, claims, &options).await
22}
23
24pub(crate) async fn sign_jwt_with_options(
25    context: &AuthContext,
26    claims: JwtClaims,
27    options: &JwtOptions,
28) -> Result<String, RustAuthError> {
29    let claims = claims_with_defaults(claims, &context.base_url, options)?;
30    if let Some(sign) = &options.jwt.sign {
31        return sign(claims).await;
32    }
33
34    let mut key = adapter::get_latest_key(context, options).await?;
35    if key
36        .as_ref()
37        .and_then(|key| key.expires_at)
38        .is_some_and(|expires_at| expires_at <= time::OffsetDateTime::now_utc())
39    {
40        key = None;
41    }
42    let key = match key {
43        Some(key) => key,
44        None => {
45            let key = crypto::encrypt_private_key(
46                context,
47                generate_jwk(options)?,
48                options.jwks.disable_private_key_encryption,
49            )?;
50            adapter::create_jwk(context, options, key).await?
51        }
52    };
53    let private_key = crypto::decrypt_private_key(
54        context,
55        &key.private_key,
56        options.jwks.disable_private_key_encryption,
57    )?;
58    encode_with_key(
59        &private_key,
60        key.alg.unwrap_or_else(|| options.algorithm()),
61        &key.id,
62        claims,
63    )
64}
65
66fn encode_with_key(
67    private_key: &str,
68    algorithm: JwkAlgorithm,
69    key_id: &str,
70    claims: JwtClaims,
71) -> Result<String, RustAuthError> {
72    let jwk = josekit::jwk::Jwk::from_bytes(private_key)
73        .map_err(|error| RustAuthError::Crypto(error.to_string()))?;
74    let payload = jwt_payload(claims)?;
75    let mut header = JwsHeader::new();
76    header.set_algorithm(algorithm.as_str());
77    header.set_key_id(key_id);
78
79    match algorithm {
80        JwkAlgorithm::EdDsa => jwt::encode_with_signer(
81            &payload,
82            &header,
83            &Eddsa
84                .signer_from_jwk(&jwk)
85                .map_err(|error| RustAuthError::Crypto(error.to_string()))?,
86        ),
87        JwkAlgorithm::Es256 => jwt::encode_with_signer(
88            &payload,
89            &header,
90            &Es256
91                .signer_from_jwk(&jwk)
92                .map_err(|error| RustAuthError::Crypto(error.to_string()))?,
93        ),
94        JwkAlgorithm::Es512 => jwt::encode_with_signer(
95            &payload,
96            &header,
97            &Es512
98                .signer_from_jwk(&jwk)
99                .map_err(|error| RustAuthError::Crypto(error.to_string()))?,
100        ),
101        JwkAlgorithm::Rs256 => jwt::encode_with_signer(
102            &payload,
103            &header,
104            &Rs256
105                .signer_from_jwk(&jwk)
106                .map_err(|error| RustAuthError::Crypto(error.to_string()))?,
107        ),
108        JwkAlgorithm::Ps256 => jwt::encode_with_signer(
109            &payload,
110            &header,
111            &Ps256
112                .signer_from_jwk(&jwk)
113                .map_err(|error| RustAuthError::Crypto(error.to_string()))?,
114        ),
115    }
116    .map_err(|error| RustAuthError::Crypto(error.to_string()))
117}
118
119fn jwt_payload(claims: JwtClaims) -> Result<JwtPayload, RustAuthError> {
120    let mut payload = JwtPayload::new();
121    for (key, value) in claims {
122        payload
123            .set_claim(&key, Some(value_to_jose(value)))
124            .map_err(|error| RustAuthError::Crypto(error.to_string()))?;
125    }
126    Ok(payload)
127}
128
129fn value_to_jose(value: Value) -> josekit::Value {
130    serde_json::from_value(value).unwrap_or(josekit::Value::Null)
131}