ocre/runtime/jwt.rs
1use crate::{
2 Ctx, Error, Result,
3 jwt::{Claims, Key, decode_with, encode_with},
4 session::{SECRET_KEY_BASE, SECRET_KEY_BASE_PREVIOUS, previous_secrets},
5};
6
7fn secret(ctx: &Ctx, name: &str) -> Option<String> {
8 ctx.env().secret(name).ok().map(|secret| secret.to_string())
9}
10
11fn key(ctx: &Ctx) -> Result<Key> {
12 Key::from_secret(secret(ctx, SECRET_KEY_BASE))
13}
14
15/// Keys of `SECRET_KEY_BASE_PREVIOUS`, during a rotation.
16fn previous_keys(ctx: &Ctx) -> Result<Vec<Key>> {
17 let secrets = previous_secrets(secret(ctx, SECRET_KEY_BASE_PREVIOUS)).map_err(Error::Internal)?;
18 Ok(secrets.iter().map(|secret| Key::from_secret_key_base(secret)).collect())
19}
20
21/// Signs `claims` with the HS256 key derived from the `SECRET_KEY_BASE` Worker secret.
22///
23/// Same token as [`encode_with`](crate::jwt::encode_with) with
24/// [`Key::from_secret_key_base`](crate::jwt::Key::from_secret_key_base).
25/// The secret is read on every call; no D1 or KV operation.
26///
27/// # Errors
28///
29/// [`Error::Internal`](crate::Error::Internal) (500) when `SECRET_KEY_BASE`
30/// is not set or is shorter than 64 characters; the message names the fix
31/// (`ocre secret`, `.dev.vars` for `ocre dev`, `ocre deploy` uploads it).
32///
33/// # Examples
34///
35/// ```rust,no_run
36/// use axum::{Json, extract::State};
37/// use ocre::{Ctx, Result, jwt::{self, Claims}};
38///
39/// // POST /api/auth/token, after checking the password:
40/// async fn token(State(ctx): State<Ctx>) -> Result<Json<serde_json::Value>> {
41/// let user_id = 42;
42/// let token = jwt::encode(&ctx, &Claims::new(user_id.to_string(), 3600))?;
43/// Ok(Json(serde_json::json!({ "token": token, "expires_in": 3600 })))
44/// }
45/// ```
46pub fn encode(ctx: &Ctx, claims: &Claims) -> Result<String> {
47 Ok(encode_with(&key(ctx)?, claims))
48}
49
50/// Verifies a token from a client with the key derived from `SECRET_KEY_BASE` and returns its claims.
51///
52/// Checks the signature, the algorithm ([`ALGORITHM`](crate::jwt::ALGORITHM)
53/// only) and the expiry against the current time ([`crate::now`]), like
54/// [`decode_with`](crate::jwt::decode_with). During a secret rotation, tokens
55/// signed with a key listed in
56/// [`SECRET_KEY_BASE_PREVIOUS`](crate::SECRET_KEY_BASE_PREVIOUS) still
57/// verify. No D1 or KV operation.
58///
59/// # Errors
60///
61/// - [`Error::Unauthorized`](crate::Error::Unauthorized) (401) for any
62/// invalid token: malformed, other algorithm, bad signature, expired.
63/// - [`Error::Internal`](crate::Error::Internal) (500) when `SECRET_KEY_BASE`
64/// is not set or is shorter than 64 characters, or a previous secret is
65/// shorter than 64 characters (message names the fix).
66///
67/// # Examples
68///
69/// ```rust,no_run
70/// use axum::http::{HeaderMap, Uri};
71/// use axum::extract::State;
72/// use ocre::{Ctx, Error, Result, jwt::{self, Location}};
73///
74/// async fn me(State(ctx): State<Ctx>, headers: HeaderMap, uri: Uri) -> Result<String> {
75/// let token = jwt::token_from(&headers, &uri, &[Location::Bearer]).ok_or(Error::Unauthorized)?;
76/// let claims = jwt::decode(&ctx, &token)?;
77/// Ok(claims.sub)
78/// }
79/// ```
80pub fn decode(ctx: &Ctx, token: &str) -> Result<Claims> {
81 let now = crate::now();
82 match decode_with(&key(ctx)?, token, now) {
83 Err(Error::Unauthorized) => {
84 let previous = previous_keys(ctx)?;
85 previous.iter().find_map(|key| decode_with(key, token, now).ok()).ok_or(Error::Unauthorized)
86 }
87 result => result,
88 }
89}