Skip to main content

openauth_plugins/jwt/
mod.rs

1//! Server-side JWT and JWKS plugin.
2
3mod adapter;
4mod claims;
5mod crypto;
6mod endpoints;
7mod keys;
8mod options;
9mod schema;
10mod sign;
11mod verify;
12
13pub use claims::{to_exp_jwt, JwtClaims, TimeInput};
14pub use keys::{Jwk, JwkAlgorithm, Jwks};
15pub use options::{
16    JwtAdapterOptions, JwtCreateJwkHandler, JwtDefinePayloadHandler, JwtGetJwksHandler,
17    JwtGetSubjectHandler, JwtJwksOptions, JwtOptions, JwtSessionContext, JwtSignHandler,
18    JwtSigningOptions,
19};
20pub use sign::sign_jwt;
21pub use verify::{verify_jwt, verify_jwt_with_options};
22
23use std::sync::Arc;
24
25use http::header;
26use openauth_core::context::request_state::{current_session, current_session_user};
27use openauth_core::error::OpenAuthError;
28use openauth_core::plugin::{AuthPlugin, PluginAfterHookAction};
29
30pub const UPSTREAM_PLUGIN_ID: &str = "jwt";
31
32pub fn jwt() -> Result<AuthPlugin, OpenAuthError> {
33    jwt_with_options(JwtOptions::default())
34}
35
36pub fn jwt_with_options(options: JwtOptions) -> Result<AuthPlugin, OpenAuthError> {
37    options.validate()?;
38    let options = Arc::new(options);
39    let mut plugin = AuthPlugin::new(UPSTREAM_PLUGIN_ID)
40        .with_version(crate::VERSION)
41        .with_schema(schema::jwks_schema())
42        .with_endpoint(endpoints::jwks_endpoint(Arc::clone(&options)))
43        .with_endpoint(endpoints::token_endpoint(Arc::clone(&options)))
44        .with_endpoint(endpoints::sign_jwt_endpoint(Arc::clone(&options)))
45        .with_endpoint(endpoints::verify_jwt_endpoint(Arc::clone(&options)));
46
47    if !options.disable_setting_jwt_header {
48        let options_for_hook = Arc::clone(&options);
49        plugin =
50            plugin.with_async_after_hook("/get-session", move |context, _request, response| {
51                let options = Arc::clone(&options_for_hook);
52                Box::pin(async move {
53                    let token = if let Some(current) = current_session()? {
54                        endpoints::sign_session_token(
55                            context,
56                            &options,
57                            current.session,
58                            current.user,
59                        )
60                        .await?
61                    } else {
62                        let Some(user) = current_session_user()? else {
63                            return Ok(PluginAfterHookAction::Continue(response));
64                        };
65                        let Some(user_id) = user
66                            .get("id")
67                            .and_then(|value| value.as_str())
68                            .map(str::to_owned)
69                        else {
70                            return Ok(PluginAfterHookAction::Continue(response));
71                        };
72                        let mut claims = match user {
73                            serde_json::Value::Object(map) => map,
74                            _ => JwtClaims::new(),
75                        };
76                        claims.insert("sub".to_owned(), serde_json::Value::String(user_id));
77                        sign::sign_jwt_with_options(context, claims, &options).await?
78                    };
79                    let mut response = response;
80                    let token = header::HeaderValue::from_str(&token)
81                        .map_err(|error| OpenAuthError::Api(error.to_string()))?;
82                    response.headers_mut().insert("set-auth-jwt", token);
83                    expose_auth_jwt_header(response.headers_mut())?;
84                    Ok(PluginAfterHookAction::Continue(response))
85                })
86            });
87    }
88
89    Ok(plugin)
90}
91
92fn expose_auth_jwt_header(headers: &mut header::HeaderMap) -> Result<(), OpenAuthError> {
93    let current = headers
94        .get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
95        .and_then(|value| value.to_str().ok())
96        .unwrap_or_default();
97    let mut values = current
98        .split(',')
99        .map(str::trim)
100        .filter(|value| !value.is_empty())
101        .map(str::to_owned)
102        .collect::<Vec<_>>();
103    if !values
104        .iter()
105        .any(|value| value.eq_ignore_ascii_case("set-auth-jwt"))
106    {
107        values.push("set-auth-jwt".to_owned());
108    }
109    let value = header::HeaderValue::from_str(&values.join(", "))
110        .map_err(|error| OpenAuthError::Api(error.to_string()))?;
111    headers.insert(header::ACCESS_CONTROL_EXPOSE_HEADERS, value);
112    Ok(())
113}