doauth

package module
v0.0.0-...-18b5938 Latest Latest
Warning

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

Go to latest
Published: Apr 27, 2026 License: MIT Imports: 15 Imported by: 0

README

doauth

doauth is a robust Go library designed to simplify OAuth2 authentication by automating the discovery of authorization server metadata. It is built to handle modern, complex environments where endpoints might not be known upfront, or where resources point to their authorization servers via standard discovery protocols.

Key Features

  • Greedy Discovery: Automatically searches for metadata using OIDC (OpenID Connect), RFC 8414 (OAuth 2.0 Authorization Server Metadata), and RFC 9728 (Discovery of Protected Resource Metadata).
  • Resource Probing: Can probe a protected resource URL to find its authorization server via WWW-Authenticate headers or X-Discovery-URL.
  • PKCE by Default: Securely handles Proof Key for Code Exchange (S256) to protect against code injection attacks.
  • Local Flow for CLIs: Includes a built-in local web server to automatically capture authorization callbacks in CLI applications.
  • Flexible Configuration: Supports both automatic discovery and manual endpoint overrides.
  • Customizable: Allows passing a custom http.Client for all network operations (discovery, exchange, refresh).
  • Stateless Exchange: Designed to work across different application instances or process boundaries.

Installation

go get github.com/diaphora/doauth

Quick Start (Standard Discovery)

package main

import (
    "context"
    "fmt"
    "github.com/diaphora/doauth"
)

func main() {
    ctx := context.Background()

    // 1. Initialize the Authenticator
    auth, _ := doauth.NewAuthenticator(doauth.Config{
        BaseURL:      "https://api.example.com",
        ClientID:     "your-client-id",
        RedirectURL:  "http://localhost:8080/callback",
    })

    // 2. Discover endpoints (OIDC, RFC 8414, RFC 9728)
    needed, _ := auth.Discover(ctx)
    if !needed {
        fmt.Println("No authentication required for this resource")
        return
    }

    // 3. Generate the Authorization URL (PKCE is enabled by default)
    url, state, verifier, _ := auth.GetAuthURL()
    fmt.Printf("Visit this URL to authorize: %s\n", url)

    // ... after getting the 'code' from your redirect handler ...
    code := "received-auth-code"

    // 4. Exchange the code for a token
    token, _ := auth.Exchange(ctx, code, state, verifier)
    fmt.Printf("Access Token: %s\n", token.AccessToken)
}

Advanced Discovery Logic

doauth uses a "greedy" approach to find authorization metadata. When you call Discover(), it performs the following steps:

  1. Standard Well-Known Paths: It checks the resource path and the host root for:
    • /.well-known/openid-configuration
    • /.well-known/oauth-authorization-server
    • /.well-known/oauth-protected-resource
  2. Greedy Fetch: It attempts to fetch the BaseURL directly, as some servers serve metadata at the root of the API path.
  3. Probing: If a resource returns 401 Unauthorized, doauth parses the WWW-Authenticate header looking for issuer or resource_metadata (RFC 9728) to find the actual authorization server.
  4. Chain Resolution: If the discovery points to a "Protected Resource Metadata" (RFC 9728), it follows the chain to the actual Authorization Server automatically.

Local Flow for CLI Applications

If you are building a CLI tool, you can use the LocalFlow helper to automate the browser opening and callback capture:

flow := doauth.NewLocalFlow(doauth.WithPort(8080))

// Open the system browser
_ = flow.OpenBrowser(authURL)

// Block until the code is received at http://localhost:8080/callback
result, _ := flow.WaitForCode(ctx)
token, _ := auth.Exchange(ctx, result.Code, state, verifier)

Manual Configuration

If your environment doesn't support discovery, or you want to bypass it, you can provide endpoints manually:

auth, _ := doauth.NewAuthenticator(doauth.Config{
    ClientID:         "my-client",
    RedirectURL:      "http://localhost/callback",
    AuthorizationURL: "https://auth.example.com/authorize",
    TokenURL:         "https://auth.example.com/token",
})

// Discover() will return immediately without network calls
_, _ = auth.Discover(ctx)

Custom HTTP Client

Use the Options pattern to provide a custom client (e.g., for proxying, custom timeouts, or logging):

myClient := &http.Client{Timeout: 30 * time.Second}
auth, _ := doauth.NewAuthenticator(cfg, doauth.WithHTTPClient(myClient))

PKCE Support

PKCE is enabled by default for all generated URLs. You can toggle it or provide a custom state:

url, state, verifier, _ := auth.GetAuthURL(
    doauth.WithPKCE(true),
    doauth.WithState("custom-secure-state"),
)

RFC Compliance

  • OIDC Discovery: Fully supported.
  • RFC 8414: OAuth 2.0 Authorization Server Metadata.
  • RFC 9728: Discovery of Protected Resource Metadata.
  • RFC 7636: PKCE (Proof Key for Code Exchange).
  • RFC 6750: Bearer Token usage via AuthorizeRequest.

License

MIT

Documentation

Index

Constants

View Source
const (
	PathOpenIDConfig        = "/.well-known/openid-configuration"
	PathOAuthAuthServer     = "/.well-known/oauth-authorization-server"
	PathOAuthProtectedRoute = "/.well-known/oauth-protected-resource"
)

Well-known paths

View Source
const (
	HeaderAuthorization   = "Authorization"
	HeaderWWWAuthenticate = "WWW-Authenticate"
	HeaderXDiscoveryURL   = "X-Discovery-URL"
)

Header names

View Source
const (
	ParamCode                = "code"
	ParamState               = "state"
	ParamError               = "error"
	ParamResource            = "resource"
	ParamCodeChallenge       = "code_challenge"
	ParamCodeChallengeMethod = "code_challenge_method"
	ParamCodeVerifier        = "code_verifier"
)

OAuth2 parameter names

View Source
const (
	CmdWindows  = "rundll32"
	ArgsWindows = "url.dll,FileProtocolHandler"
	CmdDarwin   = "open"
	CmdLinux    = "xdg-open"
)

Browser commands

View Source
const (
	DefaultPort         = 8080
	DefaultCallbackPath = "/callback"
	BearerPrefix        = "Bearer "
)

Default values

View Source
const (
	MethodS256 = "S256"
)

PKCE methods

Variables

This section is empty.

Functions

func GenerateChallenge

func GenerateChallenge(verifier string) string

GenerateChallenge creates a PKCE code challenge from a verifier using the S256 method. S256(verifier) = BASE64URL-ENCODE(SHA256(ASCII(verifier)))

func GenerateVerifier

func GenerateVerifier() (string, error)

GenerateVerifier creates a cryptographically strong random string to be used as a PKCE code verifier. The verifier must be between 43 and 128 characters long.

Types

type AuthURLOption

type AuthURLOption func(*authURLOptions)

AuthURLOption defines a functional option for GetAuthURL.

func WithPKCE

func WithPKCE(enabled bool) AuthURLOption

WithPKCE enables or disables PKCE (enabled by default).

func WithState

func WithState(state string) AuthURLOption

WithState sets a custom state for the authorization URL.

func WithVerifier

func WithVerifier(verifier string) AuthURLOption

WithVerifier sets a custom code verifier for the authorization URL.

type Authenticator

type Authenticator struct {
	// contains filtered or unexported fields
}

Authenticator handles the OAuth2 flow including discovery, URL generation, and token exchange.

func NewAuthenticator

func NewAuthenticator(cfg Config, opts ...Option) (*Authenticator, error)

NewAuthenticator creates a new Authenticator instance.

func (*Authenticator) ApplyMetadata

func (a *Authenticator) ApplyMetadata(meta *Metadata)

ApplyMetadata updates the internal oauth2.Config with endpoints and scopes from discovery.

func (*Authenticator) AuthorizeRequest

func (a *Authenticator) AuthorizeRequest(token *oauth2.Token, req *http.Request)

AuthorizeRequest injects the token as a Bearer token in the request header.

func (*Authenticator) Discover

func (a *Authenticator) Discover(ctx context.Context) (bool, error)

Discover attempts to find OAuth2 metadata. It returns true if authentication is required, false otherwise.

func (*Authenticator) Exchange

func (a *Authenticator) Exchange(ctx context.Context, code, state, verifier string) (*oauth2.Token, error)

Exchange exchanges an authorization code for a token.

func (*Authenticator) GetAuthURL

func (a *Authenticator) GetAuthURL(opts ...AuthURLOption) (string, string, string, error)

GetAuthURL generates the authorization URL. It returns the URL, the state, and the code verifier if PKCE was used.

func (*Authenticator) GetMetadata

func (a *Authenticator) GetMetadata() *Metadata

GetMetadata returns the retrieved metadata.

func (*Authenticator) RefreshToken

func (a *Authenticator) RefreshToken(ctx context.Context, token *oauth2.Token) (*oauth2.Token, error)

RefreshToken returns a fresh token, using the refresh token if the current one is expired.

type Config

type Config struct {
	// Name is the application's name
	Name string
	// BaseURL is the authorization server's base URL used for discovery.
	BaseURL string
	// ClientID is the application's ID.
	ClientID string
	// ClientSecret is the application's secret. Prefer PKCE if possible.
	ClientSecret string
	// RedirectURL is where the user will be sent after authorization.
	RedirectURL string
	// Scopes is a list of requested permissions.
	Scopes []string

	// AuthorizationURL and TokenURL can be provided manually to bypass discovery.
	AuthorizationURL string
	TokenURL         string
}

Config represents the configuration for the Authenticator.

func (*Config) McpFingerprint

func (c *Config) McpFingerprint() string

type LocalFlow

type LocalFlow struct {
	// Port is the port number for the local callback server (e.g., 8080).
	Port int
	// Timeout is how long to wait for the callback before giving up.
	Timeout time.Duration
	// CallbackPath is the path for the redirect URI (e.g., "/callback").
	CallbackPath string
	// contains filtered or unexported fields
}

LocalFlow handles the local web-based OAuth2 flow.

func NewLocalFlow

func NewLocalFlow(opts ...LocalFlowOption) *LocalFlow

NewLocalFlow creates a new LocalFlow with the provided options and sensible defaults.

func (*LocalFlow) OpenBrowser

func (f *LocalFlow) OpenBrowser(url string) error

OpenBrowser opens the specified URL in the default system browser.

func (*LocalFlow) WaitForCode

func (f *LocalFlow) WaitForCode(ctx context.Context) (*Result, error)

WaitForCode starts a local HTTP server and blocks until the authorization code is received or the timeout is reached.

type LocalFlowOption

type LocalFlowOption func(*LocalFlow)

LocalFlowOption defines a functional option for configuring the LocalFlow.

func WithCallbackPath

func WithCallbackPath(path string) LocalFlowOption

WithCallbackPath sets the URL path for the callback.

func WithLocalFlowLogger

func WithLocalFlowLogger(logger *slog.Logger) LocalFlowOption

WithLocalFlowLogger sets a custom slog logger for the LocalFlow.

func WithPort

func WithPort(port int) LocalFlowOption

WithPort sets the port for the local callback server.

func WithTimeout

func WithTimeout(timeout time.Duration) LocalFlowOption

WithTimeout sets the timeout for waiting for the callback.

type Metadata

type Metadata struct {
	Issuer               string   `json:"issuer,omitempty"`
	AuthorizationURL     string   `json:"authorization_url,omitempty"`
	TokenURL             string   `json:"token_url,omitempty"`
	JWKSURI              string   `json:"jwks_uri,omitempty"`
	RegistrationEndpoint string   `json:"registration_endpoint,omitempty"`
	ScopesSupported      []string `json:"scopes_supported,omitempty"`
	ResponseTypes        []string `json:"response_types_supported,omitempty"`
	GrantTypes           []string `json:"grant_types_supported,omitempty"`
	CodeChallengeMethods []string `json:"code_challenge_methods_supported,omitempty"`

	// Protected Resource Metadata (RFC 9728)
	Resource             string   `json:"resource,omitempty"`
	AuthorizationServers []string `json:"authorization_servers,omitempty"`
}

Metadata contains the authorization server's metadata (RFC 8414 / RFC 9728).

func DiscoverMetadata

func DiscoverMetadata(ctx context.Context, baseURL string, client *http.Client, logger *slog.Logger) (*Metadata, error)

DiscoverMetadata attempts to find OAuth2 metadata for a given base URL.

func ProbeMetadata

func ProbeMetadata(ctx context.Context, baseURL string, client *http.Client, logger *slog.Logger) (*Metadata, bool, error)

ProbeMetadata checks if a resource is protected and attempts discovery via headers.

func (*Metadata) GetEndpoints

func (m *Metadata) GetEndpoints() (auth, token string)

GetEndpoints returns the resolved authorization and token URLs.

func (*Metadata) MarshalBinary

func (m *Metadata) MarshalBinary() ([]byte, error)

MarshalBinary encodes the Metadata struct into JSON bytes.

func (*Metadata) UnmarshalBinary

func (m *Metadata) UnmarshalBinary(data []byte) error

UnmarshalBinary decodes JSON bytes back into the Metadata struct.

func (*Metadata) UnmarshalJSON

func (m *Metadata) UnmarshalJSON(data []byte) error

UnmarshalJSON implements custom decoding to handle standard and aliased endpoint names.

type Option

type Option func(*Authenticator)

Option defines a functional option for configuring the Authenticator.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets a custom HTTP client for discovery and token exchange.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets a custom slog logger for the Authenticator.

type Result

type Result struct {
	Code  string
	State string
	Err   error
}

Result represents the data received from the callback server.

Jump to

Keyboard shortcuts

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