mirror of
https://codeberg.org/pfzetto/axum-oidc
synced 2025-12-08 06:05:16 +01:00
chore(deps): Update to openidconnect 0.4
Signed-off-by: MATILLAT Quentin <qmatillat@gmail.com>
This commit is contained in:
parent
f0d9126652
commit
2800b88b82
5 changed files with 68 additions and 118 deletions
|
|
@ -19,8 +19,8 @@ tower-service = "0.3"
|
||||||
tower-layer = "0.3"
|
tower-layer = "0.3"
|
||||||
tower-sessions = { version = "0.14", default-features = false, features = [ "axum-core" ] }
|
tower-sessions = { version = "0.14", default-features = false, features = [ "axum-core" ] }
|
||||||
http = "1.2"
|
http = "1.2"
|
||||||
openidconnect = "3.5"
|
openidconnect = "4.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
reqwest = { version = "0.11", default-features = false }
|
reqwest = { version = "0.12", default-features = false }
|
||||||
urlencoding = "2.1"
|
urlencoding = "2.1"
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ dotenvy = "0.15"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
testcontainers = "0.23"
|
testcontainers = "0.23"
|
||||||
tokio = { version = "1.43", features = ["rt-multi-thread"] }
|
tokio = { version = "1.43", features = ["rt-multi-thread"] }
|
||||||
reqwest = { version = "0.11", features = ["rustls-tls"], default-features = false }
|
reqwest = { version = "0.12", features = ["rustls-tls"], default-features = false }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
headless_chrome = "1.0"
|
headless_chrome = "1.0"
|
||||||
|
|
|
||||||
16
src/error.rs
16
src/error.rs
|
|
@ -16,11 +16,13 @@ pub enum ExtractorError {
|
||||||
|
|
||||||
#[error("could not build rp initiated logout uri")]
|
#[error("could not build rp initiated logout uri")]
|
||||||
FailedToCreateRpInitiatedLogoutUri,
|
FailedToCreateRpInitiatedLogoutUri,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum MiddlewareError {
|
pub enum MiddlewareError {
|
||||||
|
#[error("configuration: {0:?}")]
|
||||||
|
Configuration(#[from] openidconnect::ConfigurationError),
|
||||||
|
|
||||||
#[error("access token hash invalid")]
|
#[error("access token hash invalid")]
|
||||||
AccessTokenHashInvalid,
|
AccessTokenHashInvalid,
|
||||||
|
|
||||||
|
|
@ -33,6 +35,9 @@ pub enum MiddlewareError {
|
||||||
#[error("signing: {0:?}")]
|
#[error("signing: {0:?}")]
|
||||||
Signing(#[from] openidconnect::SigningError),
|
Signing(#[from] openidconnect::SigningError),
|
||||||
|
|
||||||
|
#[error("signature verification: {0:?}")]
|
||||||
|
Signature(#[from] openidconnect::SignatureVerificationError),
|
||||||
|
|
||||||
#[error("claims verification: {0:?}")]
|
#[error("claims verification: {0:?}")]
|
||||||
ClaimsVerification(#[from] openidconnect::ClaimsVerificationError),
|
ClaimsVerification(#[from] openidconnect::ClaimsVerificationError),
|
||||||
|
|
||||||
|
|
@ -49,7 +54,7 @@ pub enum MiddlewareError {
|
||||||
RequestToken(
|
RequestToken(
|
||||||
#[from]
|
#[from]
|
||||||
openidconnect::RequestTokenError<
|
openidconnect::RequestTokenError<
|
||||||
openidconnect::reqwest::Error<reqwest::Error>,
|
openidconnect::HttpClientError<openidconnect::reqwest::Error>,
|
||||||
StandardErrorResponse<CoreErrorResponseType>,
|
StandardErrorResponse<CoreErrorResponseType>,
|
||||||
>,
|
>,
|
||||||
),
|
),
|
||||||
|
|
@ -76,7 +81,12 @@ pub enum Error {
|
||||||
InvalidEndSessionEndpoint(http::uri::InvalidUri),
|
InvalidEndSessionEndpoint(http::uri::InvalidUri),
|
||||||
|
|
||||||
#[error("discovery: {0:?}")]
|
#[error("discovery: {0:?}")]
|
||||||
Discovery(#[from] openidconnect::DiscoveryError<openidconnect::reqwest::Error<reqwest::Error>>),
|
Discovery(
|
||||||
|
#[from]
|
||||||
|
openidconnect::DiscoveryError<
|
||||||
|
openidconnect::HttpClientError<openidconnect::reqwest::Error>,
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
|
||||||
#[error("extractor: {0:?}")]
|
#[error("extractor: {0:?}")]
|
||||||
Extractor(#[from] ExtractorError),
|
Extractor(#[from] ExtractorError),
|
||||||
|
|
|
||||||
70
src/lib.rs
70
src/lib.rs
|
|
@ -8,14 +8,13 @@ use http::Uri;
|
||||||
use openidconnect::{
|
use openidconnect::{
|
||||||
core::{
|
core::{
|
||||||
CoreAuthDisplay, CoreAuthPrompt, CoreClaimName, CoreClaimType, CoreClientAuthMethod,
|
CoreAuthDisplay, CoreAuthPrompt, CoreClaimName, CoreClaimType, CoreClientAuthMethod,
|
||||||
CoreErrorResponseType, CoreGenderClaim, CoreGrantType, CoreJsonWebKey, CoreJsonWebKeyType,
|
CoreErrorResponseType, CoreGenderClaim, CoreGrantType, CoreJsonWebKey,
|
||||||
CoreJsonWebKeyUse, CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm,
|
CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, CoreJwsSigningAlgorithm,
|
||||||
CoreJwsSigningAlgorithm, CoreResponseMode, CoreResponseType, CoreRevocableToken,
|
CoreResponseMode, CoreResponseType, CoreRevocableToken, CoreRevocationErrorResponse,
|
||||||
CoreRevocationErrorResponse, CoreSubjectIdentifierType, CoreTokenIntrospectionResponse,
|
CoreSubjectIdentifierType, CoreTokenIntrospectionResponse, CoreTokenType,
|
||||||
CoreTokenType,
|
|
||||||
},
|
},
|
||||||
AccessToken, ClientId, ClientSecret, CsrfToken, EmptyExtraTokenFields, HttpRequest,
|
AccessToken, ClientId, ClientSecret, CsrfToken, EmptyExtraTokenFields, EndpointMaybeSet,
|
||||||
HttpResponse, IdTokenFields, IssuerUrl, Nonce, PkceCodeVerifier, RefreshToken,
|
EndpointNotSet, EndpointSet, IdTokenFields, IssuerUrl, Nonce, PkceCodeVerifier, RefreshToken,
|
||||||
StandardErrorResponse, StandardTokenResponse,
|
StandardErrorResponse, StandardTokenResponse,
|
||||||
};
|
};
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
@ -41,7 +40,6 @@ type OidcTokenResponse<AC> = StandardTokenResponse<
|
||||||
CoreGenderClaim,
|
CoreGenderClaim,
|
||||||
CoreJweContentEncryptionAlgorithm,
|
CoreJweContentEncryptionAlgorithm,
|
||||||
CoreJwsSigningAlgorithm,
|
CoreJwsSigningAlgorithm,
|
||||||
CoreJsonWebKeyType,
|
|
||||||
>,
|
>,
|
||||||
CoreTokenType,
|
CoreTokenType,
|
||||||
>;
|
>;
|
||||||
|
|
@ -51,25 +49,34 @@ pub type IdToken<AZ> = openidconnect::IdToken<
|
||||||
CoreGenderClaim,
|
CoreGenderClaim,
|
||||||
CoreJweContentEncryptionAlgorithm,
|
CoreJweContentEncryptionAlgorithm,
|
||||||
CoreJwsSigningAlgorithm,
|
CoreJwsSigningAlgorithm,
|
||||||
CoreJsonWebKeyType,
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type Client<AC> = openidconnect::Client<
|
type Client<
|
||||||
|
AC,
|
||||||
|
HasAuthUrl = EndpointSet,
|
||||||
|
HasDeviceAuthUrl = EndpointNotSet,
|
||||||
|
HasIntrospectionUrl = EndpointNotSet,
|
||||||
|
HasRevocationUrl = EndpointNotSet,
|
||||||
|
HasTokenUrl = EndpointMaybeSet,
|
||||||
|
HasUserInfoUrl = EndpointMaybeSet,
|
||||||
|
> = openidconnect::Client<
|
||||||
AC,
|
AC,
|
||||||
CoreAuthDisplay,
|
CoreAuthDisplay,
|
||||||
CoreGenderClaim,
|
CoreGenderClaim,
|
||||||
CoreJweContentEncryptionAlgorithm,
|
CoreJweContentEncryptionAlgorithm,
|
||||||
CoreJwsSigningAlgorithm,
|
|
||||||
CoreJsonWebKeyType,
|
|
||||||
CoreJsonWebKeyUse,
|
|
||||||
CoreJsonWebKey,
|
CoreJsonWebKey,
|
||||||
CoreAuthPrompt,
|
CoreAuthPrompt,
|
||||||
StandardErrorResponse<CoreErrorResponseType>,
|
StandardErrorResponse<CoreErrorResponseType>,
|
||||||
OidcTokenResponse<AC>,
|
OidcTokenResponse<AC>,
|
||||||
CoreTokenType,
|
|
||||||
CoreTokenIntrospectionResponse,
|
CoreTokenIntrospectionResponse,
|
||||||
CoreRevocableToken,
|
CoreRevocableToken,
|
||||||
CoreRevocationErrorResponse,
|
CoreRevocationErrorResponse,
|
||||||
|
HasAuthUrl,
|
||||||
|
HasDeviceAuthUrl,
|
||||||
|
HasIntrospectionUrl,
|
||||||
|
HasRevocationUrl,
|
||||||
|
HasTokenUrl,
|
||||||
|
HasUserInfoUrl,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
pub type ProviderMetadata = openidconnect::ProviderMetadata<
|
pub type ProviderMetadata = openidconnect::ProviderMetadata<
|
||||||
|
|
@ -81,9 +88,6 @@ pub type ProviderMetadata = openidconnect::ProviderMetadata<
|
||||||
CoreGrantType,
|
CoreGrantType,
|
||||||
CoreJweContentEncryptionAlgorithm,
|
CoreJweContentEncryptionAlgorithm,
|
||||||
CoreJweKeyManagementAlgorithm,
|
CoreJweKeyManagementAlgorithm,
|
||||||
CoreJwsSigningAlgorithm,
|
|
||||||
CoreJsonWebKeyType,
|
|
||||||
CoreJsonWebKeyUse,
|
|
||||||
CoreJsonWebKey,
|
CoreJsonWebKey,
|
||||||
CoreResponseMode,
|
CoreResponseMode,
|
||||||
CoreResponseType,
|
CoreResponseType,
|
||||||
|
|
@ -197,38 +201,8 @@ impl<AC: AdditionalClaims> OidcClient<AC> {
|
||||||
//TODO remove borrow with next breaking version
|
//TODO remove borrow with next breaking version
|
||||||
client: &reqwest::Client,
|
client: &reqwest::Client,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
// modified version of `openidconnect::reqwest::async_client::async_http_client`.
|
|
||||||
let async_http_client = |request: HttpRequest| async move {
|
|
||||||
let mut request_builder = client
|
|
||||||
.request(request.method, request.url.as_str())
|
|
||||||
.body(request.body);
|
|
||||||
for (name, value) in &request.headers {
|
|
||||||
request_builder = request_builder.header(name.as_str(), value.as_bytes());
|
|
||||||
}
|
|
||||||
let request = request_builder
|
|
||||||
.build()
|
|
||||||
.map_err(openidconnect::reqwest::Error::Reqwest)?;
|
|
||||||
|
|
||||||
let response = client
|
|
||||||
.execute(request)
|
|
||||||
.await
|
|
||||||
.map_err(openidconnect::reqwest::Error::Reqwest)?;
|
|
||||||
|
|
||||||
let status_code = response.status();
|
|
||||||
let headers = response.headers().to_owned();
|
|
||||||
let chunks = response
|
|
||||||
.bytes()
|
|
||||||
.await
|
|
||||||
.map_err(openidconnect::reqwest::Error::Reqwest)?;
|
|
||||||
Ok(HttpResponse {
|
|
||||||
status_code,
|
|
||||||
headers,
|
|
||||||
body: chunks.to_vec(),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let provider_metadata =
|
let provider_metadata =
|
||||||
ProviderMetadata::discover_async(IssuerUrl::new(issuer)?, async_http_client).await?;
|
ProviderMetadata::discover_async(IssuerUrl::new(issuer)?, client).await?;
|
||||||
Self::from_provider_metadata_and_client(
|
Self::from_provider_metadata_and_client(
|
||||||
provider_metadata,
|
provider_metadata,
|
||||||
application_base_url,
|
application_base_url,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
pin::Pin,
|
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -9,17 +8,16 @@ use axum::{
|
||||||
response::{IntoResponse, Redirect},
|
response::{IntoResponse, Redirect},
|
||||||
};
|
};
|
||||||
use axum_core::{extract::FromRequestParts, response::Response};
|
use axum_core::{extract::FromRequestParts, response::Response};
|
||||||
use futures_util::{future::BoxFuture, Future};
|
use futures_util::future::BoxFuture;
|
||||||
use http::{request::Parts, uri::PathAndQuery, Request, Uri};
|
use http::{request::Parts, uri::PathAndQuery, Request, Uri};
|
||||||
use tower_layer::Layer;
|
use tower_layer::Layer;
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
use tower_sessions::Session;
|
use tower_sessions::Session;
|
||||||
|
|
||||||
use openidconnect::{
|
use openidconnect::{
|
||||||
core::{CoreAuthenticationFlow, CoreErrorResponseType, CoreGenderClaim},
|
core::{CoreAuthenticationFlow, CoreErrorResponseType, CoreGenderClaim, CoreJsonWebKey},
|
||||||
AccessToken, AccessTokenHash, AuthorizationCode, CsrfToken, HttpRequest, HttpResponse,
|
AccessToken, AccessTokenHash, AuthorizationCode, CsrfToken, IdTokenClaims, IdTokenVerifier,
|
||||||
IdTokenClaims, Nonce, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl,
|
Nonce, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken,
|
||||||
RefreshToken,
|
|
||||||
RequestTokenError::ServerResponse,
|
RequestTokenError::ServerResponse,
|
||||||
Scope, TokenResponse,
|
Scope, TokenResponse,
|
||||||
};
|
};
|
||||||
|
|
@ -145,22 +143,27 @@ where
|
||||||
|
|
||||||
let token_response = oidcclient
|
let token_response = oidcclient
|
||||||
.client
|
.client
|
||||||
.exchange_code(AuthorizationCode::new(query.code.to_string()))
|
.exchange_code(AuthorizationCode::new(query.code.to_string()))?
|
||||||
// Set the PKCE code verifier.
|
// Set the PKCE code verifier.
|
||||||
.set_pkce_verifier(PkceCodeVerifier::new(
|
.set_pkce_verifier(PkceCodeVerifier::new(
|
||||||
login_session.pkce_verifier.secret().to_string(),
|
login_session.pkce_verifier.secret().to_string(),
|
||||||
))
|
))
|
||||||
.request_async(async_http_client(&oidcclient.http_client))
|
.request_async(&oidcclient.http_client)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Extract the ID token claims after verifying its authenticity and nonce.
|
// Extract the ID token claims after verifying its authenticity and nonce.
|
||||||
let id_token = token_response
|
let id_token = token_response
|
||||||
.id_token()
|
.id_token()
|
||||||
.ok_or(MiddlewareError::IdTokenMissing)?;
|
.ok_or(MiddlewareError::IdTokenMissing)?;
|
||||||
let claims = id_token
|
let id_token_verifier = oidcclient.client.id_token_verifier();
|
||||||
.claims(&oidcclient.client.id_token_verifier(), &login_session.nonce)?;
|
let claims = id_token.claims(&id_token_verifier, &login_session.nonce)?;
|
||||||
|
|
||||||
validate_access_token_hash(id_token, token_response.access_token(), claims)?;
|
validate_access_token_hash(
|
||||||
|
id_token,
|
||||||
|
id_token_verifier,
|
||||||
|
token_response.access_token(),
|
||||||
|
claims,
|
||||||
|
)?;
|
||||||
|
|
||||||
login_session.authenticated = Some(AuthenticatedSession {
|
login_session.authenticated = Some(AuthenticatedSession {
|
||||||
id_token: id_token.clone(),
|
id_token: id_token.clone(),
|
||||||
|
|
@ -428,12 +431,16 @@ fn insert_extensions<AC: AdditionalClaims>(
|
||||||
/// Returns `Ok` when access token is valid
|
/// Returns `Ok` when access token is valid
|
||||||
fn validate_access_token_hash<AC: AdditionalClaims>(
|
fn validate_access_token_hash<AC: AdditionalClaims>(
|
||||||
id_token: &IdToken<AC>,
|
id_token: &IdToken<AC>,
|
||||||
|
id_token_verifier: IdTokenVerifier<CoreJsonWebKey>,
|
||||||
access_token: &AccessToken,
|
access_token: &AccessToken,
|
||||||
claims: &IdTokenClaims<AC, CoreGenderClaim>,
|
claims: &IdTokenClaims<AC, CoreGenderClaim>,
|
||||||
) -> Result<(), MiddlewareError> {
|
) -> Result<(), MiddlewareError> {
|
||||||
if let Some(expected_access_token_hash) = claims.access_token_hash() {
|
if let Some(expected_access_token_hash) = claims.access_token_hash() {
|
||||||
let actual_access_token_hash =
|
let actual_access_token_hash = AccessTokenHash::from_token(
|
||||||
AccessTokenHash::from_token(access_token, &id_token.signing_alg()?)?;
|
access_token,
|
||||||
|
id_token.signing_alg()?,
|
||||||
|
id_token.signing_key(&id_token_verifier)?,
|
||||||
|
)?;
|
||||||
if actual_access_token_hash == *expected_access_token_hash {
|
if actual_access_token_hash == *expected_access_token_hash {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -456,24 +463,27 @@ async fn try_refresh_token<AC: AdditionalClaims>(
|
||||||
)>,
|
)>,
|
||||||
MiddlewareError,
|
MiddlewareError,
|
||||||
> {
|
> {
|
||||||
let mut refresh_request = client.client.exchange_refresh_token(refresh_token);
|
let mut refresh_request = client.client.exchange_refresh_token(refresh_token)?;
|
||||||
|
|
||||||
for scope in client.scopes.iter() {
|
for scope in client.scopes.iter() {
|
||||||
refresh_request = refresh_request.add_scope(Scope::new(scope.to_string()));
|
refresh_request = refresh_request.add_scope(Scope::new(scope.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
match refresh_request
|
match refresh_request.request_async(&client.http_client).await {
|
||||||
.request_async(async_http_client(&client.http_client))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(token_response) => {
|
Ok(token_response) => {
|
||||||
// Extract the ID token claims after verifying its authenticity and nonce.
|
// Extract the ID token claims after verifying its authenticity and nonce.
|
||||||
let id_token = token_response
|
let id_token = token_response
|
||||||
.id_token()
|
.id_token()
|
||||||
.ok_or(MiddlewareError::IdTokenMissing)?;
|
.ok_or(MiddlewareError::IdTokenMissing)?;
|
||||||
let claims = id_token.claims(&client.client.id_token_verifier(), nonce)?;
|
let id_token_verifier = client.client.id_token_verifier();
|
||||||
|
let claims = id_token.claims(&id_token_verifier, nonce)?;
|
||||||
|
|
||||||
validate_access_token_hash(id_token, token_response.access_token(), claims)?;
|
validate_access_token_hash(
|
||||||
|
id_token,
|
||||||
|
id_token_verifier,
|
||||||
|
token_response.access_token(),
|
||||||
|
claims,
|
||||||
|
)?;
|
||||||
|
|
||||||
let authenticated_session = AuthenticatedSession {
|
let authenticated_session = AuthenticatedSession {
|
||||||
id_token: id_token.clone(),
|
id_token: id_token.clone(),
|
||||||
|
|
@ -494,47 +504,3 @@ async fn try_refresh_token<AC: AdditionalClaims>(
|
||||||
Err(err) => Err(err.into()),
|
Err(err) => Err(err.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `openidconnect::reqwest::async_http_client` that uses a custom `reqwest::client`
|
|
||||||
fn async_http_client<'a>(
|
|
||||||
client: &'a reqwest::Client,
|
|
||||||
) -> impl FnOnce(
|
|
||||||
HttpRequest,
|
|
||||||
) -> Pin<
|
|
||||||
Box<
|
|
||||||
dyn Future<Output = Result<HttpResponse, openidconnect::reqwest::Error<reqwest::Error>>>
|
|
||||||
+ Send
|
|
||||||
+ 'a,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
move |request: HttpRequest| {
|
|
||||||
Box::pin(async move {
|
|
||||||
let mut request_builder = client
|
|
||||||
.request(request.method, request.url.as_str())
|
|
||||||
.body(request.body);
|
|
||||||
for (name, value) in &request.headers {
|
|
||||||
request_builder = request_builder.header(name.as_str(), value.as_bytes());
|
|
||||||
}
|
|
||||||
let request = request_builder
|
|
||||||
.build()
|
|
||||||
.map_err(openidconnect::reqwest::Error::Reqwest)?;
|
|
||||||
|
|
||||||
let response = client
|
|
||||||
.execute(request)
|
|
||||||
.await
|
|
||||||
.map_err(openidconnect::reqwest::Error::Reqwest)?;
|
|
||||||
|
|
||||||
let status_code = response.status();
|
|
||||||
let headers = response.headers().to_owned();
|
|
||||||
let chunks = response
|
|
||||||
.bytes()
|
|
||||||
.await
|
|
||||||
.map_err(openidconnect::reqwest::Error::Reqwest)?;
|
|
||||||
Ok(HttpResponse {
|
|
||||||
status_code,
|
|
||||||
headers,
|
|
||||||
body: chunks.to_vec(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue