auth

package
v0.30.0 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: BSD-3-Clause-Clear Imports: 37 Imported by: 0

README

Auth Package

This package handles authentication (authn) and authorization (authz) for the OpenTDF platform.

Package Structure

auth/
├── authn.go           # Authentication middleware and token validation
├── connect_gate.go    # Connect request gate that authenticates before body decoding
├── connect_v2.go      # connect-go v2 authentication, authorization, and IPC interceptors
├── config.go          # Configuration types
├── discovery.go       # OIDC discovery
└── authz/             # Authorization interfaces and implementations
    ├── authorizer.go  # Authorizer interface and factory
    ├── resolver.go    # Resolver registry and resource dimension context
    └── casbin/        # Casbin authorizers
        ├── v1/        # Legacy path + action authorization
        └── v2/        # RPC + dimensions authorization

Connect Request Flow

The platform serves services registered with a connect-go v1 handler and services registered on a connect-go v2 server. Both authenticate from the request headers before the body is read.

External requests to connect-go v1 services pass through these stages:

  1. The HTTP router stores the request's HTTP method in the context (ContextWithHTTPMethod).
  2. ConnectAuthNRequestGate authenticates the request from its headers, including DPoP proof validation, before Connect reads, decompresses, or unmarshals the body. Unauthenticated requests are rejected without decoding the body.
  3. ConnectAuthNInterceptor sets DPoP-Nonce response headers when nonces are required. It authenticates only when the gate did not run, such as for handlers built outside the platform server.
  4. ConnectAuthZInterceptor authorizes the request. Unary authorization may read the decoded message through resource resolvers.

Requests rejected by the gate never reach the interceptor chain, so tracing interceptors record no span for them.

External requests to connect-go v2 services pass through these stages:

  1. ConnectAuthNInterceptorV2 authenticates the request from its headers. connect-go v2 runs server interceptors before it reads the body, so no gate is needed, and the interceptor reads the HTTP method from the call. It sets DPoP-Nonce and DPoP challenge headers on the response.
  2. ConnectAuthZInterceptorV2 authorizes the request. For a unary procedure with a resource resolver it receives the request message first, runs the resolver, and passes the same message to the handler. Every other request is authorized before the body is read.

Rejections from the v2 interceptors pass through the tracing interceptor, so they are recorded, and gRPC clients receive the DPoP challenge headers.

The in-process (IPC) server uses neither gate nor external authentication; IPCUnaryServerInterceptor and IPCServerInterceptorV2 handle its requests.

Authz v1

Authz v1 is the legacy Casbin model. Its policy rows have four fields:

p, subject, resource, action, effect

The enforcement request is (subject, resource, action). For Connect/gRPC requests, resource is the RPC procedure path normalized for v1 policy compatibility: gRPC-style resources omit the leading slash, while HTTP paths keep it. The action field matches the policy action column, typically read, write, delete, unsafe, or *.

Authz v2

Authz v2 is the Casbin RPC + dimensions model. It authorizes (subject, rpc, dimensions):

  • subject: extracted from JWT roles, client ID, and username. Roles and clients are typed as role:<name> and client:<id>.
  • rpc: full Connect/gRPC procedure path such as /policy.kasregistry.KeyAccessServerRegistryService/GetKey.
  • dimensions: a serialized ResolverContext resource such as kas_uri=https://kas-a.example.com, or * when no dimensions are available.

The interceptor calls a service-registered resolver only when the selected authorizer supports resource authorization. If no resolver is registered for the RPC, v2 evaluates the request with wildcard dimensions (*). Policies that require a concrete dimension then deny; policies using wildcard dimensions may still allow.

Services register resolvers through their scoped ScopedResolverRegistry during service startup. A resolver may return one or more resources, and every non-empty resource must be allowed for the request to pass.

Moving From Authz v1 to v2 Policy

When moving policy from v1 to v2:

  • Change policy rows from p, subject, resource, action, effect to p, subject, rpc, dimensions, effect.
  • Use the full Connect/gRPC RPC path in the rpc column, including the leading slash, for example /policy.kasregistry.KeyAccessServerRegistryService/GetKey.
  • Remove the separate action column. In v2, the RPC method represents the operation.
  • Use * for dimensions when the policy is only subject/RPC scoped.
  • Use concrete dimensions only for RPCs with registered resolvers. Today, GetKey and ListKeys can use kas_uri=<value>.

For example, a v1 read rule:

p, role:standard, kasregistry.*, read, allow

becomes a v2 RPC-scoped rule:

p, role:standard, /policy.kasregistry.KeyAccessServerRegistryService/Get*, *, allow

and a v2 dimension-scoped GetKey rule:

p, role:kas-reader, /policy.kasregistry.KeyAccessServerRegistryService/GetKey, kas_uri=https://kas.example.com, allow
Current v2 Dimension Coverage

The table lists production resolver coverage in this workspace. The v2 authorizer supports arbitrary dimension keys supplied by resolvers, but only the RPCs below currently have registered production resolvers.

RPC Available dimensions
/policy.kasregistry.KeyAccessServerRegistryService/GetKey kas_uri
/policy.kasregistry.KeyAccessServerRegistryService/ListKeys kas_uri
All other RPCs none (*)
Streaming and Bidi RPCs

The Connect authN and authz interceptors cover unary, streaming, and bidi RPCs. A streaming RPC is authenticated and authorized once, at stream establishment, before the handler runs.

Because no request message is available at establishment, resource-dimension resolvers cannot run for streams. Streaming authorization is therefore procedure-level only: the enforcement request always uses wildcard dimensions (*). As a result, a dimension-scoped v2 policy row (one that requires a concrete dimension such as kas_uri=...) never matches a stream, so dimension-scoped policy fails closed for streaming RPCs. To authorize a stream, grant it with a wildcard-dimension rule. This mirrors the raw-HTTP MuxHandler path, which is likewise procedure-level.

KAS URI Policy Encoding

When writing kas_uri dimension values in v2 Casbin policy, encode only the characters that conflict with dimension parsing:

  • % -> %25
  • & -> %26
  • literal * -> %2A (Only if you want to match exactly on *)

Other URI characters such as :, /, ?, =, and + can stay readable. For example, a KAS URI with query parameters should encode the query separator & in policy:

p, role:kas-reader, /policy.kasregistry.KeyAccessServerRegistryService/GetKey, kas_uri=https://kas.example.com?foo=bar%26baz=qux, allow

[!NOTE] This escaping only covers authz v2 dimension parsing. Policy rows are still Casbin CSV, so if a URI contains a literal comma, quote the policy field using valid CSV/Casbin syntax. Quotes and newlines should not appear unescaped in valid KAS URIs.

The embedded v2 default policy grants:

  • role:admin full access.
  • role:standard read access to policy Get*, List*, and Match* RPCs, access to KAS, health, discovery, and authorization services.
  • role:unknown access to /kas.AccessService/Rewrap.

Security Guidelines

Never Log Sensitive Authentication Data

DO NOT log the following:

  1. JWT Tokens - Never log full tokens, even at DEBUG level

    • Tokens can be replayed if logs are compromised
    • Tokens may contain PII in claims
    • Large tokens can be used for DoS attacks (disk/memory exhaustion)
    • Unsanitized token content can enable log injection attacks
  2. Credentials - Never log passwords, API keys, or secrets

  3. Full UserInfo responses - May contain PII

Safe to log:

  • Claim names (e.g., which claim was missing)
  • Extracted role/group names (after validation)
  • Subject identifiers (if not sensitive in your context)
  • Error types and messages (without embedding tokens)
Example: What NOT to do
// BAD - logs full token (security risk)
e.logger.Debug("processing token", slog.Any("token", token))

// BAD - token in error message
e.logger.Error("auth failed", slog.String("token", tokenString))
Example: Safe logging
// GOOD - no sensitive data
e.logger.Debug("extracting roles from token")

// GOOD - only logs claim name, not value
e.logger.Warn("claim not found", slog.String("claim", claimName))

// GOOD - logs extracted, bounded data
e.logger.Debug("roles extracted", slog.Int("count", len(roles)))
Log Injection Prevention

Even when logging "safe" data extracted from tokens, be aware that:

  • Claims can contain newlines (fake log entries)
  • Claims can contain ANSI escape codes
  • Claims can be arbitrarily large

Consider truncating or sanitizing any user-controlled data before logging.

Documentation

Index

Constants

View Source
const (
	ActionRead   = "read"
	ActionWrite  = "write"
	ActionDelete = "delete"
	ActionUnsafe = "unsafe"
	ActionOther  = "other"
)
View Source
const (
	// DiscoveryPath is the path to the discovery endpoint
	DiscoveryPath = "/.well-known/openid-configuration"
)

Variables

This section is empty.

Functions

func ContextWithHTTPMethod added in v0.25.5

func ContextWithHTTPMethod(ctx context.Context, method string) context.Context

ContextWithHTTPMethod records the HTTP method of an incoming request. Connect request gates run before Connect exposes call information, so the server's HTTP router stores the method here for ConnectAuthNRequestGate to bind DPoP proofs to the method the client actually used.

func HTTPMethodFromContext added in v0.25.5

func HTTPMethodFromContext(ctx context.Context) (string, bool)

HTTPMethodFromContext returns the HTTP method stored by ContextWithHTTPMethod.

func IPCMetadataClientInterceptor added in v0.11.0

func IPCMetadataClientInterceptor(log *logger.Logger) connect.UnaryInterceptorFunc

IPCMetadataClientInterceptor transfers gRPC outgoing metadata to Connect request headers for IPC calls

Types

type AccessTokenVerifier added in v0.15.0

type AccessTokenVerifier interface {
	VerifyAccessToken(ctx context.Context, tokenRaw string) (jwt.Token, error)
}

AccessTokenVerifier validates raw access tokens.

type AuthNConfig

type AuthNConfig struct {
	// Deprecated: use DPoP.Enforce (server.auth.dpop.enforce) instead. Still honored
	// during the migration window: DPoP is enforced when either field is true.
	EnforceDPoP  bool                       `mapstructure:"enforceDPoP" json:"enforceDPoP" default:"false"`
	Issuer       string                     `mapstructure:"issuer" json:"issuer"`
	Audience     string                     `mapstructure:"audience" json:"audience"`
	Policy       internalauthz.PolicyConfig `mapstructure:"policy" json:"policy"`
	CacheRefresh string                     `mapstructure:"cache_refresh_interval" json:"cache_refresh_interval"`
	DPoPSkew     time.Duration              `mapstructure:"dpopskew" json:"dpopskew" default:"1h"`
	TokenSkew    time.Duration              `mapstructure:"skew" json:"skew" default:"1m"`
	DPoP         DPoPConfig                 `mapstructure:"dpop" json:"dpop"`
}

AuthNConfig is the configuration need for the platform to validate tokens

type Authentication

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

Authentication holds a jwks cache and information about the openid configuration

func NewAuthenticator

func NewAuthenticator(ctx context.Context, cfg Config, logger *logger.Logger, wellknownRegistration func(namespace string, config any) error, opts ...AuthenticatorOption) (*Authentication, error)

Creates new authN which is used to verify tokens for a set of given issuers

func (*Authentication) AccessTokenVerifier added in v0.15.0

func (a *Authentication) AccessTokenVerifier() AccessTokenVerifier

AccessTokenVerifier returns the authenticator's shared access-token verifier.

func (Authentication) ConnectAuthNInterceptor added in v0.17.0

func (a Authentication) ConnectAuthNInterceptor() connect.Interceptor

ConnectAuthNInterceptor authenticates Connect requests and enriches the request context with configured token claims needed by later middleware. It covers unary, streaming, and bidi RPCs; streaming RPCs are authenticated once at stream establishment.

When ConnectAuthNRequestGate already authenticated the request, the interceptor reuses the gate's context and only sets DPoP-Nonce response headers; validating the same DPoP proof twice would trip replay detection.

func (Authentication) ConnectAuthNInterceptorV2 added in v0.30.0

func (a Authentication) ConnectAuthNInterceptorV2() connectv2.ServerInterceptor

ConnectAuthNInterceptorV2 authenticates connect-go v2 calls with the same checks as ConnectAuthNInterceptor. v2 runs server interceptors before the request body is read, so the check uses only the request headers and covers unary, streaming, and bidi RPCs.

func (Authentication) ConnectAuthNRequestGate added in v0.25.5

func (a Authentication) ConnectAuthNRequestGate() connect.RequestGateFunc

ConnectAuthNRequestGate authenticates Connect requests from their headers before Connect reads, decompresses, or unmarshals the request body. It covers unary, streaming, and bidi RPCs.

The gate reads the HTTP method from ContextWithHTTPMethod and assumes POST when none is stored, which matches gRPC, gRPC-Web, and streaming requests. A Connect GET without a stored method fails DPoP method binding.

ConnectAuthNInterceptor skips authentication for requests this gate already authenticated, so each DPoP proof is validated once and does not trip replay detection. The interceptor still sets DPoP-Nonce response headers, which a gate cannot write.

func (Authentication) ConnectAuthZInterceptor added in v0.17.0

func (a Authentication) ConnectAuthZInterceptor() connect.Interceptor

ConnectAuthZInterceptor authorizes Connect requests using the token and configured claims already stored in the request context. It covers unary, streaming, and bidi RPCs; streaming RPCs are authorized once at stream establishment.

Streaming RPCs have no request message available at establishment, so resource-dimension resolvers cannot run and authorization is procedure-level only. Dimension-scoped v2 policy therefore fails closed for streams unless the policy explicitly allows wildcard dimensions, mirroring the raw-HTTP MuxHandler.

func (Authentication) ConnectAuthZInterceptorV2 added in v0.30.0

func (a Authentication) ConnectAuthZInterceptorV2() connectv2.ServerInterceptor

ConnectAuthZInterceptorV2 authorizes connect-go v2 calls with the same policy as ConnectAuthZInterceptor. Unary procedures with a registered resource resolver receive the request message first so the resolver can read it, and the handler receives that message unchanged. Every other call is authorized from the procedure alone, before the body is read.

func (Authentication) IPCServerInterceptorV2 added in v0.30.0

func (a Authentication) IPCServerInterceptorV2() connectv2.ServerInterceptor

IPCServerInterceptorV2 is IPCUnaryServerInterceptor for connect-go v2 calls on the in-process server.

func (Authentication) IPCUnaryServerInterceptor added in v0.5.0

func (a Authentication) IPCUnaryServerInterceptor() connect.UnaryInterceptorFunc

IPCUnaryServerInterceptor is a grpc interceptor that: 1. translates known IPC Connect request headers back to incoming metadata 2. reauthorizes routes that are configured for IPC reauth 3. rehydrates auth context from propagated incoming metadata without revalidating it

func (Authentication) MuxHandler

func (a Authentication) MuxHandler(handler http.Handler) http.Handler

verifyTokenHandler is a http handler that verifies the token

type AuthenticatorOption added in v0.18.0

type AuthenticatorOption func(*Authentication)

AuthenticatorOption is a functional option for configuring Authentication.

func WithAuthzResolverRegistry added in v0.18.0

func WithAuthzResolverRegistry(registry *internalauthz.ResolverRegistry) AuthenticatorOption

WithAuthzResolverRegistry sets the authorization resolver registry. When set, the interceptors will call resolvers to extract authorization dimensions.

type Config

type Config struct {
	Enabled      bool     `mapstructure:"enabled" json:"enabled" default:"true"`
	PublicRoutes []string `mapstructure:"-" json:"-"`
	// Used for re-authentication of IPC connections
	IPCReauthRoutes []string `mapstructure:"-" json:"-"`
	AuthNConfig     `mapstructure:",squash"`

	// Programmatic role provider overrides (not loaded from config)
	RoleProvider          platformauthz.RoleProvider                   `mapstructure:"-" json:"-"`
	RoleProviderFactories map[string]platformauthz.RoleProviderFactory `mapstructure:"-" json:"-"`
}

AuthConfig pulls AuthN and AuthZ together

type DPoPConfig added in v0.18.0

type DPoPConfig struct {
	// Enforce requires access tokens to be DPoP-bound. Replaces the deprecated
	// top-level server.auth.enforceDPoP field.
	Enforce         bool          `mapstructure:"enforce" json:"enforce" default:"false"`
	RequireNonce    bool          `mapstructure:"require_nonce" json:"require_nonce" default:"false"`
	NonceExpiration time.Duration `mapstructure:"nonce_expiration" json:"nonce_expiration" default:"5m"`
	// StrictHTU requires the htu claim in DPoP JWTs to include the origin
	// (scheme + host). When false (default), a path-only htu is accepted as
	// long as the path matches, easing SDK skew during rollout.
	StrictHTU bool `mapstructure:"strict_htu" json:"strict_htu" default:"false"`
}

func (DPoPConfig) Validate added in v0.18.0

func (c DPoPConfig) Validate() error

type DPoPNonceError added in v0.18.0

type DPoPNonceError struct {
	Message string
}

DPoPNonceError indicates a missing or expired nonce that the client should retry with a fresh one.

func (*DPoPNonceError) Error added in v0.18.0

func (e *DPoPNonceError) Error() string

type DPoPNonceMalformedError added in v0.18.0

type DPoPNonceMalformedError struct {
	Message string
}

DPoPNonceMalformedError indicates the nonce claim was present but had an invalid type or format. Unlike DPoPNonceError, this is not retryable — the client sent a malformed proof.

func (*DPoPNonceMalformedError) Error added in v0.18.0

func (e *DPoPNonceMalformedError) Error() string

type DPoPProofError added in v0.19.0

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

DPoPProofError marks a non-retryable DPoP proof rejection (tampered htu/htm, bad ath, replayed jti, malformed nonce). Handlers translate it into a WWW-Authenticate: DPoP error="invalid_dpop_proof" challenge per RFC 9449 §7.1.

func (*DPoPProofError) Error added in v0.19.0

func (e *DPoPProofError) Error() string

func (*DPoPProofError) Unwrap added in v0.19.0

func (e *DPoPProofError) Unwrap() error

type OIDCConfiguration

type OIDCConfiguration struct {
	Issuer                           string   `json:"issuer"`
	AuthorizationEndpoint            string   `json:"authorization_endpoint"`
	TokenEndpoint                    string   `json:"token_endpoint"`
	UserinfoEndpoint                 string   `json:"userinfo_endpoint"`
	JwksURI                          string   `json:"jwks_uri"`
	ResponseTypesSupported           []string `json:"response_types_supported"`
	SubjectTypesSupported            []string `json:"subject_types_supported"`
	IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
	RequireRequestURIRegistration    bool     `json:"require_request_uri_registration"`
}

OIDCConfiguration holds the openid configuration for the issuer. Currently only required fields are included (https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata)

func DiscoverOIDCConfiguration

func DiscoverOIDCConfiguration(ctx context.Context, issuer string, logger *logger.Logger) (*OIDCConfiguration, error)

DiscoverOPENIDConfiguration discovers the openid configuration for the issuer provided

type TokenVerifier added in v0.15.0

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

TokenVerifier validates access tokens against the platform's configured IdP.

func NewTokenVerifier added in v0.15.0

func NewTokenVerifier(ctx context.Context, cfg AuthNConfig, log *logger.Logger) (*TokenVerifier, error)

NewTokenVerifier creates a reusable verifier backed by the IdP JWKS endpoint.

func (*TokenVerifier) VerifyAccessToken added in v0.15.0

func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, tokenRaw string) (jwt.Token, error)

VerifyAccessToken validates the provided raw JWT and returns the parsed token on success.

Directories

Path Synopsis
Package authz provides the authorization interface and types for the OpenTDF platform.
Package authz provides the authorization interface and types for the OpenTDF platform.
casbin
Package casbin registers the Casbin authorization engine and dispatches to the configured versioned implementation.
Package casbin registers the Casbin authorization engine and dispatches to the configured versioned implementation.
casbin/v1
Package v1 provides the legacy path-based Casbin authorization implementation.
Package v1 provides the legacy path-based Casbin authorization implementation.
casbin/v2
Package v2 provides the resource/dimension-based Casbin authorization implementation.
Package v2 provides the resource/dimension-based Casbin authorization implementation.

Jump to

Keyboard shortcuts

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