oauth

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 26, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

oauth

Standalone Go OAuth 2.0 authorization server and client library.

Implements: RFC 6749 (Authorization Framework), 6750 (Bearer Token Usage), 7636 (PKCE), 7009 (Token Revocation), 7662 (Token Introspection), 9068 (JWT Profile), 8414 (AS Metadata), 7591/7592 (Dynamic Client Registration), 8628 (Device Authorization), 9126 (Pushed Authorization Requests), 9207 (Issuer Identification), 9449 (DPoP), 9700 (Security BCP), and the Client ID Metadata Document Internet-Draft (client identifiers backed by HTTPS metadata documents). The client-credentials grant (RFC 6749 §4.4) supports only confidential clients authenticating with private_key_jwt (RFC 7523) assertions; the server issues no shared secrets. It backs the official MCP OAuth Client Credentials extension (io.modelcontextprotocol/oauth-client-credentials).

Not implemented: RFC 8693 (Token Exchange). Mint audience-scoped tokens at the authorization endpoint via the resource parameter (RFC 8707) instead.

Zero external dependencies — stdlib only.

Quick Start

package main

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

	"github.com/caic-xyz/oauth"
)

type stubSessionManager struct{}
func (stubSessionManager) CurrentUser(ctx context.Context) (oauth.User, bool) {
	return oauth.User{ID: "usr_1", Username: "alice"}, true
}
func (stubSessionManager) AttachUser(ctx context.Context, u oauth.User) context.Context { return ctx }
func (stubSessionManager) FindUser(id string) (oauth.User, bool) {
	return oauth.User{ID: id, Username: id}, true
}
func (stubSessionManager) EndSession(ctx context.Context, r *http.Request, u oauth.User) (string, error) {
	return "/bye", nil
}

type stubUI struct{}
func (stubUI) LoginStartURL(r *http.Request) string      { return "/login" }
func (stubUI) ProviderLabel(provider string) string       { return provider }
func (stubUI) RenderOAuthConsent(w http.ResponseWriter, d *oauth.ConsentPageData) error {
	fmt.Fprintf(w, "Hi %s, grant %s access to %s?", d.Username, d.ClientName, d.ScopeItems)
	return nil
}

func main() {
	keyPEM, _ := os.ReadFile("key.pem")
	srv, err := oauth.NewServer(oauth.ServerConfig{
		KeyPEM:            keyPEM,
		KeyID:             "key-1",
		RefreshTokenStorePath: "oauth_store.json",
		SupportedScopes:   []string{"api:read", "api:write"},
		DefaultScopes:     []string{"api:read"},
		ScopeLabels:       map[string]string{"api:read": "Read access", "api:write": "Write access"},
		BaseURL:           func(r *http.Request) string { return "https://example.com" },
		Session:           stubSessionManager{},
		UI:                stubUI{},
	})
	if err != nil {
		log.Fatal(err)
	}

	// Register a client
	client, err := srv.RegisterClient("My App", []string{"https://example.com/callback"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Client ID: %s\n", client.ID)

	http.ListenAndServe(":8080", srv.Routes())
}

Interfaces

The package delegates product-specific concerns to two interfaces:

  • SessionManager — user login state, session attachment, user lookup, and end-session redirect
  • AuthorizationUI — login redirect URL, consent page rendering, OIDC provider labels

Optional seams: BaseURLFunc, AuditRecorder, RateLimiter.

Client Helpers

The oauth package also provides generic client helpers for Authorization Code with PKCE:

cfg := oauth.ClientConfig{ClientID: "…", ClientSecret: "…", TokenURL: "…", RedirectURI: "…"}
url := oauth.AuthorizationURL("https://example.com/oauth/authorize", cfg.ClientID, cfg.RedirectURI, []string{"api:read"}, state)
access, refresh, expiry, err := oauth.ExchangeCode(ctx, cfg, code)

License

Apache 2.0

Documentation

Overview

Package oauth is an OAuth 2.0 authorization server and client library.

This package will be split into a standalone Go module (stdlib only). No caic-internal types or backend dependencies may leak in.

Subpackages:

oauth/oauthclient - Authorization-code client helpers and provider
    (GitHub/GitLab) configurations.
oauth/oauthserver - OAuth authorization server HTTP handlers and
    token management.

This root package holds shared constants and protocol-level DTOs used by both subpackages. Server-side types live in dto_server.go; client-side types live in dto_client.go.

Implemented RFCs: 6749 (Authorization Framework), 6750 (Bearer Token Usage), 7636 (PKCE), 7009 (Token Revocation), 7662 (Token Introspection), 9068 (JWT Profile for Access Tokens), 8414 (Authorization Server Metadata), 7591 (Dynamic Client Registration), 7592 (Client Registration Management), 8628 (Device Authorization Grant), 9126 (Pushed Authorization Requests), 9207 (Issuer Identification), 9449 (DPoP), and 9700 (OAuth Security BCP). Client ID Metadata Documents are implemented from the current IETF draft.

Not implemented: 8693 (Token Exchange). Issue audience-scoped tokens at the authorization endpoint via the resource parameter (RFC 8707) instead.

Index

Constants

View Source
const (
	// GrantAuthorizationCode is the OAuth authorization-code grant type.
	GrantAuthorizationCode = "authorization_code"
	// GrantRefreshToken is the OAuth refresh-token grant type.
	GrantRefreshToken = "refresh_token"
	// GrantDeviceCode is the OAuth device authorization grant type.
	GrantDeviceCode = "urn:ietf:params:oauth:grant-type:device_code"
	// GrantClientCredentials is the OAuth client-credentials grant type for
	// machine-to-machine authorization (RFC 6749 §4.4).
	GrantClientCredentials = "client_credentials"
	// CodeChallengeS256 is the PKCE S256 code challenge method.
	CodeChallengeS256 = "S256"
	// ResponseTypeCode is the authorization-code response type.
	ResponseTypeCode = "code"
	// TokenTypeBearer is the bearer access token type.
	TokenTypeBearer = "Bearer"
	// TokenEndpointAuthNone is the public-client token endpoint auth method.
	TokenEndpointAuthNone = "none"
	// TokenEndpointAuthPrivateKeyJWT authenticates a client with an asymmetric JWT assertion.
	TokenEndpointAuthPrivateKeyJWT = "private_key_jwt" //nolint:gosec // Standard OAuth authentication method identifier, not a credential.
	// JWTAlgRS256 is the RSASSA-PKCS1-v1_5 SHA-256 JWT algorithm.
	JWTAlgRS256 = "RS256"
)

Variables

This section is empty.

Functions

func BearerChallenge

func BearerChallenge(resourceMetadataURL, scope string) string

BearerChallenge formats a WWW-Authenticate Bearer challenge.

func BearerScopeChallenge

func BearerScopeChallenge(scope string) string

BearerScopeChallenge formats a WWW-Authenticate Bearer scope challenge.

func BearerToken

func BearerToken(r *http.Request) string

BearerToken extracts an OAuth bearer token from the Authorization header.

func RefreshTokenKey

func RefreshTokenKey(token string) string

RefreshTokenKey returns the storage key for an opaque refresh token.

func SDKAPI

func SDKAPI() apispec.Config[string]

SDKAPI returns the SDK generation specification for OAuth types.

func VerifyPKCES256

func VerifyPKCES256(challenge, verifier string) bool

VerifyPKCES256 verifies a PKCE S256 code verifier against its challenge.

func WriteError

func WriteError(w http.ResponseWriter, status int, code, description string)

WriteError writes an OAuth JSON error response.

Types

type AccessTokenClaims

type AccessTokenClaims struct {
	Issuer       string             `json:"iss"`
	Subject      string             `json:"sub"`
	Audience     string             `json:"aud"`
	ClientID     string             `json:"client_id,omitempty"`
	JWTID        string             `json:"jti,omitempty"`
	Username     string             `json:"username"`
	Scope        string             `json:"scope"`
	GrantID      string             `json:"grant_id,omitempty"`
	IssuedAt     int64              `json:"iat"`
	NotBefore    int64              `json:"nbf"`
	Expiry       int64              `json:"exp"`
	Type         string             `json:"typ"`
	Confirmation *TokenConfirmation `json:"cnf,omitempty"`
}

AccessTokenClaims are JWT access-token claims.

type AuthorizationServerMetadata

type AuthorizationServerMetadata struct {
	Issuer                                        string   `json:"issuer"`
	AuthorizationEndpoint                         string   `json:"authorization_endpoint"`
	TokenEndpoint                                 string   `json:"token_endpoint"`
	IntrospectionEndpoint                         string   `json:"introspection_endpoint,omitempty"`
	JWKSURI                                       string   `json:"jwks_uri"`
	RegistrationEndpoint                          string   `json:"registration_endpoint"`
	RevocationEndpoint                            string   `json:"revocation_endpoint,omitempty"`
	EndSessionEndpoint                            string   `json:"end_session_endpoint,omitempty"`
	ResponseTypesSupported                        []string `json:"response_types_supported"`
	GrantTypesSupported                           []string `json:"grant_types_supported"`
	CodeChallengeMethodsSupported                 []string `json:"code_challenge_methods_supported"`
	TokenEndpointAuthMethodsSupported             []string `json:"token_endpoint_auth_methods_supported"`
	IntrospectionEndpointAuthMethodsSupported     []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
	RevocationEndpointAuthMethodsSupported        []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
	ScopesSupported                               []string `json:"scopes_supported,omitempty"`
	DPoPSigningAlgValuesSupported                 []string `json:"dpop_signing_alg_values_supported,omitempty"`
	AuthorizationResponseIssuerParameterSupported bool     `json:"authorization_response_iss_parameter_supported"`
	PushedAuthorizationRequestEndpoint            string   `json:"pushed_authorization_request_endpoint,omitempty"`
	RequirePushedAuthorizationRequests            bool     `json:"require_pushed_authorization_requests"`
	ClientIDMetadataDocumentSupported             bool     `json:"client_id_metadata_document_supported,omitempty"`
}

AuthorizationServerMetadata is OAuth/OIDC discovery metadata.

type BearerClaims

type BearerClaims struct {
	User         User
	Subject      string
	Username     string
	Issuer       string
	Audience     string
	Scopes       []string
	Iat          int64
	Exp          int64
	ClientID     string
	Confirmation *TokenConfirmation
}

BearerClaims holds verified bearer-token identity and authorization claims. User is the zero value for machine-to-machine tokens issued by the client-credentials grant, whose subject is the OAuth client itself.

type ClientConfig

type ClientConfig struct {
	ClientID     string
	ClientSecret string
	TokenURL     string
	RedirectURI  string
}

ClientConfig holds OAuth 2.0 token endpoint configuration for one authorization-code client.

type ClientIDMetadataDocument

type ClientIDMetadataDocument struct {
	ClientID                string   `json:"client_id"`
	ClientName              string   `json:"client_name"`
	RedirectURIs            []string `json:"redirect_uris"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method,omitempty"`
	GrantTypes              []string `json:"grant_types,omitempty"`
	ResponseTypes           []string `json:"response_types,omitempty"`
	JWKS                    *JWKSet  `json:"jwks,omitempty"`
	JWKSURI                 string   `json:"jwks_uri,omitempty"`
}

ClientIDMetadataDocument describes an OAuth client identified by its HTTPS metadata URL.

type DeviceAuthorizationRequest

type DeviceAuthorizationRequest struct {
	ClientID string `json:"client_id"`
	Scope    string `json:"scope,omitempty"`
}

DeviceAuthorizationRequest is an RFC 8628 device authorization request body.

type DeviceAuthorizationResponse

type DeviceAuthorizationResponse struct {
	DeviceCode              string `json:"device_code"`
	UserCode                string `json:"user_code"`
	VerificationURI         string `json:"verification_uri"`
	VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
	ExpiresIn               int64  `json:"expires_in"`
	Interval                int64  `json:"interval,omitempty"`
}

DeviceAuthorizationResponse is an RFC 8628 device authorization response body.

type ErrorResponse

type ErrorResponse struct {
	Error            string `json:"error"`
	ErrorDescription string `json:"error_description,omitempty"`
}

ErrorResponse is an OAuth error response body.

type IntrospectionRequest

type IntrospectionRequest struct {
	Token         string `json:"token"`
	TokenTypeHint string `json:"token_type_hint,omitempty"`
}

IntrospectionRequest is an RFC 7662 token introspection request body.

type IntrospectionResponse

type IntrospectionResponse struct {
	Active       bool               `json:"active"`
	Scope        string             `json:"scope,omitempty"`
	ClientID     string             `json:"client_id,omitempty"`
	TokenType    string             `json:"token_type,omitempty"`
	Exp          int64              `json:"exp,omitempty"`
	Sub          string             `json:"sub,omitempty"`
	Username     string             `json:"username,omitempty"`
	Iss          string             `json:"iss,omitempty"`
	Aud          string             `json:"aud,omitempty"`
	Iat          int64              `json:"iat,omitempty"`
	Confirmation *TokenConfirmation `json:"cnf,omitempty"`
}

IntrospectionResponse is an RFC 7662 token introspection response body.

type JWK

type JWK struct {
	Kty    string   `json:"kty"`
	Use    string   `json:"use,omitempty"`
	Alg    string   `json:"alg,omitempty"`
	Kid    string   `json:"kid,omitempty"`
	N      string   `json:"n,omitempty"`
	E      string   `json:"e,omitempty"`
	Crv    string   `json:"crv,omitempty"`
	X      string   `json:"x,omitempty"`
	Y      string   `json:"y,omitempty"`
	KeyOps []string `json:"key_ops,omitempty"`
}

JWK is a JSON Web Key. For server keys (RSA), only kty/n/e/kid/use/alg are populated. For client DPoP keys, crv/x/y may also be set.

func ECJWK

func ECJWK(kid string, pub *ecdsa.PublicKey) JWK

ECJWK returns an ECDSA signing key in JWK form.

crv and alg follow the key's curve (RFC 7518 §3.4, §6.2.1.1). Reporting a fixed P-256/ES256 would mislabel a P-384 or P-521 key and make every published coordinate unparseable to a conforming client.

func RSAJWK

func RSAJWK(kid string, pub *rsa.PublicKey) JWK

RSAJWK returns an RSA signing key in JWK form.

type JWKSet

type JWKSet struct {
	Keys []JWK `json:"keys"`
}

JWKSet is a JSON Web Key Set response.

type JWTHeader

type JWTHeader struct {
	Alg string `json:"alg"`
	KID string `json:"kid"`
	Typ string `json:"typ"`
}

JWTHeader is the JOSE header for a JWT access token.

type PARResponse

type PARResponse struct {
	RequestURI string `json:"request_uri"`
	ExpiresIn  int64  `json:"expires_in"`
}

PARResponse is an RFC 9126 pushed authorization request response.

type ProtectedResourceMetadata

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

ProtectedResourceMetadata is OAuth 2.0 Protected Resource Metadata.

type RegisterRequest

type RegisterRequest struct {
	ClientName              string   `json:"client_name"`
	RedirectURIs            []string `json:"redirect_uris"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method"`
	GrantTypes              []string `json:"grant_types,omitempty"`
}

RegisterRequest is a dynamic client registration request.

type RegisterResponse

type RegisterResponse struct {
	ClientID                string   `json:"client_id"`
	ClientIDIssuedAt        int64    `json:"client_id_issued_at"`
	ClientName              string   `json:"client_name,omitempty"`
	RedirectURIs            []string `json:"redirect_uris"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method"`
	GrantTypes              []string `json:"grant_types,omitempty"`
	RegistrationAccessToken string   `json:"registration_access_token,omitempty"`
	RegistrationClientURI   string   `json:"registration_client_uri,omitempty"`
}

RegisterResponse is a dynamic client registration response (RFC 7592).

type Token

type Token struct {
	AccessToken  string
	TokenType    string // e.g., "Bearer"; always set by the constructor
	RefreshToken string
	Expiry       time.Time // zero means no expiry
}

Token holds an OAuth 2.0 access token and associated metadata.

func (*Token) Expired

func (t *Token) Expired() bool

Expired reports whether t is expired, with a built-in early-expiry buffer (10s). A zero Expiry means the token never expires. Callers that need exact expiry can compare time.Now().After(t.Expiry).

type TokenConfirmation

type TokenConfirmation struct {
	JKT string `json:"jkt"`
}

TokenConfirmation holds key confirmation claims per RFC 9449 §6.1.

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int64  `json:"expires_in"`
	RefreshToken string `json:"refresh_token,omitempty"`
	Scope        string `json:"scope,omitempty"`
}

TokenResponse is an OAuth token endpoint response.

type UpdateClientRequest

type UpdateClientRequest struct {
	ClientName              *string   `json:"client_name,omitempty"`
	RedirectURIs            *[]string `json:"redirect_uris,omitempty"`
	TokenEndpointAuthMethod *string   `json:"token_endpoint_auth_method,omitempty"`
	GrantTypes              *[]string `json:"grant_types,omitempty"`
}

UpdateClientRequest is an RFC 7592 client update request body. Nil pointer fields leave the corresponding client field unchanged.

type User

type User struct {
	ID       string
	Username string
	Provider string
}

User is the package-neutral OAuth view of an authenticated resource owner.

Directories

Path Synopsis
Package oauthclient provides an OAuth 2.0 authorization-code client for third-party providers.
Package oauthclient provides an OAuth 2.0 authorization-code client for third-party providers.
Package oauthserver is a generic OAuth 2.0 authorization server.
Package oauthserver is a generic OAuth 2.0 authorization server.

Jump to

Keyboard shortcuts

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