Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/raystack/salt/llms.txt

Use this file to discover all available pages before exploring further.

The auth package provides authentication and auditing capabilities for secure Go applications. It includes OpenID Connect (OIDC) authentication with PKCE support and comprehensive audit logging.

Sub-packages

OIDC

OpenID Connect authentication with PKCE flow

Audit

Audit logging for tracking user actions and system events

Installation

go get github.com/raystack/salt/auth/oidc
go get github.com/raystack/salt/auth/audit

OIDC Package

The oidc package provides OpenID Connect authentication with PKCE (Proof Key for Code Exchange) support for enhanced security.

Features

  • PKCE Support: Implements RFC 7636 for secure authorization
  • Browser-based Flow: Automatic browser opening for user authentication
  • Token Management: Handles access tokens, refresh tokens, and ID tokens
  • OAuth2 Integration: Built on top of golang.org/x/oauth2

Creating a Token Source

func NewTokenSource(ctx context.Context, conf *oauth2.Config, audience string) oauth2.TokenSource
Creates a new token source that handles the OIDC authentication flow. Example:
package main

import (
    "context"
    "fmt"
    "log"
    
    "github.com/raystack/salt/auth/oidc"
    "golang.org/x/oauth2"
)

func main() {
    ctx := context.Background()
    
    config := &oauth2.Config{
        ClientID:     "your-client-id",
        ClientSecret: "your-client-secret",
        RedirectURL:  "http://localhost:8080/callback",
        Endpoint: oauth2.Endpoint{
            AuthURL:  "https://auth.example.com/authorize",
            TokenURL: "https://auth.example.com/token",
        },
        Scopes: []string{"profile", "email"},
    }
    
    tokenSource := oidc.NewTokenSource(ctx, config, "your-api-audience")
    
    token, err := tokenSource.Token()
    if err != nil {
        log.Fatal("Failed to get token:", err)
    }
    
    fmt.Printf("Access Token: %s\n", token.AccessToken)
}

PKCE Flow

The OIDC package automatically implements the PKCE flow:
  1. Generate Code Verifier: Random 32-byte string
  2. Create Code Challenge: SHA256 hash of verifier
  3. Authorization Request: Includes code challenge
  4. Token Exchange: Includes code verifier
This prevents authorization code interception attacks.

Google Service Account Support

The package also supports Google Service Account authentication:
package main

import (
    "context"
    "log"
    
    "github.com/raystack/salt/auth/oidc"
    "google.golang.org/api/option"
)

func main() {
    ctx := context.Background()
    
    // Use service account credentials
    tokenSource, err := oidc.NewGoogleServiceAccountTokenSource(
        ctx,
        "path/to/service-account.json",
        "https://api.example.com",
    )
    if err != nil {
        log.Fatal(err)
    }
    
    token, err := tokenSource.Token()
    if err != nil {
        log.Fatal(err)
    }
    
    // Use token for API calls
    _ = token
}

Cobra Integration

The package provides helpers for integrating OIDC authentication into Cobra CLI applications:
package main

import (
    "github.com/raystack/salt/auth/oidc"
    "github.com/spf13/cobra"
)

func main() {
    var clientID, clientSecret string
    
    loginCmd := &cobra.Command{
        Use:   "login",
        Short: "Authenticate with OIDC",
        RunE: func(cmd *cobra.Command, args []string) error {
            return oidc.LoginCommand(cmd.Context(), clientID, clientSecret)
        },
    }
    
    loginCmd.Flags().StringVar(&clientID, "client-id", "", "OAuth2 client ID")
    loginCmd.Flags().StringVar(&clientSecret, "client-secret", "", "OAuth2 client secret")
    
    rootCmd := &cobra.Command{Use: "app"}
    rootCmd.AddCommand(loginCmd)
    rootCmd.Execute()
}

Audit Package

The audit package provides comprehensive audit logging for tracking user actions and system events.

Features

  • Actor Tracking: Records who performed each action
  • Metadata Support: Attach custom metadata to audit logs
  • Repository Pattern: Pluggable storage backends
  • Context Integration: Extracts audit information from context

Core Types

Service

type Service struct {
    // Internal fields
}
The main audit service that handles logging.

Log

type Log struct {
    Timestamp time.Time              `json:"timestamp"`
    Actor     string                 `json:"actor"`
    Action    string                 `json:"action"`
    Data      interface{}            `json:"data"`
    Metadata  map[string]interface{} `json:"metadata"`
}
Represents a single audit log entry.

Creating an Audit Service

func New(opts ...AuditOption) *Service
Example:
package main

import (
    "context"
    "log"
    
    "github.com/raystack/salt/auth/audit"
    "github.com/raystack/salt/auth/audit/repositories"
)

func main() {
    // Create PostgreSQL repository
    repo, err := repositories.NewPostgres(dbConfig)
    if err != nil {
        log.Fatal(err)
    }
    
    // Create audit service
    auditSvc := audit.New(
        audit.WithRepository(repo),
        audit.WithActorExtractor(extractActorFromContext),
        audit.WithMetadataExtractor(extractMetadataFromContext),
    )
    
    // Log an action
    ctx := audit.WithActor(context.Background(), "user@example.com")
    err = auditSvc.Log(ctx, "user.login", map[string]interface{}{
        "ip_address": "192.168.1.1",
        "user_agent": "Mozilla/5.0",
    })
    if err != nil {
        log.Fatal(err)
    }
}

Audit Options

WithRepository

func WithRepository(r repository) AuditOption
Sets the storage repository for audit logs.
repo, _ := repositories.NewPostgres(cfg)
auditSvc := audit.New(audit.WithRepository(repo))

WithActorExtractor

func WithActorExtractor(fn func(context.Context) (string, error)) AuditOption
Customizes how the actor (user) is extracted from context.
extractActor := func(ctx context.Context) (string, error) {
    user, ok := ctx.Value("user").(string)
    if !ok {
        return "system", nil
    }
    return user, nil
}

auditSvc := audit.New(audit.WithActorExtractor(extractActor))

WithMetadataExtractor

func WithMetadataExtractor(fn func(context.Context) map[string]interface{}) AuditOption
Extracts additional metadata from context.
extractMetadata := func(ctx context.Context) map[string]interface{} {
    return map[string]interface{}{
        "request_id": ctx.Value("request_id"),
        "trace_id":   ctx.Value("trace_id"),
    }
}

auditSvc := audit.New(audit.WithMetadataExtractor(extractMetadata))

Context Helpers

WithActor

func WithActor(ctx context.Context, actor string) context.Context
Adds actor information to context.
ctx := audit.WithActor(ctx, "user@example.com")

WithMetadata

func WithMetadata(ctx context.Context, md map[string]interface{}) (context.Context, error)
Adds metadata to context.
ctx, err := audit.WithMetadata(ctx, map[string]interface{}{
    "ip_address": "192.168.1.1",
    "user_agent": "Mozilla/5.0",
})

Logging Actions

func (s *Service) Log(ctx context.Context, action string, data interface{}) error
Logs an audit event. Example:
// User login
ctx := audit.WithActor(ctx, "alice@example.com")
err := auditSvc.Log(ctx, "user.login", map[string]interface{}{
    "method": "password",
    "success": true,
})

// Resource creation
err = auditSvc.Log(ctx, "resource.create", map[string]interface{}{
    "resource_type": "project",
    "resource_id":   "proj-123",
    "name":          "My Project",
})

// Permission change
err = auditSvc.Log(ctx, "permission.grant", map[string]interface{}{
    "user_id":    "user-456",
    "resource_id": "proj-123",
    "permission":  "admin",
})

PostgreSQL Repository

The package includes a PostgreSQL repository implementation:
package main

import (
    "github.com/raystack/salt/auth/audit/repositories"
    "github.com/raystack/salt/db"
)

func main() {
    dbClient, _ := db.New(db.Config{
        Driver: "postgres",
        URL:    "postgres://localhost:5432/mydb",
    })
    
    repo := repositories.NewPostgres(dbClient)
    
    auditSvc := audit.New(audit.WithRepository(repo))
}

Complete Example: Authenticated API with Audit

package main

import (
    "context"
    "log"
    "net/http"
    
    "github.com/raystack/salt/auth/audit"
    "github.com/raystack/salt/auth/audit/repositories"
    "github.com/raystack/salt/auth/oidc"
    "github.com/raystack/salt/db"
    "golang.org/x/oauth2"
)

func main() {
    // Setup audit logging
    dbClient, _ := db.New(db.Config{
        Driver: "postgres",
        URL:    "postgres://localhost:5432/audit",
    })
    
    repo := repositories.NewPostgres(dbClient)
    auditSvc := audit.New(
        audit.WithRepository(repo),
        audit.WithActorExtractor(func(ctx context.Context) (string, error) {
            user, _ := ctx.Value("user").(string)
            return user, nil
        }),
    )
    
    // Setup OIDC
    oauthConfig := &oauth2.Config{
        ClientID:     "client-id",
        ClientSecret: "client-secret",
        RedirectURL:  "http://localhost:8080/callback",
        Endpoint: oauth2.Endpoint{
            AuthURL:  "https://auth.example.com/authorize",
            TokenURL: "https://auth.example.com/token",
        },
    }
    
    // Authentication middleware
    authMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            token := r.Header.Get("Authorization")
            if token == "" {
                http.Error(w, "Unauthorized", http.StatusUnauthorized)
                return
            }
            
            // Validate token and extract user
            user := "user@example.com" // Extract from token
            ctx := context.WithValue(r.Context(), "user", user)
            
            next(w, r.WithContext(ctx))
        }
    }
    
    // API endpoint with audit logging
    http.HandleFunc("/api/resource", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        user := ctx.Value("user").(string)
        
        // Log the action
        auditCtx := audit.WithActor(ctx, user)
        auditSvc.Log(auditCtx, "resource.access", map[string]interface{}{
            "method": r.Method,
            "path":   r.URL.Path,
        })
        
        w.Write([]byte("Resource data"))
    }))
    
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Best Practices

Log all actions that involve data access, modification, or permission changes:
auditSvc.Log(ctx, "data.export", exportDetails)
Attach meaningful metadata to audit logs:
ctx, _ = audit.WithMetadata(ctx, map[string]interface{}{
    "ip_address": clientIP,
    "user_agent": userAgent,
})
Follow a naming convention for actions:
// Format: <resource>.<action>
"user.login"
"project.create"
"permission.revoke"
Never log tokens or passwords in audit logs:
auditSvc.Log(ctx, "auth.success", map[string]interface{}{
    "method": "password",
    // Don't include actual password or token
})