rustauth_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, JwtOptionsBuilder, JwtSessionContext,
18 JwtSignHandler, JwtSigningOptions,
19};
20pub use schema::JwtSchemaOptions;
21pub use sign::sign_jwt;
22pub use verify::{verify_jwt, verify_jwt_with_options};
23
24use std::sync::Arc;
25
26use http::header;
27use rustauth_core::context::request_state::{current_session, current_session_user};
28use rustauth_core::error::RustAuthError;
29use rustauth_core::plugin::{AuthPlugin, PluginAfterHookAction};
30
31pub const UPSTREAM_PLUGIN_ID: &str = "jwt";
32
33pub fn jwt(options: JwtOptions) -> Result<AuthPlugin, RustAuthError> {
34 options.validate()?;
35 let options = Arc::new(options);
36 let mut plugin = AuthPlugin::new(UPSTREAM_PLUGIN_ID)
37 .with_version(crate::VERSION)
38 .with_state(Arc::clone(&options))
39 .with_schema(schema::jwks_schema(&options.schema))
40 .with_endpoint(endpoints::jwks_endpoint(Arc::clone(&options)))
41 .with_endpoint(endpoints::token_endpoint(Arc::clone(&options)))
42 .with_endpoint(endpoints::sign_jwt_endpoint(Arc::clone(&options)))
43 .with_endpoint(endpoints::verify_jwt_endpoint(Arc::clone(&options)));
44
45 if !options.disable_setting_jwt_header {
46 let options_for_hook = Arc::clone(&options);
47 plugin =
48 plugin.with_async_after_hook("/get-session", move |context, _request, response| {
49 let options = Arc::clone(&options_for_hook);
50 Box::pin(async move {
51 let token = if let Some(current) = current_session()? {
52 endpoints::sign_session_token(
53 context,
54 &options,
55 current.session,
56 current.user,
57 )
58 .await?
59 } else {
60 let Some(user) = current_session_user()? else {
61 return Ok(PluginAfterHookAction::Continue(response));
62 };
63 let Some(user_id) = user
64 .get("id")
65 .and_then(|value| value.as_str())
66 .map(str::to_owned)
67 else {
68 return Ok(PluginAfterHookAction::Continue(response));
69 };
70 let mut claims = match user {
71 serde_json::Value::Object(map) => map,
72 _ => JwtClaims::new(),
73 };
74 claims.insert("sub".to_owned(), serde_json::Value::String(user_id));
75 sign::sign_jwt_with_options(context, claims, &options).await?
76 };
77 let mut response = response;
78 let token = header::HeaderValue::from_str(&token)
79 .map_err(|error| RustAuthError::Api(error.to_string()))?;
80 response.headers_mut().insert("set-auth-jwt", token);
81 expose_auth_jwt_header(response.headers_mut())?;
82 Ok(PluginAfterHookAction::Continue(response))
83 })
84 });
85 }
86
87 Ok(plugin)
88}
89
90pub fn jwt_options_from_context(
91 context: &rustauth_core::context::AuthContext,
92) -> Option<Arc<JwtOptions>> {
93 context
94 .plugin(UPSTREAM_PLUGIN_ID)
95 .and_then(|plugin| plugin.state::<JwtOptions>())
96}
97
98fn expose_auth_jwt_header(headers: &mut header::HeaderMap) -> Result<(), RustAuthError> {
99 let current = headers
100 .get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
101 .and_then(|value| value.to_str().ok())
102 .unwrap_or_default();
103 let mut values = current
104 .split(',')
105 .map(str::trim)
106 .filter(|value| !value.is_empty())
107 .map(str::to_owned)
108 .collect::<Vec<_>>();
109 if !values
110 .iter()
111 .any(|value| value.eq_ignore_ascii_case("set-auth-jwt"))
112 {
113 values.push("set-auth-jwt".to_owned());
114 }
115 let value = header::HeaderValue::from_str(&values.join(", "))
116 .map_err(|error| RustAuthError::Api(error.to_string()))?;
117 headers.insert(header::ACCESS_CONTROL_EXPOSE_HEADERS, value);
118 Ok(())
119}