Files
ResolveSpec/pkg/security/oauth2_examples.go
Hein 7600a6d1fb
Some checks failed
Build , Vet Test, and Lint / Run Vet Tests (1.24.x) (push) Successful in -22m52s
Build , Vet Test, and Lint / Run Vet Tests (1.23.x) (push) Successful in -22m42s
Build , Vet Test, and Lint / Build (push) Successful in -26m19s
Build , Vet Test, and Lint / Lint Code (push) Successful in -25m40s
Tests / Unit Tests (push) Successful in -26m33s
Tests / Integration Tests (push) Failing after -26m55s
fix(security): 🐛 handle errors in OAuth2 examples and passkey methods
* Add error handling for JSON encoding and HTTP server calls.
* Update passkey examples to improve readability and maintainability.
* Ensure consistent use of error handling across all examples.
2026-01-31 22:58:52 +02:00

616 lines
19 KiB
Go

package security
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/mux"
)
// Example: OAuth2 Authentication with Google
func ExampleOAuth2Google() {
db, _ := sql.Open("postgres", "connection-string")
// Create OAuth2 authenticator for Google
oauth2Auth := NewGoogleAuthenticator(
"your-client-id",
"your-client-secret",
"http://localhost:8080/auth/google/callback",
db,
)
router := mux.NewRouter()
// Login endpoint - redirects to Google
router.HandleFunc("/auth/google/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := oauth2Auth.OAuth2GenerateState()
authURL, _ := oauth2Auth.OAuth2GetAuthURL("google", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
// Callback endpoint - handles Google response
router.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
loginResp, err := oauth2Auth.OAuth2HandleCallback(r.Context(), "google", code, state)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
// Set session cookie
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: loginResp.Token,
Path: "/",
MaxAge: int(loginResp.ExpiresIn),
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
// Return user info as JSON
_ = json.NewEncoder(w).Encode(loginResp)
})
_ = http.ListenAndServe(":8080", router)
}
// Example: OAuth2 Authentication with GitHub
func ExampleOAuth2GitHub() {
db, _ := sql.Open("postgres", "connection-string")
oauth2Auth := NewGitHubAuthenticator(
"your-github-client-id",
"your-github-client-secret",
"http://localhost:8080/auth/github/callback",
db,
)
router := mux.NewRouter()
router.HandleFunc("/auth/github/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := oauth2Auth.OAuth2GenerateState()
authURL, _ := oauth2Auth.OAuth2GetAuthURL("github", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
router.HandleFunc("/auth/github/callback", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
loginResp, err := oauth2Auth.OAuth2HandleCallback(r.Context(), "github", code, state)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
_ = json.NewEncoder(w).Encode(loginResp)
})
_ = http.ListenAndServe(":8080", router)
}
// Example: Custom OAuth2 Provider
func ExampleOAuth2Custom() {
db, _ := sql.Open("postgres", "connection-string")
// Custom OAuth2 provider configuration
oauth2Auth := NewDatabaseAuthenticator(db).WithOAuth2(OAuth2Config{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
RedirectURL: "http://localhost:8080/auth/callback",
Scopes: []string{"openid", "profile", "email"},
AuthURL: "https://your-provider.com/oauth/authorize",
TokenURL: "https://your-provider.com/oauth/token",
UserInfoURL: "https://your-provider.com/oauth/userinfo",
ProviderName: "custom-provider",
// Custom user info parser
UserInfoParser: func(userInfo map[string]any) (*UserContext, error) {
// Extract custom fields from your provider
return &UserContext{
UserName: userInfo["username"].(string),
Email: userInfo["email"].(string),
RemoteID: userInfo["id"].(string),
UserLevel: 1,
Roles: []string{"user"},
Claims: userInfo,
}, nil
},
})
router := mux.NewRouter()
router.HandleFunc("/auth/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := oauth2Auth.OAuth2GenerateState()
authURL, _ := oauth2Auth.OAuth2GetAuthURL("custom-provider", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
router.HandleFunc("/auth/callback", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
loginResp, err := oauth2Auth.OAuth2HandleCallback(r.Context(), "custom-provider", code, state)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
_ = json.NewEncoder(w).Encode(loginResp)
})
_ = http.ListenAndServe(":8080", router)
}
// Example: Multi-Provider OAuth2 with Security Integration
func ExampleOAuth2MultiProvider() {
db, _ := sql.Open("postgres", "connection-string")
// Create OAuth2 authenticators for multiple providers
googleAuth := NewGoogleAuthenticator(
"google-client-id",
"google-client-secret",
"http://localhost:8080/auth/google/callback",
db,
)
githubAuth := NewGitHubAuthenticator(
"github-client-id",
"github-client-secret",
"http://localhost:8080/auth/github/callback",
db,
)
// Create column and row security providers
colSec := NewDatabaseColumnSecurityProvider(db)
rowSec := NewDatabaseRowSecurityProvider(db)
router := mux.NewRouter()
// Google OAuth2 routes
router.HandleFunc("/auth/google/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := googleAuth.OAuth2GenerateState()
authURL, _ := googleAuth.OAuth2GetAuthURL("google", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
router.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
loginResp, err := googleAuth.OAuth2HandleCallback(r.Context(), "google", code, state)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: loginResp.Token,
Path: "/",
MaxAge: int(loginResp.ExpiresIn),
HttpOnly: true,
})
http.Redirect(w, r, "/dashboard", http.StatusTemporaryRedirect)
})
// GitHub OAuth2 routes
router.HandleFunc("/auth/github/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := githubAuth.OAuth2GenerateState()
authURL, _ := githubAuth.OAuth2GetAuthURL("github", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
router.HandleFunc("/auth/github/callback", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
loginResp, err := githubAuth.OAuth2HandleCallback(r.Context(), "github", code, state)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: loginResp.Token,
Path: "/",
MaxAge: int(loginResp.ExpiresIn),
HttpOnly: true,
})
http.Redirect(w, r, "/dashboard", http.StatusTemporaryRedirect)
})
// Use Google auth for protected routes (or GitHub - both work)
provider, _ := NewCompositeSecurityProvider(googleAuth, colSec, rowSec)
securityList, _ := NewSecurityList(provider)
// Protected route with authentication
protectedRouter := router.PathPrefix("/api").Subrouter()
protectedRouter.Use(NewAuthMiddleware(securityList))
protectedRouter.Use(SetSecurityMiddleware(securityList))
protectedRouter.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
userCtx, _ := GetUserContext(r.Context())
_ = json.NewEncoder(w).Encode(userCtx)
})
_ = http.ListenAndServe(":8080", router)
}
// Example: OAuth2 with Token Refresh
func ExampleOAuth2TokenRefresh() {
db, _ := sql.Open("postgres", "connection-string")
oauth2Auth := NewGoogleAuthenticator(
"your-client-id",
"your-client-secret",
"http://localhost:8080/auth/google/callback",
db,
)
router := mux.NewRouter()
// Refresh token endpoint
router.HandleFunc("/auth/refresh", func(w http.ResponseWriter, r *http.Request) {
var req struct {
RefreshToken string `json:"refresh_token"`
Provider string `json:"provider"` // "google", "github", etc.
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
// Default to google if not specified
if req.Provider == "" {
req.Provider = "google"
}
// Use OAuth2-specific refresh method
loginResp, err := oauth2Auth.OAuth2RefreshToken(r.Context(), req.RefreshToken, req.Provider)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
// Set new session cookie
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: loginResp.Token,
Path: "/",
MaxAge: int(loginResp.ExpiresIn),
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
_ = json.NewEncoder(w).Encode(loginResp)
})
_ = http.ListenAndServe(":8080", router)
}
// Example: OAuth2 Logout
func ExampleOAuth2Logout() {
db, _ := sql.Open("postgres", "connection-string")
oauth2Auth := NewGoogleAuthenticator(
"your-client-id",
"your-client-secret",
"http://localhost:8080/auth/google/callback",
db,
)
router := mux.NewRouter()
router.HandleFunc("/auth/logout", func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
cookie, err := r.Cookie("session_token")
if err == nil {
token = cookie.Value
}
}
if token != "" {
// Get user ID from session
userCtx, err := oauth2Auth.Authenticate(r)
if err == nil {
_ = oauth2Auth.Logout(r.Context(), LogoutRequest{
Token: token,
UserID: userCtx.UserID,
})
}
}
// Clear cookie
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
})
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("Logged out successfully"))
})
_ = http.ListenAndServe(":8080", router)
}
// Example: Complete OAuth2 Integration with Database Setup
func ExampleOAuth2Complete() {
db, _ := sql.Open("postgres", "connection-string")
// Create tables (run once)
setupOAuth2Tables(db)
// Create OAuth2 authenticator
oauth2Auth := NewGoogleAuthenticator(
"your-client-id",
"your-client-secret",
"http://localhost:8080/auth/google/callback",
db,
)
// Create security providers
colSec := NewDatabaseColumnSecurityProvider(db)
rowSec := NewDatabaseRowSecurityProvider(db)
provider, _ := NewCompositeSecurityProvider(oauth2Auth, colSec, rowSec)
securityList, _ := NewSecurityList(provider)
router := mux.NewRouter()
// Public routes
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("Welcome! <a href='/auth/google/login'>Login with Google</a>"))
})
router.HandleFunc("/auth/google/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := oauth2Auth.OAuth2GenerateState()
authURL, _ := oauth2Auth.OAuth2GetAuthURL("github", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
router.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
loginResp, err := oauth2Auth.OAuth2HandleCallback(r.Context(), "github", code, state)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: loginResp.Token,
Path: "/",
MaxAge: int(loginResp.ExpiresIn),
HttpOnly: true,
})
http.Redirect(w, r, "/dashboard", http.StatusTemporaryRedirect)
})
// Protected routes
protectedRouter := router.PathPrefix("/").Subrouter()
protectedRouter.Use(NewAuthMiddleware(securityList))
protectedRouter.Use(SetSecurityMiddleware(securityList))
protectedRouter.HandleFunc("/dashboard", func(w http.ResponseWriter, r *http.Request) {
userCtx, _ := GetUserContext(r.Context())
_, _ = fmt.Fprintf(w, "Welcome, %s! Your email: %s", userCtx.UserName, userCtx.Email)
})
protectedRouter.HandleFunc("/api/profile", func(w http.ResponseWriter, r *http.Request) {
userCtx, _ := GetUserContext(r.Context())
_ = json.NewEncoder(w).Encode(userCtx)
})
protectedRouter.HandleFunc("/auth/logout", func(w http.ResponseWriter, r *http.Request) {
userCtx, _ := GetUserContext(r.Context())
_ = oauth2Auth.Logout(r.Context(), LogoutRequest{
Token: userCtx.SessionID,
UserID: userCtx.UserID,
})
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
})
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
})
_ = http.ListenAndServe(":8080", router)
}
func setupOAuth2Tables(db *sql.DB) {
// Create tables from database_schema.sql
// This is a helper function - in production, use migrations
ctx := context.Background()
// Create users table if not exists
_, _ = db.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255),
user_level INTEGER DEFAULT 0,
roles VARCHAR(500),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP,
remote_id VARCHAR(255),
auth_provider VARCHAR(50)
)
`)
// Create user_sessions table (used for both regular and OAuth2 sessions)
_, _ = db.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS user_sessions (
id SERIAL PRIMARY KEY,
session_token VARCHAR(500) NOT NULL UNIQUE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_activity_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45),
user_agent TEXT,
access_token TEXT,
refresh_token TEXT,
token_type VARCHAR(50) DEFAULT 'Bearer',
auth_provider VARCHAR(50)
)
`)
}
// Example: All OAuth2 Providers at Once
func ExampleOAuth2AllProviders() {
db, _ := sql.Open("postgres", "connection-string")
// Create authenticator with ALL OAuth2 providers
auth := NewDatabaseAuthenticator(db).
WithOAuth2(OAuth2Config{
ClientID: "google-client-id",
ClientSecret: "google-client-secret",
RedirectURL: "http://localhost:8080/auth/google/callback",
Scopes: []string{"openid", "profile", "email"},
AuthURL: "https://accounts.google.com/o/oauth2/auth",
TokenURL: "https://oauth2.googleapis.com/token",
UserInfoURL: "https://www.googleapis.com/oauth2/v2/userinfo",
ProviderName: "google",
}).
WithOAuth2(OAuth2Config{
ClientID: "github-client-id",
ClientSecret: "github-client-secret",
RedirectURL: "http://localhost:8080/auth/github/callback",
Scopes: []string{"user:email"},
AuthURL: "https://github.com/login/oauth/authorize",
TokenURL: "https://github.com/login/oauth/access_token",
UserInfoURL: "https://api.github.com/user",
ProviderName: "github",
}).
WithOAuth2(OAuth2Config{
ClientID: "microsoft-client-id",
ClientSecret: "microsoft-client-secret",
RedirectURL: "http://localhost:8080/auth/microsoft/callback",
Scopes: []string{"openid", "profile", "email"},
AuthURL: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
TokenURL: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
UserInfoURL: "https://graph.microsoft.com/v1.0/me",
ProviderName: "microsoft",
}).
WithOAuth2(OAuth2Config{
ClientID: "facebook-client-id",
ClientSecret: "facebook-client-secret",
RedirectURL: "http://localhost:8080/auth/facebook/callback",
Scopes: []string{"email"},
AuthURL: "https://www.facebook.com/v12.0/dialog/oauth",
TokenURL: "https://graph.facebook.com/v12.0/oauth/access_token",
UserInfoURL: "https://graph.facebook.com/me?fields=id,name,email",
ProviderName: "facebook",
})
// Get list of configured providers
providers := auth.OAuth2GetProviders()
fmt.Printf("Configured OAuth2 providers: %v\n", providers)
router := mux.NewRouter()
// Google routes
router.HandleFunc("/auth/google/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := auth.OAuth2GenerateState()
authURL, _ := auth.OAuth2GetAuthURL("google", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
router.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
loginResp, err := auth.OAuth2HandleCallback(r.Context(), "google", r.URL.Query().Get("code"), r.URL.Query().Get("state"))
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
_ = json.NewEncoder(w).Encode(loginResp)
})
// GitHub routes
router.HandleFunc("/auth/github/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := auth.OAuth2GenerateState()
authURL, _ := auth.OAuth2GetAuthURL("github", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
router.HandleFunc("/auth/github/callback", func(w http.ResponseWriter, r *http.Request) {
loginResp, err := auth.OAuth2HandleCallback(r.Context(), "github", r.URL.Query().Get("code"), r.URL.Query().Get("state"))
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
_ = json.NewEncoder(w).Encode(loginResp)
})
// Microsoft routes
router.HandleFunc("/auth/microsoft/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := auth.OAuth2GenerateState()
authURL, _ := auth.OAuth2GetAuthURL("microsoft", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
router.HandleFunc("/auth/microsoft/callback", func(w http.ResponseWriter, r *http.Request) {
loginResp, err := auth.OAuth2HandleCallback(r.Context(), "microsoft", r.URL.Query().Get("code"), r.URL.Query().Get("state"))
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
_ = json.NewEncoder(w).Encode(loginResp)
})
// Facebook routes
router.HandleFunc("/auth/facebook/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := auth.OAuth2GenerateState()
authURL, _ := auth.OAuth2GetAuthURL("facebook", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
router.HandleFunc("/auth/facebook/callback", func(w http.ResponseWriter, r *http.Request) {
loginResp, err := auth.OAuth2HandleCallback(r.Context(), "facebook", r.URL.Query().Get("code"), r.URL.Query().Get("state"))
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
_ = json.NewEncoder(w).Encode(loginResp)
})
// Create security list for protected routes
colSec := NewDatabaseColumnSecurityProvider(db)
rowSec := NewDatabaseRowSecurityProvider(db)
provider, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
securityList, _ := NewSecurityList(provider)
// Protected routes work for ALL OAuth2 providers + regular sessions
protectedRouter := router.PathPrefix("/api").Subrouter()
protectedRouter.Use(NewAuthMiddleware(securityList))
protectedRouter.Use(SetSecurityMiddleware(securityList))
protectedRouter.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
userCtx, _ := GetUserContext(r.Context())
_ = json.NewEncoder(w).Encode(userCtx)
})
_ = http.ListenAndServe(":8080", router)
}