From 18b9d7bc1f98ba33608dc1df92dac39f8b983ca1 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Wed, 18 Jun 2025 09:29:06 +0000 Subject: [PATCH] Add org.matrix.login.jwt support. Signed-off-by: Jason Volk --- Cargo.lock | 61 ++++++++++++++---- Cargo.toml | 7 +- src/admin/Cargo.toml | 1 + src/admin/debug/commands.rs | 62 +++++++++++++++++- src/admin/debug/mod.rs | 22 +++++++ src/api/client/session/jwt.rs | 117 ++++++++++++++++++++++++++++++++++ src/api/client/session/mod.rs | 8 ++- src/core/Cargo.toml | 1 + src/core/config/mod.rs | 103 +++++++++++++++++++++++++++++- src/core/mod.rs | 1 + tuwunel-example.toml | 66 +++++++++++++++++++ 11 files changed, 434 insertions(+), 15 deletions(-) create mode 100644 src/api/client/session/jwt.rs diff --git a/Cargo.lock b/Cargo.lock index 56eb6276..c7d127b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2096,6 +2096,21 @@ dependencies = [ "serde", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "konst" version = "0.3.16" @@ -2796,6 +2811,16 @@ dependencies = [ "syn", ] +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3384,7 +3409,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.10.1" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "assign", "js_int", @@ -3404,7 +3429,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.10.0" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "js_int", "ruma-common", @@ -3416,7 +3441,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "as_variant", "assign", @@ -3439,7 +3464,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.13.0" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "as_variant", "base64", @@ -3471,7 +3496,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "as_variant", "indexmap", @@ -3496,7 +3521,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "bytes", "headers", @@ -3518,7 +3543,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "js_int", "thiserror 2.0.12", @@ -3527,7 +3552,7 @@ dependencies = [ [[package]] name = "ruma-identity-service-api" version = "0.9.0" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "js_int", "ruma-common", @@ -3537,7 +3562,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "cfg-if", "proc-macro-crate", @@ -3552,7 +3577,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "js_int", "ruma-common", @@ -3564,7 +3589,7 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.15.0" -source = "git+https://github.com/matrix-construct/ruma?rev=3d082885f6532cc84ba65ebd2c3ff31b25a7022d#3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +source = "git+https://github.com/matrix-construct/ruma?rev=0155c2b33233bec9dece79d5134a9574b347f4c1#0155c2b33233bec9dece79d5134a9574b347f4c1" dependencies = [ "base64", "ed25519-dalek", @@ -4140,6 +4165,18 @@ dependencies = [ "quote", ] +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.12", + "time", +] + [[package]] name = "siphasher" version = "1.0.1" @@ -4793,6 +4830,7 @@ dependencies = [ "futures", "log", "ruma", + "serde", "serde_json", "serde_yaml", "tokio", @@ -4864,6 +4902,7 @@ dependencies = [ "http-body-util", "ipaddress", "itertools 0.14.0", + "jsonwebtoken", "ldap3", "libc", "libloading", diff --git a/Cargo.toml b/Cargo.toml index e3c400a0..d9282c0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -225,6 +225,11 @@ version = "0.1.3" [workspace.dependencies.itertools] version = "0.14.0" +[workspace.dependencies.jsonwebtoken] +version = "9.3.1" +default-features = false +features = ["use_pem"] + [workspace.dependencies.ldap3] git = "https://github.com/matrix-construct/ldap3" rev = "7d423314b9dbc66347284e38fc2b78c3d8f3d494" @@ -306,7 +311,7 @@ default-features = false [workspace.dependencies.ruma] git = "https://github.com/matrix-construct/ruma" -rev = "3d082885f6532cc84ba65ebd2c3ff31b25a7022d" +rev = "0155c2b33233bec9dece79d5134a9574b347f4c1" features = [ "compat", "rand", diff --git a/src/admin/Cargo.toml b/src/admin/Cargo.toml index 9b03297c..0a28a02b 100644 --- a/src/admin/Cargo.toml +++ b/src/admin/Cargo.toml @@ -85,6 +85,7 @@ ctor.workspace = true futures.workspace = true log.workspace = true ruma.workspace = true +serde.workspace = true serde_json.workspace = true serde_yaml.workspace = true tokio.workspace = true diff --git a/src/admin/debug/commands.rs b/src/admin/debug/commands.rs index d164c694..10bd0a26 100644 --- a/src/admin/debug/commands.rs +++ b/src/admin/debug/commands.rs @@ -2,6 +2,7 @@ use std::{ collections::HashMap, fmt::Write, iter::once, + str::FromStr, time::{Instant, SystemTime}, }; @@ -11,9 +12,10 @@ use ruma::{ OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId, api::federation::event::get_room_state, events::AnyStateEvent, serde::Raw, }; +use serde::Serialize; use tracing_subscriber::EnvFilter; use tuwunel_core::{ - Err, Result, debug_error, err, info, + Err, Result, debug_error, err, info, jwt, matrix::{ Event, pdu::{PduEvent, PduId, RawPduId}, @@ -22,6 +24,7 @@ use tuwunel_core::{ utils::{ stream::{IterStream, ReadyExt}, string::EMPTY, + time::now_secs, }, warn, }; @@ -975,3 +978,60 @@ pub(super) async fn trim_memory(&self) -> Result { writeln!(self, "done").await } + +#[admin_command] +pub(super) async fn create_jwt( + &self, + user: String, + exp_from_now: Option, + nbf_from_now: Option, + issuer: Option, + audience: Option, +) -> Result { + use jwt::{Algorithm, EncodingKey, Header, encode}; + + #[derive(Serialize)] + struct Claim { + sub: String, + iss: String, + aud: String, + exp: usize, + nbf: usize, + } + + let config = &self.services.config.jwt; + if config.format.as_str() != "HMAC" { + return Err!("This command only supports HMAC key format, not {}.", config.format); + } + + let key = EncodingKey::from_secret(config.key.as_ref()); + let alg = Algorithm::from_str(config.algorithm.as_str()).map_err(|e| { + err!(Config("jwt.algorithm", "JWT algorithm is not recognized or configured {e}")) + })?; + + let header = Header { alg, ..Default::default() }; + let claim = Claim { + sub: user, + + iss: issuer.unwrap_or_default(), + + aud: audience.unwrap_or_default(), + + exp: exp_from_now + .and_then(|val| now_secs().checked_add(val)) + .map(TryInto::try_into) + .and_then(Result::ok) + .unwrap_or(usize::MAX), + + nbf: nbf_from_now + .and_then(|val| now_secs().checked_add(val)) + .map(TryInto::try_into) + .and_then(Result::ok) + .unwrap_or(0), + }; + + encode(&header, &claim, &key) + .map_err(|e| err!("Failed to encode JWT: {e}")) + .map(async |token| self.write_str(&token).await)? + .await +} diff --git a/src/admin/debug/mod.rs b/src/admin/debug/mod.rs index b4c75705..c634abb7 100644 --- a/src/admin/debug/mod.rs +++ b/src/admin/debug/mod.rs @@ -234,6 +234,28 @@ pub(super) enum DebugCommand { level: Option, }, + /// - Create a JWT token for login + CreateJwt { + /// Localpart of the user's MXID + user: String, + + /// Set expiration time in seconds from now. + #[arg(long)] + exp_from_now: Option, + + /// Set not-before time in seconds from now. + #[arg(long)] + nbf_from_now: Option, + + /// Claim an issuer. + #[arg(long)] + issuer: Option, + + /// Claim an audience. + #[arg(long)] + audience: Option, + }, + /// - Developer test stubs #[command(subcommand)] #[allow(non_snake_case)] diff --git a/src/api/client/session/jwt.rs b/src/api/client/session/jwt.rs new file mode 100644 index 00000000..8016f90d --- /dev/null +++ b/src/api/client/session/jwt.rs @@ -0,0 +1,117 @@ +use std::str::FromStr; + +use jwt::{Algorithm, DecodingKey, Validation, decode}; +use ruma::{ + OwnedUserId, UserId, + api::client::session::login::v3::{Request, Token}, +}; +use serde::Deserialize; +use tuwunel_core::{Err, Result, at, config::JwtConfig, debug, err, jwt, warn}; +use tuwunel_service::Services; + +use crate::Ruma; + +#[derive(Debug, Deserialize)] +struct Claim { + /// Subject is the localpart of the User MXID + sub: String, +} + +pub(super) async fn handle_login( + services: &Services, + _body: &Ruma, + info: &Token, +) -> Result { + let config = &services.config.jwt; + + if !config.enable { + return Err!(Request(Unknown("JWT login is not enabled."))); + } + + let claim = validate(config, &info.token)?; + let local = claim.sub.to_lowercase(); + let server = &services.server.name; + let user_id = UserId::parse_with_server_name(local, server).map_err(|e| { + err!(Request(InvalidUsername("JWT subject is not a valid user MXID: {e}"))) + })?; + + if !services.users.exists(&user_id).await { + if !config.register_user { + return Err!(Request(NotFound("User {user_id} is not registered on this server."))); + } + + services + .users + .create(&user_id, Some("*"), Some("jwt")) + .await?; + } + + Ok(user_id) +} + +fn validate(config: &JwtConfig, token: &str) -> Result { + let verifier = init_verifier(config)?; + let validator = init_validator(config)?; + decode::(token, &verifier, &validator) + .map(|decoded| (decoded.header, decoded.claims)) + .inspect(|(head, claim)| debug!(?head, ?claim, "JWT token decoded")) + .map_err(|e| err!(Request(Forbidden("Invalid JWT token: {e}")))) + .map(at!(1)) +} + +fn init_verifier(config: &JwtConfig) -> Result { + let key = &config.key; + let format = config.format.as_str(); + + Ok(match format { + | "HMAC" => DecodingKey::from_secret(key.as_bytes()), + + | "HMACB64" => DecodingKey::from_base64_secret(key.as_str()) + .map_err(|e| err!(Config("jwt.key", "JWT key is not valid base64: {e}")))?, + + | "ECDSA" => DecodingKey::from_ec_pem(key.as_bytes()) + .map_err(|e| err!(Config("jwt.key", "JWT key is not valid PEM: {e}")))?, + + | _ => return Err!(Config("jwt.format", "Key format {format:?} is not supported.")), + }) +} + +fn init_validator(config: &JwtConfig) -> Result { + let alg = config.algorithm.as_str(); + let alg = Algorithm::from_str(alg).map_err(|e| { + err!(Config("jwt.algorithm", "JWT algorithm is not recognized or configured {e}")) + })?; + + let mut validator = Validation::new(alg); + let mut required_spec_claims: Vec<_> = ["sub"].into(); + + validator.validate_exp = config.validate_exp; + if config.require_exp { + required_spec_claims.push("exp"); + } + + validator.validate_nbf = config.validate_nbf; + if config.require_nbf { + required_spec_claims.push("nbf"); + } + + if !config.audience.is_empty() { + required_spec_claims.push("aud"); + validator.set_audience(&config.audience); + } + + if !config.issuer.is_empty() { + required_spec_claims.push("iss"); + validator.set_issuer(&config.issuer); + } + + if cfg!(debug_assertions) && !config.validate_signature { + warn!("JWT signature validation is disabled!"); + validator.insecure_disable_signature_validation(); + } + + validator.set_required_spec_claims(&required_spec_claims); + debug!(?validator, "JWT configured"); + + Ok(validator) +} diff --git a/src/api/client/session/mod.rs b/src/api/client/session/mod.rs index b4191e73..385acff9 100644 --- a/src/api/client/session/mod.rs +++ b/src/api/client/session/mod.rs @@ -1,4 +1,5 @@ mod appservice; +mod jwt; mod ldap; mod logout; mod password; @@ -9,7 +10,10 @@ use axum_client_ip::InsecureClientIp; use ruma::api::client::session::{ get_login_types::{ self, - v3::{ApplicationServiceLoginType, LoginType, PasswordLoginType, TokenLoginType}, + v3::{ + ApplicationServiceLoginType, JwtLoginType, LoginType, PasswordLoginType, + TokenLoginType, + }, }, login::{ self, @@ -39,6 +43,7 @@ pub(crate) async fn get_login_types_route( Ok(get_login_types::v3::Response::new(vec![ LoginType::Password(PasswordLoginType::default()), LoginType::ApplicationService(ApplicationServiceLoginType::default()), + LoginType::Jwt(JwtLoginType::default()), LoginType::Token(TokenLoginType { get_login_token: services.config.login_via_existing_session, }), @@ -69,6 +74,7 @@ pub(crate) async fn login_route( let user_id = match &body.login_info { | LoginInfo::Password(info) => password::handle_login(&services, &body, info).await?, | LoginInfo::Token(info) => token::handle_login(&services, &body, info).await?, + | LoginInfo::Jwt(info) => jwt::handle_login(&services, &body, info).await?, | LoginInfo::ApplicationService(info) => appservice::handle_login(&services, &body, info).await?, | _ => { diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index e6dc2fc4..cc1026c7 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -78,6 +78,7 @@ http-body-util.workspace = true http.workspace = true ipaddress.workspace = true itertools.workspace = true +jsonwebtoken.workspace = true ldap3.workspace = true libc.workspace = true libloading.workspace = true diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 9800cc04..bb217803 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -52,7 +52,7 @@ use crate::{Result, err, error::Error, utils::sys}; ### For more information, see: ### https://tuwunel.chat/configuration.html "#, - ignore = "catchall well_known tls blurhashing allow_invalid_tls_certificates ldap" + ignore = "catchall well_known tls blurhashing allow_invalid_tls_certificates ldap jwt" )] pub struct Config { /// The server_name is the pretty name of this server. It is used as a @@ -1814,6 +1814,10 @@ pub struct Config { #[serde(default)] pub ldap: LdapConfig, + // external structure; separate section + #[serde(default)] + pub jwt: JwtConfig, + #[serde(flatten)] #[allow(clippy::zero_sized_map_values)] // this is a catchall, the map shouldn't be zero at runtime @@ -1991,6 +1995,99 @@ pub struct LdapConfig { pub admin_filter: String, } +#[derive(Clone, Debug, Default, Deserialize)] +#[config_example_generator(filename = "tuwunel-example.toml", section = "global.jwt")] +pub struct JwtConfig { + /// Enable JWT logins + /// + /// default: false + #[serde(default)] + pub enable: bool, + + /// Validation key, also called 'secret' in Synapse config. The type of key + /// can be configured in 'format', but defaults to the common HMAC which + /// is a plaintext shared-secret, so you should keep this value private. + /// + /// display: sensitive + /// default: + #[serde(default, alias = "secret")] + pub key: String, + + /// Format of the 'key'. Only HMAC, ECDSA, and B64HMAC are supported + /// Binary keys cannot be pasted into this config, so B64HMAC is an + /// alternative to HMAC for properly random secret strings. + /// - HMAC is a plaintext shared-secret private-key. + /// - B64HMAC is a base64-encoded version of HMAC. + /// - ECDSA is a PEM-encoded public-key. + /// + /// default: "HMAC" + #[serde(default = "default_jwt_format")] + pub format: String, + + /// Automatically create new user from a valid claim, otherwise access is + /// denied for an unknown even with an authentic token. + /// + /// default: true + #[serde(default = "true_fn")] + pub register_user: bool, + + /// JWT algorithm + /// + /// default: "HS256" + #[serde(default = "default_jwt_algorithm")] + pub algorithm: String, + + /// Optional audience claim list. The token must claim one or more values + /// from this list when set. + /// + /// default: [] + #[serde(default)] + pub audience: Vec, + + /// Optional issuer claim list. The token must claim one or more values + /// from this list when set. + /// + /// default: [] + #[serde(default)] + pub issuer: Vec, + + /// Require expiration claim in the token. This defaults to false for + /// synapse migration compatibility. + /// + /// default: false + #[serde(default)] + pub require_exp: bool, + + /// Require not-before claim in the token. This defaults to false for + /// synapse migration compatibility. + /// + /// default: false + #[serde(default)] + pub require_nbf: bool, + + /// Validate expiration time of the token when present. Whether or not it is + /// required depends on require_exp, but when present this ensures the token + /// is not used after a time. + /// + /// default: true + #[serde(default = "true_fn")] + pub validate_exp: bool, + + /// Validate not-before time of the token when present. Whether or not it is + /// required depends on require_nbf, but when present this ensures the token + /// is not used before a time. + /// + /// default: true + #[serde(default = "true_fn")] + pub validate_nbf: bool, + + /// Bypass validation for diagnostic/debug use only. + /// + /// default: true + #[serde(default = "true_fn")] + pub validate_signature: bool, +} + #[derive(Deserialize, Clone, Debug)] #[serde(transparent)] struct ListeningPort { @@ -2392,3 +2489,7 @@ fn default_ldap_uid_attribute() -> String { String::from("uid") } fn default_ldap_mail_attribute() -> String { String::from("mail") } fn default_ldap_name_attribute() -> String { String::from("givenName") } + +fn default_jwt_algorithm() -> String { "HS256".to_owned() } + +fn default_jwt_format() -> String { "HMAC".to_owned() } diff --git a/src/core/mod.rs b/src/core/mod.rs index 44178b42..fcda4f8c 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -14,6 +14,7 @@ pub mod utils; pub use ::arrayvec; pub use ::http; +pub use ::jsonwebtoken as jwt; pub use ::ruma; pub use ::smallstr; pub use ::smallvec; diff --git a/tuwunel-example.toml b/tuwunel-example.toml index 0a6c1719..08ca97d0 100644 --- a/tuwunel-example.toml +++ b/tuwunel-example.toml @@ -1712,3 +1712,69 @@ # example: "(objectClass=tuwunelAdmin)" or "(uid={username})" # #admin_filter = false + +[global.jwt] + +# Enable JWT logins +# +#enable = false + +# Validation key, also called 'secret' in Synapse config. The type of key +# can be configured in 'format', but defaults to the common HMAC which +# is a plaintext shared-secret, so you should keep this value private. +# +#key = + +# Format of the 'key'. Only HMAC, ECDSA, and B64HMAC are supported +# Binary keys cannot be pasted into this config, so B64HMAC is an +# alternative to HMAC for properly random secret strings. +# - HMAC is a plaintext shared-secret private-key. +# - B64HMAC is a base64-encoded version of HMAC. +# - ECDSA is a PEM-encoded public-key. +# +#format = "HMAC" + +# Automatically create new user from a valid claim, otherwise access is +# denied for an unknown even with an authentic token. +# +#register_user = true + +# JWT algorithm +# +#algorithm = "HS256" + +# Optional audience claim list. The token must claim one or more values +# from this list when set. +# +#audience = [] + +# Optional issuer claim list. The token must claim one or more values +# from this list when set. +# +#issuer = [] + +# Require expiration claim in the token. This defaults to false for +# synapse migration compatibility. +# +#require_exp = false + +# Require not-before claim in the token. This defaults to false for +# synapse migration compatibility. +# +#require_nbf = false + +# Validate expiration time of the token when present. Whether or not it is +# required depends on require_exp, but when present this ensures the token +# is not used after a time. +# +#validate_exp = true + +# Validate not-before time of the token when present. Whether or not it is +# required depends on require_nbf, but when present this ensures the token +# is not used before a time. +# +#validate_nbf = true + +# Bypass validation for diagnostic/debug use only. +# +#validate_signature = true