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/README.md b/README.md index 9a3c51d..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,47 +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); - - let listener = TcpListener::bind("[::]:8080").await.unwrap(); - axum::serve(listener, 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..7f636de --- /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.7" +axum-oidc = { path = "./../.." } +tower = "0.4" +tower-sessions = "0.9" + +dotenvy = "0.15.7" diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs new file mode 100644 index 0000000..5853c2f --- /dev/null +++ b/examples/basic/src/main.rs @@ -0,0 +1,72 @@ +use axum::{ + error_handling::HandleErrorLayer, http::Uri, response::IntoResponse, routing::get, Router, +}; +use axum_oidc::{ + error::MiddlewareError, EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcLoginLayer, +}; +use tokio::net::TcpListener; +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_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_layer); + + let listener = TcpListener::bind("[::]:8080").await.unwrap(); + axum::serve(listener, 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() + } +}