authware

package module
v0.2.1 Latest Latest
Warning

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

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

README

go-authware

Minimal-dependency HTTP authentication library for Go servers.

Go Version CI Go Report Card Go Reference

Features

  • Five auth modes: None, Bearer, API Key, OAuth/JWT, OAuth Proxy
  • OIDC auto-discovery — works with any OpenID Connect provider
  • Minimal dependencies — stdlib + go-jsonfast for zero-alloc JSON
  • 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
  • RFC 8414 AS Metadata via OAuth proxy
  • RFC 7591 Dynamic Client Registration shim
  • Zero-alloc hot paths using unsafe.Slice for string→[]byte views
  • go-jsonfast Builder for zero-alloc JSON serialization in proxy handlers
  • Nginx auth_request compatible handler
  • Environment variable configuration via ConfigFromEnv
  • PGO-optimized with default.pgo profile

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)

OAuth Proxy (MCP 2025-11-25)

The OAuthProxy bridges MCP clients that require public-client OAuth with upstream IdPs (e.g. Azure AD, Okta) that don't natively support RFC 7591 Dynamic Client Registration.

It provides four HTTP handlers per the MCP specification (2025-11-25):

  • ASMetadataHandler — serves /.well-known/oauth-authorization-server with RFC 8414 metadata (issuer derived from request origin; upstream discovery fetched once via sync.Once)
  • AuthorizeHandler — 302-redirects to the upstream IdP's authorization endpoint, forwarding all query parameters (PKCE, state, redirect_uri, etc.)
  • RegisterHandler — minimal RFC 7591 DCR shim returning a pre-configured client_id
  • TokenHandler — proxies token exchange requests to the upstream IdP

MCP spec default endpoint paths are /authorize, /token, /register:

proxy := authware.NewOAuthProxy(&authware.Config{
    OAuthAuthorizationServers: []string{"https://login.microsoftonline.com/tenant/v2.0"},
    OAuthClientID:             "my-app-client-id",
}, slog.Default())

if proxy != nil {
    mux.HandleFunc("GET /.well-known/oauth-authorization-server", proxy.ASMetadataHandler())
    mux.HandleFunc("GET /authorize", proxy.AuthorizeHandler())
    mux.HandleFunc("POST /register", proxy.RegisterHandler())
    mux.HandleFunc("POST /token", proxy.TokenHandler())
}

All proxy JSON serialization uses go-jsonfast Builder with pooled buffers — zero encoding/json.Marshal calls on the hot path.

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

Benchmarks

Measured on Intel Xeon Gold 6426Y (32 cores):

Benchmark ns/op B/op allocs/op
Bearer (accept) 20 0 0
Bearer (reject) 11 0 0
API Key 23 0 0
API Key (Authorization header) 36 0 0
None 4 0 0
OAuth HMAC HS256 1050 24 1
Scope check 32 0 0
Secure equal 22 0 0
Proxy: Register (DCR) 4365 6480 22
Proxy: AS Metadata (cached) 3693 6676 23
Proxy: Token 96172 50914 120
Middleware (Bearer) 355 624 7

Hot paths (Bearer, API Key, None, Scope check, Secure equal) are zero-allocation. OAuth HMAC has a single allocation for the HMAC hash buffer reuse. Proxy handlers use pooled go-jsonfast builders.

📁 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
├── proxy.go            # OAuth proxy (AS metadata, DCR shim, token proxy)
├── challenge.go        # HTTP challenge/error response formatting
├── handler.go          # Nginx auth_request handler
├── env.go              # Environment variable configuration
├── doc.go              # Package documentation
├── bench_test.go       # Performance benchmarks (12 benchmarks)
├── default.pgo         # PGO profile for build optimization
├── .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
  • RFC 8414 AS Metadata via OAuth proxy
  • RFC 7591 Dynamic Client Registration shim
  • 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)

OAuth Proxy

OAuthProxy bridges MCP clients (e.g. Claude Desktop) with upstream IdPs that don't support RFC 7591 Dynamic Client Registration. It provides four handlers: ASMetadataHandler, AuthorizeHandler, RegisterHandler, and TokenHandler. All proxy JSON serialization uses go-jsonfast Builder with pooled buffers.

proxy := authware.NewOAuthProxy(cfg, slog.Default())
mux.HandleFunc("GET /.well-known/oauth-authorization-server", proxy.ASMetadataHandler())
mux.HandleFunc("GET /authorize", proxy.AuthorizeHandler())
mux.HandleFunc("POST /register", proxy.RegisterHandler())
mux.HandleFunc("POST /token", proxy.TokenHandler())

Dependencies

This library depends only on go-jsonfast (https://github.com/ubyte-source/go-jsonfast) for zero-allocation JWT claims parsing and proxy JSON serialization. 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
	OAuthClientID              string // upstream IdP client_id returned by the built-in DCR shim
	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 OAuthProxy added in v0.1.0

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

OAuthProxy provides HTTP handlers for the MCP 2025-11-25 OAuth facade. It handles AS metadata discovery, Dynamic Client Registration (DCR) shim, authorization redirect, and token endpoint proxying. This bridges MCP clients that require public-client OAuth with upstream IdPs that don't natively support RFC 7591 DCR.

func NewOAuthProxy added in v0.1.0

func NewOAuthProxy(cfg *Config, log *slog.Logger) *OAuthProxy

NewOAuthProxy creates an OAuth proxy from the given Config. Returns nil if no OAuthAuthorizationServers are configured or OAuthClientID is empty (proxy not needed).

Example
package main

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

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

func main() {
	proxy := authware.NewOAuthProxy(&authware.Config{
		OAuthAuthorizationServers: []string{"https://login.microsoftonline.com/tenant/v2.0"},
		OAuthClientID:             "my-client-id",
	}, slog.Default())

	if proxy != nil {
		mux := http.NewServeMux()
		mux.HandleFunc("/.well-known/oauth-authorization-server", proxy.ASMetadataHandler())
		mux.HandleFunc("/oauth/register", proxy.RegisterHandler())
		mux.HandleFunc("/oauth/token", proxy.TokenHandler())

		// Test the DCR shim returns the pre-configured client_id.
		r := httptest.NewRequest(http.MethodPost, "/oauth/register", http.NoBody)
		w := httptest.NewRecorder()
		proxy.RegisterHandler().ServeHTTP(w, r)
		fmt.Println(w.Code)
	}
}
Output:
201

func (*OAuthProxy) ASMetadataHandler added in v0.1.0

func (p *OAuthProxy) ASMetadataHandler() http.HandlerFunc

ASMetadataHandler returns an http.HandlerFunc that serves /.well-known/oauth-authorization-server per RFC 8414 and the MCP specification (2025-11-25). It fetches the upstream IdP's discovery document once, then serves a metadata document where the MCP server acts as the authorization server (issuer matches the request origin).

func (*OAuthProxy) AuthorizeHandler added in v0.2.0

func (p *OAuthProxy) AuthorizeHandler() http.HandlerFunc

AuthorizeHandler returns an http.HandlerFunc that redirects the user to the upstream IdP's authorization endpoint, passing all query parameters through. Per the MCP specification (2025-11-25), clients use /authorize either from AS metadata or as the default fallback. This handler 302-redirects to the real upstream IdP authorize URL.

func (*OAuthProxy) RegisterHandler added in v0.1.0

func (p *OAuthProxy) RegisterHandler() http.HandlerFunc

RegisterHandler returns an http.HandlerFunc implementing a minimal RFC 7591 Dynamic Client Registration shim. It accepts any registration request and returns the pre-configured upstream IdP client_id.

func (*OAuthProxy) TokenHandler added in v0.1.0

func (p *OAuthProxy) TokenHandler() http.HandlerFunc

TokenHandler returns an http.HandlerFunc that proxies token exchange requests to the upstream IdP's token endpoint.

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