Skip to content

📝 [Proposal]: Add Enterprise-Grade SPNEGO Authentication Middleware #1367

@jarod2011

Description

@jarod2011

Feature Proposal Description

Add Enterprise-Grade SPNEGO Authentication Middleware
This middleware provides Kerberos-based authentication for Fiber v3:

  • Handles full SPNEGO protocol (WWW-Authenticate challenges + Authorization validation)
  • Dynamic keytab loading via callback functions (files, secrets managers, databases)
  • Sets authenticated principal in c.Locals("user")
  • System credential cache fallback
  • Automatic SPN/realm resolution
  • Customizable unauthorized responses

Workflow:

  1. Client accesses protected route
  2. Middleware returns 401 + WWW-Authenticate: Negotiate
  3. Client provides Kerberos ticket (browser auto-completes)
  4. Middleware validates ticket and decrypt request user info
  5. Success: Continue route with c.Locals("user") set

Alignment with Express API

This implementation follows Express patterns

authMiddleware, err := NewSpnegoKrb5AuthenticateMiddleware(&Config{
	KeytabLookup: keytabLookup,
})
app.Use("/protected", authMiddleware)
app.Get("/protected/resource", func(c fiber.Ctx) error {
	identity, ok := GetAuthenticatedIdentityFromContext(c)
	if !ok {
		return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
	}
	return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName()))
})

HTTP RFC Standards Compliance

Fully compliant with:

  • RFC 4559 (SPNEGO-based Kerberos HTTP Authentication)
    • Section 4.1: Negotiate scheme implementation
    • Section 4.2: GSS-API message encapsulation
  • RFC 4120 (Kerberos V5)
    • AP-REQ/AP-REP validation
    • Service ticket verification
  • RFC 7235 (HTTP/1.1 Authentication)
    • WWW-Authenticate challenge generation
    • Authorization header processing

Compliance ensured through:

  • Standardized gokrb5 implementation
  • Test coverage for all authentication flows
  • Support for MIT Kerberos and Active Directory

API Stability

// KeytabLookupFunc is a function type that returns a keytab or an error
// It's used to look up the keytab dynamically when needed
// This design allows for extensibility, enabling keytab retrieval from various sources
// such as databases, remote services, or other custom implementations beyond static files
type KeytabLookupFunc func() (*keytab.Keytab, error)

// Config holds the configuration for the SPNEGO middleware
// It includes the keytab lookup function and a logger
type Config struct {
	// KeytabLookup is a function that retrieves the keytab
	KeytabLookup KeytabLookupFunc
	// Log is the logger used for middleware logging
	Log *log.Logger
}

Feature Examples

This just a example server
1. user should use `kinit` login to kerberos at first and use `klist` check session.
2. use `curl --negotiate ` to trigger spnego authenticate

app := fiber.New()
keytabLookup, err := NewKeytabFileLookupFunc("/keytabFile/one.keytab", "/keytabFile/two.keyta")
if err != nil {
	panic(fmt.Errorf("create keytab lookup function failed: %w", err))
}
authMiddleware, err := NewSpnegoKrb5AuthenticateMiddleware(&Config{
	KeytabLookup: keytabLookup,
})
if err != nil {
	panic(fmt.Errorf("create spnego middleware failed: %w", err))
}
// Apply the middleware to protected routes
app.Use("/protected", authMiddleware)
// Access authenticated identity
app.Get("/protected/resource", func(c fiber.Ctx) error {
    identity, ok := GetAuthenticatedIdentityFromContext(c)
	if !ok {
		return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
	}
	return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName()))
})
log.Info("Server is running on :3000")
go func() {
	<-time.After(time.Second * 1)
	fmt.Println("use curl -kv --negotiate http://sso.example.local:3000/protected/resource")
	fmt.Println("if response is 401, execute `klist` to check use kerberos session")
	<-time.After(time.Second * 2)
	fmt.Println("close server")
	if err = app.Shutdown(); err != nil {
		panic(fmt.Errorf("shutdown server failed: %w", err))
	}
}()
if err := app.Listen("sso.example.local:3000"); err != nil {
	panic(fmt.Errorf("start server failed: %w", err))
}

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have searched for existing issues that describe my proposal before opening this one.
  • I understand that a proposal that does not meet these guidelines may be closed without explanation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions