Rust web service that provides OAuth/OIDC authentication and proxies authenticated requests to provider APIs (Google, GitHub, etc.) with optional TEE (Trusted Execution Environment) attestation. Built with Axum, Diesel (async Postgres/blocking SQLite), and designed for deployment on Phala Cloud's TDX infrastructure.
- OAuth Proxy - Authenticated proxy to provider APIs (Google, GitHub, etc.)
- TEE Attestation - Optional TDX attestation for requests in trusted execution environments
- Multi-Provider SSO - Google OIDC, GitHub OAuth2, Dex (local testing)
- OAuth App Grants - Authorization code + PKCE flows for third-party apps, with scoped proxy access
- API Key Authentication - Bearer token support for external apps (scope-limited, soft-delete)
- Account Management - Link/unlink OAuth providers, manage API keys
- Reproducible Builds - Nix-based builds for TEE verification
- Dual Database Support - Async Postgres (production) or blocking SQLite (dev)
- TEE Attestation - Phala Cloud TDX attestation integration
- OAuth Applications - App registration, consent, and proxy scopes
- Reproducible Builds - Nix-based reproducible builds
- Deployment - Phala Cloud and self-hosted deployment
- Runtime: Tokio
- Web: Axum 0.8, tower-http, tower-cookies
- DB: Diesel 2.x + diesel-async (Postgres) / r2d2 (SQLite)
- Auth: openidconnect, oauth2
- Attestation: dstack (Phala TEE)
- Build: Nix flakes + crane
Prerequisites: Rust toolchain, Diesel CLI with SQLite, and this repo.
- Install Diesel CLI (once):
cargo install diesel_cli --no-default-features --features sqlite
- Configure env (copy
.env.exampleto.envand adjust if needed):
cp .env.example .env
- Initialize DB and run migrations:
set -a; source .env; set +a
diesel setup
diesel migration run
- Run the server (SQLite is the default cargo feature):
cargo run
Visit http://127.0.0.1:8080/login
Notes:
- If
COOKIE_KEY_BASE64is not set, the app will generate a random dev key on startup; sessions will be invalid after restart. /auth/googleand its callback are placeholders until OIDC is fully wired.
- Start Postgres (example Docker):
docker run --rm -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=oauth3 -p 5432:5432 postgres:16
- Use a Postgres
DATABASE_URLin your.env:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/oauth3
- Install libpq (required for Diesel Postgres builds)
The native Postgres client library (libpq) must be available on your system for builds with the pg feature.
-
macOS (Homebrew):
brew install libpq- Ensure headers and libs are discoverable by your toolchain (recommended shell exports):
export LDFLAGS="-L$(brew --prefix libpq)/lib"export CPPFLAGS="-I$(brew --prefix libpq)/include"export PATH="$(brew --prefix libpq)/bin:$PATH"
- You can add these to your shell profile (e.g.,
~/.zshrc) so they persist.
-
Ubuntu/Debian:
sudo apt-get update && sudo apt-get install -y libpq-dev
-
Fedora/CentOS/RHEL:
sudo dnf install -y postgresql-devel(orpostgresql-libson some versions)
-
Windows:
- Install PostgreSQL and ensure its
bin/andlib/directories are on PATH/LIB. Alternatively, use MSYS2 and installmingw-w64-<triplet>-postgresql.
- Install PostgreSQL and ensure its
If you prefer not to install a system libpq, let us know — we can add an optional vendored build via pq-sys that compiles libpq from source.
- Install Diesel CLI for Postgres and run migrations:
cargo install diesel_cli --no-default-features --features postgres
diesel setup
diesel migration run
- Build and run with the
pgfeature:
cargo run --no-default-features --features pg
The compose stack includes:
- Postgres database
- Dex (local OIDC provider for testing)
- Phala simulator (TEE attestation testing)
- oauth3 app (built with Nix for reproducibility)
Prerequisites:
- Docker and Docker Compose
- Nix with flakes (see docs/nix-builds.md)
Steps:
-
Build the Docker image:
# Option A: Use Dockerfile.nix (works on macOS) docker build -f Dockerfile.nix -t oauth3:nix . # Option B: Use pure Nix (Linux only) nix build .#dockerImage && docker load < result
-
Configure environment:
cp .env.example .env
For local testing with Dex (no external OAuth setup needed):
COOKIE_KEY_BASE64— generate with:head -c 64 /dev/urandom | base64- Dex credentials are pre-configured in
.env.example
For live Google/GitHub OAuth (optional):
- Set
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,AUTH_GOOGLE_MODE=live - Set
GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET,AUTH_GITHUB_MODE=live - Configure redirect URIs in provider consoles:
- Google:
http://localhost:8080/auth/callback/google - GitHub:
http://localhost:8080/auth/callback/github
- Google:
-
Start the stack:
docker compose up
Services running:
db: Postgres atlocalhost:5432dex: OIDC provider atlocalhost:5556simulator: TEE attestation atlocalhost:8090app: oauth3 server atlocalhost:8080
-
Test the app:
http://localhost:8080/login- Dex (local): No setup required, click "Continue with Dex"
- Google/GitHub: Requires credentials in
.envandAUTH_*_MODE=live
-
Test TEE attestation:
curl http://localhost:8080/info curl http://localhost:8080/attestation
Stopping:
docker compose down # Stop containers
docker compose down -v # Also remove volumes (fresh DB)Rebuilding after code changes:
# Rebuild image
docker build -f Dockerfile.nix -t oauth3:nix .
# Restart stack
docker compose upNotes:
- Migrations run automatically on app startup
- Dex provides a local OIDC provider for testing without external OAuth setup
- Sessions require persistent
COOKIE_KEY_BASE64 - See docs/attestation.md for TEE attestation details
- See docs/nix-builds.md for reproducible build details
The application reads configuration from environment variables (flat) and maps them to structured config.
Required/important variables:
DATABASE_URL—sqlite://dev.dbor Postgres DSN likepostgres://user:pass@host:5432/dbAPP_BIND_ADDR— default127.0.0.1:8080APP_PUBLIC_URL— defaulthttp://127.0.0.1:8080COOKIE_KEY_BASE64— base64-encoded 32 or 64 bytes; if 32B, it will be duplicated to 64B. If unset, a random 64B dev key is generated each run.APP_FORCE_SECURE—true|falseto forceSecurecookie (defaults to false for local)
OAuth/OIDC Providers:
Dex (local OIDC, pre-configured):
DEX_CLIENT_ID,DEX_CLIENT_SECRET,DEX_ISSUERAUTH_DEX_MODE—placeholder(default) orlive
Google OIDC:
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRETGOOGLE_ISSUER(defaulthttps://accounts.google.com)GOOGLE_SCOPES(space-separated)AUTH_GOOGLE_MODE—placeholder(default) orlive
GitHub OAuth2:
GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRETGITHUB_SCOPES(space-separated)AUTH_GITHUB_MODE—placeholder(default) orlive
Generate a 64-byte key (recommended):
head -c 64 /dev/urandom | base64
- Diesel config is in
diesel.tomland migrations are undermigrations/. - For development, you can regenerate
src/schema.rsvia:
diesel print-schema > src/schema.rs
SQLite note: The app also attempts to run pending migrations on startup when built with the sqlite feature.
OAuth Proxy:
ANY /proxy/{provider}/{path}— Authenticated proxy to provider APIs- Requires session cookie or
Authorization: Bearer <api_key>header - Optional
?attest=truefor TEE attestation - Examples:
GET /proxy/google/oauth2/v2/userinfoGET /proxy/google/calendar/v3/calendars/primary/eventsGET /proxy/github/user
- Requires session cookie or
Authentication:
GET /— landing pageGET /healthz— liveness probeGET /login— login page with provider optionsGET /account— account management (link providers, API keys)GET /auth/{provider}— start OAuth flow (google, github, dex)GET /auth/callback/{provider}— OAuth callbackGET /me— current user info from sessionPOST /logout— clear session
Account Management:
GET /account/linked-identities— list linked OAuth providersPOST /account/link/{provider}— link additional providerPOST /account/unlink/{provider}— unlink providerGET /providers— list available OAuth providers
API Keys:
GET /account/api-keys— list user's API keysPOST /account/api-keys— create new API keyDELETE /account/api-keys/{key_id}— soft-delete API key
TEE Attestation (see docs/attestation.md):
GET /info— application version and build infoGET /attestation— TDX attestation quote
Static Assets:
GET /static/*— CSS, JS, images
For external applications (non-browser), use API keys instead of session cookies:
Creating API Keys:
- Log in via browser and visit
/account - Create a new API key (copy it immediately - shown only once)
- Use in external apps with
Authorization: Bearer oak_...header
Example Usage:
# Using curl
curl https://your-domain.com/proxy/google/oauth2/v2/userinfo \
-H 'Authorization: Bearer oak_YOUR_API_KEY_HERE'
# With attestation
curl 'https://your-domain.com/proxy/google/oauth2/v2/userinfo?attest=true' \
-H 'Authorization: Bearer oak_YOUR_API_KEY_HERE'Security:
- Keys are hashed (SHA-256) before storage
- Soft-deleted (never reused)
- Scope-limited (proxy only, cannot create new keys)
- Last-used tracking
Local Development:
docker compose up # All services (db, dex, simulator, app)Phala Cloud:
# Configure .env.phala
cp .env.phala.example .env.phala
nano .env.phala
# CI builds and pushes images automatically on push to main.
# To deploy manually:
./scripts/phala-deploy.sh .env.phala
# Deploy with data migration from old CVM:
./scripts/phala-deploy.sh .env.phala old-cvm-nameSee docs/DEPLOYMENT.md for complete instructions.
GitHub Actions:
The repository includes a workflow that automatically builds and publishes Docker images to GHCR on push to main or tag creation. Images are built with Nix for reproducibility.
- Default cargo feature is
sqlite. Use--no-default-features --features pgfor Postgres - Logs:
RUST_LOG=info,oauth3=debug - OAuth requires real credentials and configured redirect URIs
- Migrations run automatically on startup
- Session persistence requires stable
COOKIE_KEY_BASE64
Troubleshooting Postgres builds:
ld: library 'pq' not found→ install libpq (see Quickstart section)
- Token Refresh: Automatic token refresh in proxy endpoint
- Encryption at Rest: AEAD encryption for stored tokens
- Background Cleanup: Periodic cleanup of expired tokens/identities
- Token Revocation: Support for provider revocation endpoints
- Provider UI: Web interface for managing provider configurations
- Observability: Structured logging, metrics, trace IDs
Dex (recommended for local dev):
- Pre-configured in
.env.exampleanddocker-compose.yml - No external setup required
- Set
AUTH_DEX_MODE=liveto enable - Works with
docker compose up
Google OIDC:
- Create OAuth 2.0 Client in Google Cloud Console
- Configure redirect URI:
http://localhost:8080/auth/callback/google - Set
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRETin.env - Set
AUTH_GOOGLE_MODE=live
GitHub OAuth:
- Create OAuth App in GitHub Developer Settings
- Configure callback URL:
http://localhost:8080/auth/callback/github - Set
GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRETin.env - Set
AUTH_GITHUB_MODE=live
Placeholder mode (default):
- Providers in
placeholdermode show UI but create mock sessions - Useful for frontend testing without real OAuth setup