openauth_plugins/jwt/
mod.rs1mod 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}