Wiki
Download
Manual
Eggs
API
Tests
Bugs
show
edit
history
You can edit this page using
wiki syntax
for markup.
Article contents:
== Oauthtoothy Complete OAuth2 authentication middleware for CHICKEN Scheme web applications. === Description Oauthtoothy is a configurable OAuth2 authentication middleware that provides complete OAuth2 authentication flow integration for web applications. It handles OAuth2 authorization, token exchange, user info retrieval, and session management automatically. The middleware registers routes for each provider and manages authentication state across requests. === Requirements * CHICKEN Scheme 5.0 or later * Dependencies: medea, openssl, http-client, intarweb, uri-common, schematra, schematra-session === Installation <enscript highlight="bash"> chicken-install oauthtoothy </enscript> === Basic Usage <enscript highlight="scheme"> (import schematra schematra-session oauthtoothy chiccup) ;; Define user data parser for Google (define (parse-google-user json-response) `((id . ,(alist-ref 'id json-response)) (name . ,(alist-ref 'name json-response)) (email . ,(alist-ref 'email json-response)))) ;; Configure OAuth2 provider (define google-provider `((name . "google") (client-id . ,(get-environment-variable "GOOGLE_CLIENT_ID")) (client-secret . ,(get-environment-variable "GOOGLE_CLIENT_SECRET")) (auth-url . "https://accounts.google.com/o/oauth2/auth") (token-url . "https://oauth2.googleapis.com/token") (user-info-url . "https://www.googleapis.com/oauth2/v2/userinfo") (scopes . "profile email") (user-info-parser . ,parse-google-user))) ;; Install middleware (use-middleware! (session-middleware "secret-key")) (use-middleware! (oauthtoothy-middleware (list google-provider) success-redirect: "/profile")) ;; Protected route (get ("/profile") (let ((auth (current-auth))) (if (alist-ref 'authenticated? auth) (ccup->html `[h1 ,(string-append "Welcome, " (alist-ref 'name auth))]) (redirect "/auth/google")))) (schematra-install) (schematra-start) </enscript> === API <procedure>(oauthtoothy-middleware providers #!key success-redirect save-proc load-proc)</procedure> Creates OAuth2 authentication middleware for Schematra applications. '''Parameters:''' * {{providers}}: List of OAuth2 provider configurations * {{success-redirect}}: URL to redirect to after successful authentication (default: "/") * {{save-proc}}: Function to save user data for persistence (optional) * {{load-proc}}: Function to load user data from storage (optional) <enscript highlight="scheme"> ;; Basic setup (oauthtoothy-middleware (list google-provider)) ;; With custom redirect and persistence (oauthtoothy-middleware (list google-provider github-provider) success-redirect: "/dashboard" save-proc: save-user-to-db load-proc: load-user-from-db) </enscript> ==== Registered Routes For each provider named "example", the middleware automatically registers: * {{GET /auth/example}} - Initiates OAuth2 flow, redirects to provider * {{GET /auth/example/callback}} - Handles OAuth2 callback from provider <parameter>(auth-base-url [url])</parameter> Base URL parameter for OAuth2 callback URLs. Default: "http://localhost:8080". This URL must be registered with your OAuth2 providers as an allowed callback URL. The actual callback path will be "{base-url}/auth/{provider}/callback". <enscript highlight="scheme"> ;; For production deployment (auth-base-url "https://myapp.example.com") ;; For local development with custom port (auth-base-url "http://localhost:3000") </enscript> <parameter>(current-auth)</parameter> Current authentication state parameter containing user authentication details. '''Unauthenticated state:''' {{((authenticated? . #f))}} '''Authenticated state structure:''' * {{authenticated?}}: boolean - #t if user is authenticated * {{user-id}}: any - unique identifier for the user * Additional fields from the user-info-parser <enscript highlight="scheme"> ;; Check authentication in route handlers (get ("/protected") (let ((auth (current-auth))) (if (alist-ref 'authenticated? auth) (format "Welcome, ~A!" (alist-ref 'name auth)) (redirect "/login")))) ;; Access user data (let ((auth (current-auth))) (when (alist-ref 'authenticated? auth) (let ((user-id (alist-ref 'user-id auth)) (email (alist-ref 'email auth))) ;; Process authenticated user... ))) </enscript> === Provider Configuration Each provider must be an association list with these required keys: '''Required Keys:''' * {{name}}: string - Unique identifier for the provider (e.g., "google", "github") * {{client-id}}: string - OAuth2 client ID from the provider * {{client-secret}}: string - OAuth2 client secret from the provider * {{auth-url}}: string - Provider's authorization endpoint URL * {{token-url}}: string - Provider's token exchange endpoint URL * {{user-info-url}}: string - Provider's user information endpoint URL * {{scopes}}: string - Space-separated list of OAuth2 scopes to request * {{user-info-parser}}: procedure - Function to normalize provider-specific user data <enscript highlight="scheme"> ;; Google OAuth2 provider (define google-provider `((name . "google") (client-id . "your-google-client-id") (client-secret . "your-google-client-secret") (auth-url . "https://accounts.google.com/o/oauth2/auth") (token-url . "https://oauth2.googleapis.com/token") (user-info-url . "https://www.googleapis.com/oauth2/v2/userinfo") (scopes . "profile email") (user-info-parser . ,parse-google-user))) ;; GitHub OAuth2 provider (define github-provider `((name . "github") (client-id . "your-github-client-id") (client-secret . "your-github-client-secret") (auth-url . "https://github.com/login/oauth/authorize") (token-url . "https://github.com/login/oauth/access_token") (user-info-url . "https://api.github.com/user") (scopes . "read:user user:email") (user-info-parser . ,parse-github-user))) </enscript> ==== User Info Parser The user-info-parser function normalizes provider-specific user data into a consistent format: <enscript highlight="scheme"> ;; Google user parser (define (parse-google-user json-response) `((id . ,(alist-ref 'id json-response)) (name . ,(alist-ref 'name json-response)) (email . ,(alist-ref 'email json-response)) (avatar . ,(alist-ref 'picture json-response)))) ;; GitHub user parser (define (parse-github-user json-response) `((id . ,(number->string (alist-ref 'id json-response))) (name . ,(alist-ref 'name json-response)) (email . ,(alist-ref 'email json-response)) (username . ,(alist-ref 'login json-response)) (avatar . ,(alist-ref 'avatar_url json-response)))) </enscript> === Persistence For user data persistence beyond sessions, provide save-proc and load-proc functions: <enscript highlight="scheme"> ;; Save user data to database (define (save-user-to-db user-id user-data token) (execute-sql "INSERT OR REPLACE INTO users (id, name, email, avatar) VALUES (?, ?, ?, ?)" user-id (alist-ref 'name user-data) (alist-ref 'email user-data) (alist-ref 'avatar user-data))) ;; Load user data from database (define (load-user-from-db user-id) (let ((row (query-row "SELECT name, email, avatar FROM users WHERE id = ?" user-id))) (if row `((name . ,(first row)) (email . ,(second row)) (avatar . ,(third row))) #f))) ;; Use with middleware (oauthtoothy-middleware (list google-provider) save-proc: save-user-to-db load-proc: load-user-from-db) </enscript> === Complete Examples ==== Multi-Provider Authentication <enscript highlight="scheme"> (import schematra schematra-session oauthtoothy chiccup) ;; User parsers (define (parse-google-user json-response) `((id . ,(alist-ref 'id json-response)) (name . ,(alist-ref 'name json-response)) (email . ,(alist-ref 'email json-response)) (provider . "google"))) (define (parse-github-user json-response) `((id . ,(number->string (alist-ref 'id json-response))) (name . ,(alist-ref 'name json-response)) (username . ,(alist-ref 'login json-response)) (provider . "github"))) ;; Provider configurations (define providers (list `((name . "google") (client-id . ,(get-environment-variable "GOOGLE_CLIENT_ID")) (client-secret . ,(get-environment-variable "GOOGLE_CLIENT_SECRET")) (auth-url . "https://accounts.google.com/o/oauth2/auth") (token-url . "https://oauth2.googleapis.com/token") (user-info-url . "https://www.googleapis.com/oauth2/v2/userinfo") (scopes . "profile email") (user-info-parser . ,parse-google-user)) `((name . "github") (client-id . ,(get-environment-variable "GITHUB_CLIENT_ID")) (client-secret . ,(get-environment-variable "GITHUB_CLIENT_SECRET")) (auth-url . "https://github.com/login/oauth/authorize") (token-url . "https://github.com/login/oauth/access_token") (user-info-url . "https://api.github.com/user") (scopes . "read:user user:email") (user-info-parser . ,parse-github-user)))) ;; Install middleware (use-middleware! (session-middleware "your-secret-key")) (use-middleware! (oauthtoothy-middleware providers success-redirect: "/profile")) ;; Login page with provider options (get ("/login") (ccup->html `[.login-page [h1 "Login"] [.providers [a.btn (@ (href "/auth/google")) "Login with Google"] [a.btn (@ (href "/auth/github")) "Login with GitHub"]]])) ;; Protected profile page (get ("/profile") (let ((auth (current-auth))) (if (alist-ref 'authenticated? auth) (ccup->html `[.profile [h1 ,(format "Welcome, ~A!" (alist-ref 'name auth))] [p ,(format "Provider: ~A" (alist-ref 'provider auth))] [a (@ (href "/logout")) "Logout"]]) (redirect "/login")))) ;; Logout (get ("/logout") (session-destroy!) (redirect "/")) (schematra-install) (schematra-start) </enscript> ==== Database Integration <enscript highlight="scheme"> (import sql-de-lite) ;; Database setup (define db (open-database "app.db")) (execute-sql db "CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, name TEXT, email TEXT, provider TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )") ;; Persistence functions (define (save-user user-id user-data token) (execute-sql db "INSERT OR REPLACE INTO users (id, name, email, provider) VALUES (?, ?, ?, ?)" user-id (alist-ref 'name user-data) (alist-ref 'email user-data) (alist-ref 'provider user-data))) (define (load-user user-id) (let ((row (query-row db "SELECT name, email, provider FROM users WHERE id = ?" user-id))) (if row `((name . ,(first row)) (email . ,(second row)) (provider . ,(third row))) #f))) ;; Install middleware with persistence (use-middleware! (oauthtoothy-middleware providers success-redirect: "/profile" save-proc: save-user load-proc: load-user)) </enscript> === Security Considerations * Store client secrets securely using environment variables * Register exact callback URLs with OAuth2 providers * Use HTTPS in production (set auth-base-url accordingly) * Validate and sanitize user data from providers * Consider token refresh for long-lived sessions * Implement proper session security with schematra-session === Session Integration Oauthtoothy requires session middleware to be installed first. It stores the user-id in the session and optionally uses save-proc/load-proc for additional user data persistence. <enscript highlight="scheme"> ;; Session middleware must be installed before oauthtoothy (use-middleware! (session-middleware "your-secret-key")) (use-middleware! (oauthtoothy-middleware providers)) ;; Session contains user-id after authentication (session-get "user-id") ; => user's unique identifier </enscript> === License Copyright © 2025 Rolando Abarca. Released under BSD-3-Clause License. === Repository [[https://github.com/schematra/schematra|GitHub Repository]]
Description of your changes:
I would like to authenticate
Authentication
Username:
Password:
Spam control
What do you get when you subtract 21 from 20?