This commit is contained in:
Paul Zinselmeyer 2025-11-25 14:13:33 +01:00
commit a766608f55
Signed by: pfzetto
SSH key fingerprint: SHA256:BOdea0+zY02mYo29j2zzK6uVpcc3Gkp4w6C7YrHbN8A
5 changed files with 35 additions and 30 deletions

View file

@ -7,7 +7,6 @@ version = "0.1.0"
axum = { version = "0.8", features = ["macros"] } axum = { version = "0.8", features = ["macros"] }
axum-oidc = { path = "./../.." } axum-oidc = { path = "./../.." }
dotenvy = "0.15" dotenvy = "0.15"
openidconnect = "4.0.1"
tokio = { version = "1.48.0", features = ["macros", "net", "rt-multi-thread"] } tokio = { version = "1.48.0", features = ["macros", "net", "rt-multi-thread"] }
tower = "0.5" tower = "0.5"
tower-sessions = "0.14" tower-sessions = "0.14"

View file

@ -1,20 +1,22 @@
use axum::{ use axum::{
Router,
error_handling::HandleErrorLayer, error_handling::HandleErrorLayer,
http::Uri, http::Uri,
response::IntoResponse, response::IntoResponse,
routing::{any, get}, routing::{any, get},
Router,
}; };
use axum_oidc::{ use axum_oidc::{
error::MiddlewareError, handle_oidc_redirect, Audience, ClientId, ClientSecret,
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer, EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer,
OidcRpInitiatedLogout, OidcRpInitiatedLogout,
error::MiddlewareError,
handle_oidc_redirect,
openidconnect::{Audience, ClientId, ClientSecret, IssuerUrl, Scope},
}; };
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tower::ServiceBuilder; use tower::ServiceBuilder;
use tower_sessions::{ use tower_sessions::{
cookie::{time::Duration, SameSite},
Expiry, MemoryStore, SessionManagerLayer, Expiry, MemoryStore, SessionManagerLayer,
cookie::{SameSite, time::Duration},
}; };
use tracing::Level; use tracing::Level;
@ -47,15 +49,19 @@ async fn main() {
.with_default_http_client() .with_default_http_client()
.with_redirect_url(Uri::from_static("http://localhost:8080/oidc")) .with_redirect_url(Uri::from_static("http://localhost:8080/oidc"))
.with_client_id(ClientId::new(client_id)) .with_client_id(ClientId::new(client_id))
.add_scope("profile") .add_scope(Scope::new("profile".into()))
.add_scope("email") .add_scope(Scope::new("email".into()))
// Optional: add untrusted audiences. If the `aud` claim contains any of these audiences, the token is rejected. // Optional: add untrusted audiences. If the `aud` claim contains any of these audiences, the token is rejected.
.add_untrusted_audience(Audience::new("123456789".to_string())); .add_untrusted_audience(Audience::new("123456789".to_string()));
if let Some(client_secret) = client_secret { if let Some(client_secret) = client_secret {
oidc_client = oidc_client.with_client_secret(ClientSecret::new(client_secret)); oidc_client = oidc_client.with_client_secret(ClientSecret::new(client_secret));
} }
let oidc_client = oidc_client.discover(issuer).await.unwrap().build(); let oidc_client = oidc_client
.discover(IssuerUrl::new(issuer.into()).expect("Invalid IssuerUrl"))
.await
.unwrap()
.build();
let oidc_auth_service = ServiceBuilder::new() let oidc_auth_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|e: MiddlewareError| async { .layer(HandleErrorLayer::new(|e: MiddlewareError| async {

View file

@ -1,7 +1,9 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use http::Uri; use http::Uri;
use openidconnect::{Audience, ClientId, ClientSecret, IssuerUrl}; use openidconnect::{
Audience, AuthenticationContextClass, ClientId, ClientSecret, IssuerUrl, Scope,
};
use crate::{error::Error, AdditionalClaims, Client, OidcClient, ProviderMetadata}; use crate::{error::Error, AdditionalClaims, Client, OidcClient, ProviderMetadata};
@ -21,8 +23,8 @@ pub struct Builder<AC: AdditionalClaims, Credentials, Client, HttpClient, Redire
http_client: HttpClient, http_client: HttpClient,
redirect_url: RedirectUrl, redirect_url: RedirectUrl,
end_session_endpoint: Option<Uri>, end_session_endpoint: Option<Uri>,
scopes: Vec<Box<str>>, scopes: Vec<Scope>,
auth_context_class: Option<Box<str>>, auth_context_class: Option<AuthenticationContextClass>,
untrusted_audiences: Vec<Audience>, untrusted_audiences: Vec<Audience>,
_ac: PhantomData<AC>, _ac: PhantomData<AC>,
} }
@ -41,7 +43,7 @@ impl<AC: AdditionalClaims> Builder<AC, (), (), (), ()> {
http_client: (), http_client: (),
redirect_url: (), redirect_url: (),
end_session_endpoint: None, end_session_endpoint: None,
scopes: vec![Box::from("openid")], scopes: vec![Scope::new("openid".to_string())],
auth_context_class: None, auth_context_class: None,
untrusted_audiences: Vec::new(), untrusted_audiences: Vec::new(),
_ac: PhantomData, _ac: PhantomData,
@ -58,20 +60,20 @@ impl<AC: AdditionalClaims> OidcClient<AC> {
impl<AC: AdditionalClaims, CREDS, CLIENT, HTTP, RURL> Builder<AC, CREDS, CLIENT, HTTP, RURL> { impl<AC: AdditionalClaims, CREDS, CLIENT, HTTP, RURL> Builder<AC, CREDS, CLIENT, HTTP, RURL> {
/// add a scope to existing (default) scopes /// add a scope to existing (default) scopes
pub fn add_scope(mut self, scope: impl Into<Box<str>>) -> Self { pub fn add_scope(mut self, scope: Scope) -> Self {
self.scopes.push(scope.into()); self.scopes.push(scope);
self self
} }
/// replace scopes (including default) /// replace scopes (including default)
pub fn with_scopes(mut self, scopes: impl Iterator<Item = impl Into<Box<str>>>) -> Self { pub fn with_scopes(mut self, scopes: Vec<Scope>) -> Self {
self.scopes = scopes.map(|x| x.into()).collect::<Vec<_>>(); self.scopes = scopes;
self self
} }
/// authenticate with Authentication Context Class Reference /// authenticate with Authentication Context Class Reference
pub fn with_auth_context_class(mut self, acr: impl Into<Box<str>>) -> Self { pub fn with_auth_context_class(mut self, acr: AuthenticationContextClass) -> Self {
self.auth_context_class = Some(acr.into()); self.auth_context_class = Some(acr);
self self
} }
@ -212,14 +214,13 @@ impl<AC: AdditionalClaims> Builder<AC, ClientCredentials, (), HttpClient, Redire
/// discover issuer details /// discover issuer details
pub async fn discover( pub async fn discover(
self, self,
issuer: String, issuer: IssuerUrl,
) -> Result< ) -> Result<
Builder<AC, ClientCredentials, OpenidconnectClient<AC>, HttpClient, RedirectUrl>, Builder<AC, ClientCredentials, OpenidconnectClient<AC>, HttpClient, RedirectUrl>,
Error, Error,
> { > {
let issuer_url = IssuerUrl::new(issuer)?;
let http_client = self.http_client.0.clone(); let http_client = self.http_client.0.clone();
let provider_metadata = ProviderMetadata::discover_async(issuer_url, &http_client); let provider_metadata = ProviderMetadata::discover_async(issuer, &http_client);
Self::manual(self, provider_metadata.await?) Self::manual(self, provider_metadata.await?)
} }

View file

@ -12,9 +12,9 @@ use openidconnect::{
CoreResponseMode, CoreResponseType, CoreRevocableToken, CoreRevocationErrorResponse, CoreResponseMode, CoreResponseType, CoreRevocableToken, CoreRevocationErrorResponse,
CoreSubjectIdentifierType, CoreTokenIntrospectionResponse, CoreTokenType, CoreSubjectIdentifierType, CoreTokenIntrospectionResponse, CoreTokenType,
}, },
AccessToken, CsrfToken, EmptyExtraTokenFields, EndpointMaybeSet, EndpointNotSet, EndpointSet, AccessToken, Audience, AuthenticationContextClass, ClientId, CsrfToken, EmptyExtraTokenFields,
IdTokenFields, Nonce, PkceCodeVerifier, RefreshToken, StandardErrorResponse, EndpointMaybeSet, EndpointNotSet, EndpointSet, IdTokenFields, Nonce, PkceCodeVerifier,
StandardTokenResponse, RefreshToken, Scope, StandardErrorResponse, StandardTokenResponse,
}; };
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
@ -27,7 +27,7 @@ mod middleware;
pub use extractor::{OidcAccessToken, OidcClaims, OidcRpInitiatedLogout, OidcUserInfo}; pub use extractor::{OidcAccessToken, OidcClaims, OidcRpInitiatedLogout, OidcUserInfo};
pub use handler::handle_oidc_redirect; pub use handler::handle_oidc_redirect;
pub use middleware::{OidcAuthLayer, OidcAuthMiddleware, OidcLoginLayer, OidcLoginMiddleware}; pub use middleware::{OidcAuthLayer, OidcAuthMiddleware, OidcLoginLayer, OidcLoginMiddleware};
pub use openidconnect::{Audience, ClientId, ClientSecret}; pub use openidconnect;
const SESSION_KEY: &str = "axum-oidc"; const SESSION_KEY: &str = "axum-oidc";
@ -102,12 +102,12 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
/// OpenID Connect Client /// OpenID Connect Client
#[derive(Clone)] #[derive(Clone)]
pub struct OidcClient<AC: AdditionalClaims> { pub struct OidcClient<AC: AdditionalClaims> {
scopes: Vec<Box<str>>, scopes: Vec<Scope>,
client_id: ClientId, client_id: ClientId,
client: Client<AC>, client: Client<AC>,
http_client: reqwest::Client, http_client: reqwest::Client,
end_session_endpoint: Option<Uri>, end_session_endpoint: Option<Uri>,
auth_context_class: Option<Box<str>>, auth_context_class: Option<AuthenticationContextClass>,
untrusted_audiences: Vec<Audience>, untrusted_audiences: Vec<Audience>,
} }

View file

@ -16,8 +16,8 @@ use tower_sessions::Session;
use openidconnect::{ use openidconnect::{
core::{CoreAuthenticationFlow, CoreErrorResponseType, CoreGenderClaim, CoreJsonWebKey}, core::{CoreAuthenticationFlow, CoreErrorResponseType, CoreGenderClaim, CoreJsonWebKey},
AccessToken, AccessTokenHash, AuthenticationContextClass, CsrfToken, IdTokenClaims, AccessToken, AccessTokenHash, CsrfToken, IdTokenClaims, IdTokenVerifier, Nonce,
IdTokenVerifier, Nonce, OAuth2TokenResponse, PkceCodeChallenge, RefreshToken, OAuth2TokenResponse, PkceCodeChallenge, RefreshToken,
RequestTokenError::ServerResponse, RequestTokenError::ServerResponse,
Scope, TokenResponse, UserInfoClaims, Scope, TokenResponse, UserInfoClaims,
}; };
@ -143,8 +143,7 @@ where
} }
if let Some(acr) = oidcclient.auth_context_class { if let Some(acr) = oidcclient.auth_context_class {
auth = auth auth = auth.add_auth_context_value(acr);
.add_auth_context_value(AuthenticationContextClass::new(acr.into()));
} }
auth.set_pkce_challenge(pkce_challenge).url() auth.set_pkce_challenge(pkce_challenge).url()