pub struct OpenidConnectPlugin {Show 21 fields
discovery: Option<String>,
jwks_uri_cfg: Option<String>,
introspection_endpoint: Option<String>,
client_id: Option<String>,
client_secret: Option<String>,
ssl_verify: bool,
timeout: Duration,
allowed_algs: Vec<Algorithm>,
valid_issuers: Vec<String>,
audience_claim: String,
audience_required: bool,
audience_match_client_id: bool,
set_userinfo_header: bool,
set_access_token_header: bool,
access_token_in_authorization_header: bool,
use_jwks: bool,
jwk_ttl: Duration,
resources: Arc<PluginResources>,
jwks_uri_resolved: Mutex<Option<String>>,
jwks_cache: Mutex<Option<CachedJwks>>,
interactive: Option<Interactive>,
}Expand description
Authenticates requests by validating a bearer access token via JWKS signature verification or token introspection.
Fields§
§discovery: Option<String>Well-known discovery URL; resolves jwks_uri when not given directly.
jwks_uri_cfg: Option<String>Explicit JWKS endpoint (takes precedence over discovery).
introspection_endpoint: Option<String>Introspection endpoint; used only when no JWKS source is configured.
client_id: Option<String>§client_secret: Option<String>§ssl_verify: bool§timeout: Duration§allowed_algs: Vec<Algorithm>Signature algorithms the token is allowed to be signed with.
valid_issuers: Vec<String>Issuers accepted for the iss claim; empty = do not validate issuer.
audience_claim: String§audience_required: bool§audience_match_client_id: bool§set_userinfo_header: bool§set_access_token_header: bool§use_jwks: boolTrue when a JWKS source (discovery/jwks_uri) is configured.
jwk_ttl: Duration§resources: Arc<PluginResources>§jwks_uri_resolved: Mutex<Option<String>>Lazily-resolved JWKS URI (from discovery), cached for the process.
jwks_cache: Mutex<Option<CachedJwks>>§interactive: Option<Interactive>Interactive login flow; None in bearer-only mode.
Implementations§
Source§impl OpenidConnectPlugin
impl OpenidConnectPlugin
Sourcepub fn from_config(
config: &HashMap<String, Value>,
resources: &Arc<PluginResources>,
) -> Result<Self, String>
pub fn from_config( config: &HashMap<String, Value>, resources: &Arc<PluginResources>, ) -> Result<Self, String>
Builds the plugin from node config (bearer-only subset).
Accepted keys:
discovery(string): OIDC discovery URL (.../.well-known/openid-configuration); used to resolvejwks_uri.jwks_uri(string): explicit JWKS endpoint; takes precedence overdiscoveryfor signature verification.introspection_endpoint(string): RFC 7662 introspection endpoint; used only when no JWKS source is configured.client_id(string): OAuth client id (introspection auth / audience).client_secret(string): OAuth client secret (introspection auth).bearer_only(bool, defaulttrue): must be true.falseis rejected at load — interactive login is not supported (see module docs).token_signing_alg_values_expected(string or array): permitted signature algorithms (e.g.RS256,ES256). Defaults toRS256, RS384, RS512, ES256, ES384.claim_validator.issuer.valid_issuers(array): acceptedissvalues.claim_validator.audience.{claim,required,match_with_client_id}: audience validation (claim defaults toaud).set_userinfo_header(bool, defaulttrue): base64-encode the claims into theX-Userinforequest header for the upstream.set_access_token_header(bool, defaulttrue) /access_token_in_authorization_header(bool, defaultfalse): forward the validated access token asX-Access-Token(or leave it inAuthorization).ssl_verify(bool, defaulttrue),timeout(integer seconds, default3).
Rejected at load: bearer_only: false, and configs with neither a JWKS
source (discovery/jwks_uri) nor an introspection_endpoint.
type: openid-connect
config:
discovery: https://idp.example.com/.well-known/openid-configuration
bearer_only: true
client_id: my-api
token_signing_alg_values_expected: RS256
claim_validator:
issuer:
valid_issuers: ["https://idp.example.com/"]
audience:
required: true
match_with_client_id: trueSourceasync fn jwks_uri(&self) -> Result<String, TokenError>
async fn jwks_uri(&self) -> Result<String, TokenError>
Resolves the JWKS URI, fetching the discovery document once if needed.
Every failure here is a genuine provider/infra problem, not the
caller’s token being bad, so it always classifies as TokenError::Infra.
Sourceasync fn get_jwks(&self, force: bool) -> Result<Vec<Jwk>, TokenError>
async fn get_jwks(&self, force: bool) -> Result<Vec<Jwk>, TokenError>
Returns the current JWKS, fetching/refreshing when stale or when
force is set (used on an unknown kid). Every failure here is a
genuine provider/infra problem, so it always classifies as
TokenError::Infra.
Sourceasync fn get(&self, url: &str) -> Result<OutboundResponse, OutboundError>
async fn get(&self, url: &str) -> Result<OutboundResponse, OutboundError>
Convenience GET through the shared outbound client.
Sourceasync fn validate_via_jwks(
&self,
token: &str,
) -> Result<HashMap<String, Value>, TokenError>
async fn validate_via_jwks( &self, token: &str, ) -> Result<HashMap<String, Value>, TokenError>
Validates the token via JWKS signature verification plus claim checks.
Provider/JWKS callout trouble classifies as TokenError::Infra; the
token itself being malformed, unsigned by a known key, unverifiable, or
failing an issuer/audience check classifies as TokenError::Denied.
Sourceasync fn validate_via_introspection(
&self,
token: &str,
) -> Result<HashMap<String, Value>, TokenError>
async fn validate_via_introspection( &self, token: &str, ) -> Result<HashMap<String, Value>, TokenError>
Validates the token via the introspection endpoint. Provider callout
trouble classifies as TokenError::Infra; the introspection response
itself declaring the token inactive, or failing claim checks,
classifies as TokenError::Denied.
Sourcefn validate_claims(&self, claims: &HashMap<String, Value>) -> Result<(), String>
fn validate_claims(&self, claims: &HashMap<String, Value>) -> Result<(), String>
Applies the configured issuer and audience claim checks.
Sourcefn attach(&self, ctx: &mut Context, claims: HashMap<String, Value>, token: &str)
fn attach(&self, ctx: &mut Context, claims: HashMap<String, Value>, token: &str)
Writes the validated claims into context.message and the configured
forwarding headers.
Sourcefn reject(
ctx: Context,
message: &str,
) -> Result<PluginOutput, PluginExecutionError>
fn reject( ctx: Context, message: &str, ) -> Result<PluginOutput, PluginExecutionError>
Builds a 401 rejection for a deliberate authentication failure (missing
bearer token, invalid/unverifiable token, CSRF/nonce/session-flow
failure) and exits on the denied port.
Sourcefn infra_error(ctx: Context, message: String) -> PluginExecutionError
fn infra_error(ctx: Context, message: String) -> PluginExecutionError
Builds a genuine infrastructure-failure Err (discovery, JWKS,
introspection, or token-endpoint callout that transport-failed,
returned a non-2xx status, or handed back unparseable data). Unlike
reject, this exits through the error port because the node could
not do its job, not because a presented credential was deliberately
refused — and the response says so (see provider_error): a 502
with {"error": "provider_error"} and no www-authenticate challenge,
so neither an API client nor a browser user mistakes an IdP outage for
a failed login.
Sourcefn store_error(ctx: Context, e: StoreError) -> PluginExecutionError
fn store_error(ctx: Context, e: StoreError) -> PluginExecutionError
Session-store outage: 503 through the error port. Deliberately NOT 401 — bouncing users to an IdP whose callback also cannot persist a session is a redirect loop disguised as an outage.
Sourceasync fn execute_interactive(
&self,
ctx: Context,
) -> Result<PluginOutput, PluginExecutionError>
async fn execute_interactive( &self, ctx: Context, ) -> Result<PluginOutput, PluginExecutionError>
Drives the interactive flow: session check, callback handling, or a fresh redirect to the identity provider.
Sourceasync fn read_session(
&self,
ctx: &Context,
) -> Result<Option<SessionData>, StoreError>
async fn read_session( &self, ctx: &Context, ) -> Result<Option<SessionData>, StoreError>
Reads the session cookie via the configured backend. Ok(None) =
unauthenticated; Err = store outage (503 via store_error).
Sourceasync fn refresh_if_needed(
&self,
ctx: Context,
flow: &Interactive,
raw: &str,
session: SessionData,
) -> ControlFlow<Result<PluginOutput, PluginExecutionError>, (Context, SessionData)>
async fn refresh_if_needed( &self, ctx: Context, flow: &Interactive, raw: &str, session: SessionData, ) -> ControlFlow<Result<PluginOutput, PluginExecutionError>, (Context, SessionData)>
Pre-attach refresh check (redis mode only, gated on refresh_enabled
and the access token being within 30s of expires_at): coordinates a
lock-guarded refresh so concurrent requests for the same session don’t
all hit the IdP.
ControlFlow::Continue carries the (possibly refreshed) session data
for the caller to attach as normal; ControlFlow::Break is an
immediate exit the caller must return directly — either a 503 store
error, or a fallback to re-login when the IdP-side refresh itself
failed. Nothing outside this function and Self::do_refresh ever
touches the lock: every branch below unlocks exactly once before
breaking or continuing.
Sourceasync fn do_refresh(
&self,
ctx: &Context,
flow: &Interactive,
id: &SessionId,
refresh_token: &str,
old: &SessionData,
) -> Result<SessionData, RefreshFailure>
async fn do_refresh( &self, ctx: &Context, flow: &Interactive, id: &SessionId, refresh_token: &str, old: &SessionData, ) -> Result<SessionData, RefreshFailure>
Performs the refresh callout, optional id_token re-validation, and
session rewrite. Never touches the lock — Self::refresh_if_needed
unlocks on every outcome of this call.
Sourceasync fn refresh_tokens(&self, refresh_token: &str) -> Result<Value, String>
async fn refresh_tokens(&self, refresh_token: &str) -> Result<Value, String>
Refreshes an access token at the token endpoint (RFC 6749 §6). Cloned
from Self::exchange_code’s request shape, with the refresh-token
grant body instead.
Sourceasync fn begin_auth(
&self,
ctx: Context,
) -> Result<PluginOutput, PluginExecutionError>
async fn begin_auth( &self, ctx: Context, ) -> Result<PluginOutput, PluginExecutionError>
Starts the flow: generate CSRF/nonce/PKCE, set the flow cookie, and redirect the browser to the IdP authorization endpoint.
Sourceasync fn handle_callback(
&self,
ctx: Context,
) -> Result<PluginOutput, PluginExecutionError>
async fn handle_callback( &self, ctx: Context, ) -> Result<PluginOutput, PluginExecutionError>
Handles the IdP redirect back: verify state, exchange the code, validate the id_token, seal a session cookie, and redirect to the original URL.
Sourcefn read_flow(&self, ctx: &Context) -> Option<FlowState>
fn read_flow(&self, ctx: &Context) -> Option<FlowState>
Reads and opens the transient flow cookie.
Sourceasync fn exchange_code(
&self,
token_endpoint: &str,
code: &str,
verifier: &str,
redirect_uri: &str,
) -> Result<Value, String>
async fn exchange_code( &self, token_endpoint: &str, code: &str, verifier: &str, redirect_uri: &str, ) -> Result<Value, String>
Exchanges an authorization code for tokens at the token endpoint.
Resolves the authorization endpoint (config or discovery).
Sourceasync fn token_endpoint(&self) -> Result<String, String>
async fn token_endpoint(&self) -> Result<String, String>
Resolves the token endpoint (config or discovery).
Trait Implementations§
Source§impl Plugin for OpenidConnectPlugin
impl Plugin for OpenidConnectPlugin
Source§fn plugin_type(&self) -> &str
fn plugin_type(&self) -> &str
Source§fn execute<'life0, 'async_trait>(
&'life0 self,
ctx: Context,
) -> Pin<Box<dyn Future<Output = Result<PluginOutput, PluginExecutionError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn execute<'life0, 'async_trait>(
&'life0 self,
ctx: Context,
) -> Pin<Box<dyn Future<Output = Result<PluginOutput, PluginExecutionError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn reads_response_body(&self) -> bool
fn reads_response_body(&self) -> bool
context.response.body. Read moreSource§fn cache_target(&self) -> Option<CacheTarget>
fn cache_target(&self) -> Option<CacheTarget>
proxy-cache half. Read moreAuto Trait Implementations§
impl !Freeze for OpenidConnectPlugin
impl !RefUnwindSafe for OpenidConnectPlugin
impl !UnwindSafe for OpenidConnectPlugin
impl Send for OpenidConnectPlugin
impl Sync for OpenidConnectPlugin
impl Unpin for OpenidConnectPlugin
impl UnsafeUnpin for OpenidConnectPlugin
Blanket Implementations§
§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more