authware

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 14, 2026 License: MIT Imports: 25 Imported by: 0

README

go-authware

Minimal-dependency HTTP authentication library for Go servers.

Go Version CI Go Report Card Go Reference

Features

  • Four auth modes: None, Bearer, API Key, OAuth/JWT
  • OIDC auto-discovery — works with any OpenID Connect provider
  • Minimal dependencies — stdlib + go-jsonfast for zero-alloc JSON parsing
  • Constant-time comparisons via crypto/subtle on all secret paths
  • JWT/JWKS support: HMAC-SHA256/384/512, RSA (PKCS1v15 + PSS), ECDSA (P-256/384/521)
  • JWKS auto-refresh with cache TTL and stampede prevention
  • RFC 9728 Protected Resource Metadata
  • Zero-alloc hot paths using unsafe.Slice for string→[]byte views
  • Nginx auth_request compatible handler
  • Environment variable configuration via ConfigFromEnv

Install

go get github.com/ubyte-source/go-authware

Quick Start

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/ubyte-source/go-authware"
)

func main() {
    auth, err := authware.New(&authware.Config{
        Mode:        authware.ModeBearer,
        BearerToken: "my-secret-token",
    }, nil)
    if err != nil {
        log.Fatal(err)
    }

    http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
        id, err := auth.Authenticate(r)
        if err != nil {
            status, header, msg := auth.Challenge(err, "")
            if header != "" {
                w.Header().Set("WWW-Authenticate", header)
            }
            http.Error(w, msg, status)
            return
        }
        fmt.Fprintf(w, "Hello, %s (method: %s)", id.Subject, id.Method)
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}

Middleware

auth, _ := authware.New(cfg, nil)
mux := http.NewServeMux()
mux.Handle("/api/", authware.Middleware(auth)(apiHandler))
mux.Handle("/admin/", authware.Middleware(auth)(
    authware.RequireScopes("admin")(adminHandler),
))

The authenticated identity is available in the request context:

id, ok := authware.IdentityFromContext(r.Context())
// id.Subject, id.Method, id.Scopes

OAuth / JWT with OIDC Auto-Discovery

When OAuthJWKSURL is omitted, the library fetches {issuer}/.well-known/openid-configuration to resolve the JWKS endpoint automatically. Works with Google, Auth0, Keycloak, Azure AD, Okta, Anthropic, and any other OIDC-compliant provider.

auth, err := authware.New(&authware.Config{
    Mode:                authware.ModeOAuth,
    OAuthIssuer:         "https://accounts.google.com",
    OAuthAudience:       "my-api",
    OAuthRequiredScopes: []string{"api:read"},
}, nil)

Explicit JWKS URL is still supported:

auth, err := authware.New(&authware.Config{
    Mode:          authware.ModeOAuth,
    OAuthIssuer:   "https://auth.example.com",
    OAuthAudience: "my-api",
    OAuthJWKSURL:  "https://auth.example.com/.well-known/jwks.json",
}, nil)

Nginx auth_request

The AuthCheckHandler returns an http.Handler compatible with nginx's auth_request module. On success it returns 200 with identity headers; on failure 401/403.

mux.Handle("/check", authware.AuthCheckHandler(auth))

nginx configuration:

location /api/ {
    auth_request /check;
    auth_request_set $auth_subject $upstream_http_x_auth_subject;
    auth_request_set $auth_method  $upstream_http_x_auth_method;
    proxy_set_header X-Auth-Subject $auth_subject;
    proxy_set_header X-Auth-Method  $auth_method;
    proxy_pass http://backend;
}

location = /check {
    internal;
    proxy_pass http://127.0.0.1:9090/check;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
}

The AuthCheckHandler can be used to build a standalone auth-check microservice that sits behind nginx and validates every incoming request.

Environment Configuration

ConfigFromEnv reads AUTH_* environment variables for easy deployment. See .env.example for all available variables.

cfg := authware.ConfigFromEnv()
auth, err := authware.New(cfg, nil)

Auth Modes

Mode Config Fields Description
none Pass-through, no authentication
bearer BearerToken Static bearer token in Authorization: Bearer <token>
apikey APIKey, APIKeyHeader Static key in custom header or Authorization: ApiKey <key>
oauth OAuthIssuer + optional fields Full JWT validation with JWKS / OIDC discovery

📁 Project Structure

go-authware/
├── authware.go         # Constructor, config, middleware, context helpers
├── types.go            # Config, Identity, Authenticator interface
├── bearer.go           # Bearer token authenticator
├── apikey.go           # API key authenticator
├── none.go             # Pass-through authenticator
├── jwt.go              # OAuth/JWT validation with JWKS auto-discovery
├── oidc.go             # OIDC auto-discovery
├── challenge.go        # HTTP challenge/error response formatting
├── handler.go          # Nginx auth_request handler
├── env.go              # Environment variable configuration
├── doc.go              # Package documentation
├── .golangci.yml       # Ultra-strict linter config (27 linters)
├── Makefile            # Build, test, bench, lint
├── CONTRIBUTING.md     # Contribution guidelines
├── SECURITY.md         # Security policy
└── LICENSE             # MIT

🤝 Contributing

Contributions are welcome. Please fork the repository, create a feature branch, and submit a pull request.

For contribution guidelines, see CONTRIBUTING.md.


🔖 Versioning

We use SemVer for versioning. For available versions, see the tags on this repository.


👤 Authors

  • Paolo FabrisInitial workubyte.it

See also the list of contributors who participated in this project.

📄 License

This project is licensed under the MIT License — see the LICENSE file for details.


☕ Support This Project

If go-authware has been useful for your projects, consider supporting its development:

Buy Me A Coffee


Star this repository if you find it useful.

For questions, issues, or contributions, visit our GitHub repository.

Documentation

Overview

Package authware provides pluggable HTTP authentication for Go servers.

Designed for high-throughput production services where authentication must be both correct and fast. Supports four modes out of the box:

  • None — pass-through, no authentication
  • Bearer — static token comparison (constant-time)
  • API Key — header or Authorization scheme comparison
  • OAuth / JWT — full JWT validation with JWKS auto-discovery

All static comparisons use crypto/subtle.ConstantTimeCompare. JWT validation uses zero-allocation unsafe string→[]byte views for token parsing and single-pass claims extraction via go-jsonfast.

The OAuth mode implements:

  • JWT signature verification (HMAC-SHA256/384/512, RSA, RSA-PSS, ECDSA)
  • OIDC auto-discovery from any OpenID Connect provider
  • Automatic JWKS key rotation with cache TTL
  • Stampede prevention via serialized refresh with double-check
  • RFC 9728 Protected Resource Metadata
  • Scope validation (scope and scp claims)
  • Clock skew tolerance for nbf/iat claims

OIDC Auto-Discovery

When OAuthJWKSURL is not configured, the library automatically discovers the JWKS endpoint by fetching {issuer}/.well-known/openid-configuration. This works with any OIDC-compliant provider: Google, Auth0, Keycloak, Azure AD, Okta, Anthropic, and others.

Middleware

The package provides standard HTTP middleware for common auth patterns:

auth, _ := authware.New(cfg, nil)
mux := http.NewServeMux()
mux.Handle("/api/", authware.Middleware(auth)(apiHandler))
mux.Handle("/admin/", authware.Middleware(auth)(
    authware.RequireScopes("admin")(adminHandler),
))

The authenticated identity is stored in the request context and can be retrieved via IdentityFromContext:

id := authware.IdentityFromContext(r.Context())

Nginx auth_request

AuthCheckHandler returns an http.Handler compatible with nginx's auth_request module. On success it sets X-Auth-Subject, X-Auth-Method, and X-Auth-Scopes response headers for the upstream:

mux.Handle("/check", authware.AuthCheckHandler(auth))

Environment Configuration

ConfigFromEnv reads AUTH_* environment variables to build a Config, making it easy to configure the provider without code changes:

cfg := authware.ConfigFromEnv()
auth, err := authware.New(cfg, nil)

Direct Usage

auth, err := authware.New(cfg, nil)
id, err := auth.Authenticate(r)

Dependencies

This library depends only on go-jsonfast (https://github.com/ubyte-source/go-jsonfast) for zero-allocation JWT claims parsing. No other external modules are required.

Index

Examples

Constants

View Source
const (
	ModeNone   = "none"
	ModeBearer = "bearer"
	ModeAPIKey = "apikey"
	ModeOAuth  = "oauth"
)

Supported authentication modes.

Variables

This section is empty.

Functions

func AuthCheckHandler

func AuthCheckHandler(auth Authenticator) http.Handler

AuthCheckHandler returns an HTTP handler for use with nginx auth_request or similar reverse-proxy authentication subrequests.

On success it returns 200 with the following response headers:

  • X-Auth-Subject: the authenticated subject
  • X-Auth-Method: the authentication method used
  • X-Auth-Scopes: space-separated scopes (if any)

On failure it returns the appropriate HTTP status (401/403) with a WWW-Authenticate header for Bearer-based schemes.

Nginx configuration example:

location /api {
    auth_request /check;
    auth_request_set $auth_user $upstream_http_x_auth_subject;
}
location = /check {
    internal;
    proxy_pass http://authcheck:9090/check;
    proxy_pass_request_headers on;
}
Example
package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/ubyte-source/go-authware"
)

func main() {
	auth, err := authware.New(&authware.Config{
		Mode:        authware.ModeBearer,
		BearerToken: "secret",
	}, nil)
	if err != nil {
		log.Fatal(err)
	}

	handler := authware.AuthCheckHandler(auth)

	r := httptest.NewRequest(http.MethodGet, "/check", http.NoBody)
	r.Header.Set("Authorization", "Bearer secret")
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, r)

	fmt.Println(w.Code)
	fmt.Println(w.Header().Get("X-Auth-Method"))
}
Output:
200
bearer

func Middleware

func Middleware(auth Authenticator) func(http.Handler) http.Handler

Middleware returns an HTTP middleware that authenticates every request. On success the Identity is stored in the request context; on failure the appropriate HTTP status and challenge header are returned.

Example
package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/ubyte-source/go-authware"
)

func main() {
	auth, err := authware.New(&authware.Config{
		Mode:        authware.ModeBearer,
		BearerToken: "tok",
	}, nil)
	if err != nil {
		log.Fatal(err)
	}

	handler := authware.Middleware(auth)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		id, _ := authware.IdentityFromContext(r.Context())
		w.Header().Set("X-Subject", id.Subject)
	}))

	r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	r.Header.Set("Authorization", "Bearer tok")
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, r)

	fmt.Println(w.Code)
}
Output:
200

func RequireScopes

func RequireScopes(scopes ...string) func(http.Handler) http.Handler

RequireScopes returns an HTTP middleware that verifies the authenticated identity (from context) possesses all the specified scopes. Must be applied after Middleware.

func WithIdentity

func WithIdentity(ctx context.Context, id Identity) context.Context

WithIdentity returns a copy of ctx carrying the given Identity.

Types

type Authenticator

type Authenticator interface {
	// Authenticate returns the authenticated identity or an error.
	Authenticate(r *http.Request) (Identity, error)
	// Challenge returns the HTTP status, WWW-Authenticate header, and error message.
	Challenge(err error, resourceMetadataURL string) (status int, header string, message string)
	// Metadata returns RFC 9728 Protected Resource Metadata, or nil if not applicable.
	Metadata(resource string) *ProtectedResourceMetadata
}

Authenticator validates an inbound HTTP request.

func New

func New(cfg *Config, client *http.Client) (Authenticator, error)

New creates an Authenticator from the provided configuration.

Example (ApiKey)
package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/ubyte-source/go-authware"
)

func main() {
	auth, err := authware.New(&authware.Config{
		Mode:   authware.ModeAPIKey,
		APIKey: "secret-key",
	}, nil)
	if err != nil {
		log.Fatal(err)
	}

	r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	r.Header.Set("X-API-Key", "secret-key")

	id, err := auth.Authenticate(r)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(id.Method)
}
Output:
apikey
Example (Bearer)
package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/ubyte-source/go-authware"
)

func main() {
	auth, err := authware.New(&authware.Config{
		Mode:        authware.ModeBearer,
		BearerToken: "my-secret-token",
	}, nil)
	if err != nil {
		log.Fatal(err)
	}

	r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	r.Header.Set("Authorization", "Bearer my-secret-token")

	id, err := auth.Authenticate(r)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(id.Method)
}
Output:
bearer
Example (None)
package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/ubyte-source/go-authware"
)

func main() {
	auth, err := authware.New(&authware.Config{
		Mode: authware.ModeNone,
	}, nil)
	if err != nil {
		log.Fatal(err)
	}

	r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	id, err := auth.Authenticate(r)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(id.Method)
}
Output:
none

type Config

type Config struct {
	Mode                       string
	Realm                      string
	BearerToken                string
	APIKey                     string
	APIKeyHeader               string
	OAuthIssuer                string
	OAuthAudience              string
	OAuthJWKSURL               string
	OAuthHMACSecret            string
	OAuthResource              string
	OAuthResourceDocumentation string
	OAuthResourceName          string

	OAuthRequiredScopes       []string
	OAuthAuthorizationServers []string
}

Config defines the supported HTTP authentication modes.

func ConfigFromEnv

func ConfigFromEnv() *Config

ConfigFromEnv reads authentication configuration from environment variables. This is intended for building standalone auth-check servers or configuring authentication in containerized deployments.

Environment variables:

  • AUTH_MODE: none|bearer|apikey|oauth
  • AUTH_REALM: realm for WWW-Authenticate header (default: restricted)
  • AUTH_BEARER_TOKEN: static bearer token
  • AUTH_API_KEY: static API key
  • AUTH_API_KEY_HEADER: custom header name for API key (default: X-API-Key)
  • AUTH_OAUTH_ISSUER: OAuth issuer URL
  • AUTH_OAUTH_AUDIENCE: expected audience claim
  • AUTH_OAUTH_JWKS_URL: JWKS endpoint (auto-discovered via OIDC if empty)
  • AUTH_OAUTH_HMAC_SECRET: HMAC shared secret (testing only)
  • AUTH_OAUTH_REQUIRED_SCOPES: comma-separated required scopes
  • AUTH_OAUTH_RESOURCE: protected resource identifier (RFC 9728)
  • AUTH_OAUTH_RESOURCE_DOCUMENTATION: resource documentation URL
  • AUTH_OAUTH_RESOURCE_NAME: human-readable resource name
  • AUTH_OAUTH_AUTHORIZATION_SERVERS: comma-separated authorization server URLs
Example
package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/ubyte-source/go-authware"
)

func main() {
	cfg := authware.ConfigFromEnv()
	auth, err := authware.New(cfg, nil)
	if err != nil {
		log.Fatal(err)
	}

	r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	id, err := auth.Authenticate(r)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(id.Method)
}
Output:
none

type Identity

type Identity struct {
	Subject string
	Method  string
	Scopes  string
}

Identity describes the authenticated caller.

func IdentityFromContext

func IdentityFromContext(ctx context.Context) (Identity, bool)

IdentityFromContext returns the authenticated identity from the request context. Returns the zero value and false if the request was not authenticated.

Example
package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/ubyte-source/go-authware"
)

func main() {
	auth, err := authware.New(&authware.Config{
		Mode:        authware.ModeBearer,
		BearerToken: "tok",
	}, nil)
	if err != nil {
		log.Fatal(err)
	}

	handler := authware.Middleware(auth)(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		id, ok := authware.IdentityFromContext(r.Context())
		fmt.Println(ok, id.Method)
	}))

	r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	r.Header.Set("Authorization", "Bearer tok")
	handler.ServeHTTP(httptest.NewRecorder(), r)
}
Output:
true bearer

type ProtectedResourceMetadata

type ProtectedResourceMetadata struct {
	Resource              string   `json:"resource"`
	ResourceDocumentation string   `json:"resource_documentation,omitempty"`
	ResourceName          string   `json:"resource_name,omitempty"`
	AuthorizationServers  []string `json:"authorization_servers,omitempty"`
	ScopesSupported       []string `json:"scopes_supported,omitempty"`

	BearerMethodsSupported []string `json:"bearer_methods_supported,omitempty"`
}

ProtectedResourceMetadata is served from RFC 9728 metadata discovery endpoints.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL