Add oidc-provider-mock dev setup with docker-compose and dotenvy

- docker-compose.yml runs oidc-provider-mock on :9400 with predefined
  alice/bob users carrying preferred_username claims
- .env auto-loaded via dotenvy for zero-config local dev
- index route now redirects unauthenticated users to the login flow
  instead of returning 401
This commit is contained in:
Opencode Agent
2026-07-30 23:23:59 +00:00
parent 635b685bc9
commit 69dcd84d0b
5 changed files with 43 additions and 8 deletions
+7 -4
View File
@@ -1,9 +1,12 @@
# Base URL of the OIDC provider (e.g. Keycloak realm URL)
OIDC_PROVIDER_URL=http://localhost:8080
# Base URL of the OIDC provider.
# For local development, `docker-compose up -d` starts oidc-provider-mock
# at http://localhost:9400 (accepts any client ID/secret).
# For Keycloak use e.g. https://keycloak.example.com/realms/myrealm
OIDC_PROVIDER_URL=http://localhost:9400
# OAuth2 client credentials (required)
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_CLIENT_ID=dev-client
OIDC_CLIENT_SECRET=dev-secret
# Full callback URL — must match the redirect URI configured at the provider
OIDC_REDIRECT_URI=http://localhost:3000/auth/callback
Generated
+1
View File
@@ -216,6 +216,7 @@ dependencies = [
"axum",
"axum-oidc-client",
"base64",
"dotenvy",
"serde",
"serde_json",
"tokio",
+1
View File
@@ -11,3 +11,4 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
base64 = "0.22"
anyhow = "1"
dotenvy = "0.15"
+10
View File
@@ -0,0 +1,10 @@
services:
oidc:
image: ghcr.io/geigerzaehler/oidc-provider-mock:latest
ports:
- "9400:9400"
command:
- --user-claims
- '{"sub": "alice", "preferred_username": "alice", "email": "alice@example.com", "name": "Alice"}'
- --user-claims
- '{"sub": "bob", "preferred_username": "bob", "email": "bob@example.com", "name": "Bob"}'
+24 -4
View File
@@ -1,13 +1,20 @@
use std::env;
use std::sync::Arc;
use axum::{http::StatusCode, response::Html, routing::get, Json, Router};
use axum::{
extract::State,
http::StatusCode,
response::{Html, IntoResponse, Redirect, Response},
routing::get,
Json, Router,
};
use axum_oidc_client::authentication::AuthenticationLayer;
use axum_oidc_client::authentication::builder::OAuthConfigurationBuilder;
use axum_oidc_client::authentication::logout::handle_default_logout::DefaultLogoutHandler;
use axum_oidc_client::auth_cache::AuthCache;
use axum_oidc_client::auth_session::AuthSession;
use axum_oidc_client::cache::{TwoTierAuthCache, config::TwoTierCacheConfig};
use axum_oidc_client::extractors::OptionalAuthSession;
use axum_oidc_client::sql_cache::{SqlAuthCache, SqlCacheConfig};
use axum_oidc_client::authentication::CodeChallengeMethod;
use base64::Engine;
@@ -78,12 +85,22 @@ async fn hello(session: AuthSession) -> Result<Json<HelloResponse>, StatusCode>
Ok(Json(HelloResponse { preferred_username }))
}
async fn index(_session: AuthSession) -> Html<&'static str> {
Html(INDEX_HTML)
#[derive(Clone)]
struct AppState {
login_path: String,
}
async fn index(session: OptionalAuthSession, State(state): State<AppState>) -> Response {
match session.0 {
Some(_) => Html(INDEX_HTML).into_response(),
None => Redirect::to(&state.login_path).into_response(),
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenvy::dotenv().ok();
let provider_url = env_required("OIDC_PROVIDER_URL");
let client_id = env_required("OIDC_CLIENT_ID");
let client_secret = env_required("OIDC_CLIENT_SECRET");
@@ -131,7 +148,10 @@ async fn main() -> anyhow::Result<()> {
Arc::new(config),
cache,
logout_handler,
));
))
.with_state(AppState {
login_path: auth_base_path,
});
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
println!("Listening on http://0.0.0.0:3000");