diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5a1c7a1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: Cargo Build & Test + +on: + push: + pull_request: + schedule: + - cron: '0 0 1,7,14,21 * *' + +env: + CARGO_TERM_COLOR: always + +jobs: + build_and_test: + name: axum-oidc - latest + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - stable + - nightly + steps: + - uses: actions/checkout@v3 + - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - run: cargo build --verbose + - run: cargo test --verbose + + build_examples: + name: axum-oidc - examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: rustup update stable && rustup default stable + - run: cargo build --verbose + working-directory: ./examples/basic diff --git a/.gitignore b/.gitignore index 96ef6c0..092ecc7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -/target +target +.env Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 0eb12ce..88b1488 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "axum-oidc" description = "A wrapper for the openidconnect crate for axum" -version = "0.1.1" +version = "0.1.2" edition = "2021" authors = [ "Paul Z " ] readme = "README.md" diff --git a/README.md b/README.md index 1c11fcc..2d0b2fb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +**THIS IS AN OLD VERSION! PLEASE USE THE LATEST VERSION IF POSSIBLE!** + This Library allows using [OpenID Connect](https://openid.net/developers/how-connect-works/) with [axum](https://github.com/tokio-rs/axum). It authenticates the user with the OpenID Conenct Issuer and provides Extractors. @@ -15,49 +17,8 @@ The `OidcAccessToken`-extractor can be used to get the OpenId Connect Access Tok Your OIDC-Client must be allowed to redirect to **every** subpath of your application base url. -```rust -#[tokio::main] -async fn main() { - - let session_store = MemoryStore::default(); - let session_service = ServiceBuilder::new() - .layer(HandleErrorLayer::new(|_: BoxError| async { - StatusCode::BAD_REQUEST - })) - .layer(SessionManagerLayer::new(session_store).with_same_site(SameSite::Lax)); - - let oidc_login_service = ServiceBuilder::new() - .layer(HandleErrorLayer::new(|e: MiddlewareError| async { - e.into_response() - })) - .layer(OidcLoginLayer::::new()); - - let oidc_auth_service = ServiceBuilder::new() - .layer(HandleErrorLayer::new(|e: MiddlewareError| async { - e.into_response() - })) - .layer( - OidcAuthLayer::::discover_client( - Uri::from_static("https://example.com"), - "".to_string(), - "".to_string(), - "".to_owned(), - vec![], - ).await.unwrap(), - ); - - let app = Router::new() - .route("/", get(|| async { "Hello, authenticated World!" })) - .layer(oidc_login_service) - .layer(oidc_auth_service) - .layer(session_service); - - axum::Server::bind(&"[::]:8080".parse().unwrap()) - .serve(app.into_make_service()) - .await - .unwrap(); -} -``` +# Examples +Take a look at the `examples` folder for examples. # Example Projects Here is a place for projects that are using this library. diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml new file mode 100644 index 0000000..d4e6e6b --- /dev/null +++ b/examples/basic/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "basic" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1.36.0", features = ["net", "macros", "rt-multi-thread"] } +axum = "0.6" +axum-oidc = { path = "./../.." } +tower = "0.4" +tower-sessions = "0.4" + +dotenvy = "0.15.7" diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs new file mode 100644 index 0000000..b34bbcc --- /dev/null +++ b/examples/basic/src/main.rs @@ -0,0 +1,79 @@ +use axum::{ + error_handling::HandleErrorLayer, + http::{StatusCode, Uri}, + response::IntoResponse, + routing::get, + BoxError, Router, +}; +use axum_oidc::{ + error::MiddlewareError, EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcLoginLayer, +}; +use tower::ServiceBuilder; +use tower_sessions::{cookie::SameSite, MemoryStore, SessionManagerLayer}; + +#[tokio::main] +async fn main() { + dotenvy::dotenv().ok(); + let app_url = std::env::var("APP_URL").expect("APP_URL env variable"); + let issuer = std::env::var("ISSUER").expect("ISSUER env variable"); + let client_id = std::env::var("CLIENT_ID").expect("CLIENT_ID env variable"); + let client_secret = std::env::var("CLIENT_SECRET").ok(); + + let session_store = MemoryStore::default(); + let session_service = ServiceBuilder::new() + .layer(HandleErrorLayer::new(|_: BoxError| async { + StatusCode::BAD_REQUEST + })) + .layer(SessionManagerLayer::new(session_store).with_same_site(SameSite::Lax)); + + let oidc_login_service = ServiceBuilder::new() + .layer(HandleErrorLayer::new(|e: MiddlewareError| async { + e.into_response() + })) + .layer(OidcLoginLayer::::new()); + + let oidc_auth_service = ServiceBuilder::new() + .layer(HandleErrorLayer::new(|e: MiddlewareError| async { + e.into_response() + })) + .layer( + OidcAuthLayer::::discover_client( + Uri::from_maybe_shared(app_url).expect("valid APP_URL"), + issuer, + client_id, + client_secret, + vec![], + ) + .await + .unwrap(), + ); + + let app = Router::new() + .route("/foo", get(authenticated)) + .layer(oidc_login_service) + .route("/bar", get(maybe_authenticated)) + .layer(oidc_auth_service) + .layer(session_service); + + axum::Server::bind(&"[::]:8080".parse().unwrap()) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +async fn authenticated(claims: OidcClaims) -> impl IntoResponse { + format!("Hello {}", claims.0.subject().as_str()) +} + +async fn maybe_authenticated( + claims: Option>, +) -> impl IntoResponse { + if let Some(claims) = claims { + format!( + "Hello {}! You are already logged in from another Handler.", + claims.0.subject().as_str() + ) + } else { + "Hello anon!".to_string() + } +} diff --git a/src/middleware.rs b/src/middleware.rs index c9390f2..030f979 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -1,6 +1,5 @@ use std::{ marker::PhantomData, - str::FromStr, task::{Context, Poll}, }; @@ -16,20 +15,15 @@ use tower_service::Service; use tower_sessions::Session; use openidconnect::{ - core::{ - CoreAuthenticationFlow, CoreGenderClaim, CoreIdTokenFields, CoreJsonWebKeyType, - CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm, - }, - reqwest::async_http_client, - AccessTokenHash, AuthorizationCode, CsrfToken, ExtraTokenFields, IdTokenFields, Nonce, - OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken, Scope, - StandardTokenResponse, TokenResponse, TokenType, + core::CoreAuthenticationFlow, reqwest::async_http_client, AccessTokenHash, AuthorizationCode, + CsrfToken, Nonce, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, Scope, + TokenResponse, }; use crate::{ error::{Error, MiddlewareError}, extractor::{OidcAccessToken, OidcClaims}, - AdditionalClaims, BoxError, IdToken, OidcClient, OidcQuery, OidcSession, SESSION_KEY, + AdditionalClaims, BoxError, OidcClient, OidcQuery, OidcSession, SESSION_KEY, }; /// Layer for the [OidcLoginMiddleware]. @@ -419,6 +413,7 @@ pub fn strip_oidc_from_path(base_url: Uri, uri: &Uri) -> Result