storage

package
v0.7.2 Latest Latest
Warning

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

Go to latest
Published: Jan 19, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package storage provides storage interfaces and implementations for the OAuth authorization server. This package implements fosite's storage interfaces to persist OAuth tokens and related data.

Fosite Storage Architecture

Fosite uses Interface Segregation Principle to split storage into focused interfaces. Each OAuth feature (authorization codes, access tokens, refresh tokens, PKCE) has its own storage interface. This design allows:

  • Feature composition: Enable only the OAuth features you need
  • Testing isolation: Mock only the interfaces relevant to your test
  • Clear contracts: Each interface documents exactly what it requires

The main fosite storage interfaces we implement:

  • oauth2.AuthorizeCodeStorage: Authorization code grant (RFC 6749 Section 4.1)
  • oauth2.AccessTokenStorage: Access token persistence
  • oauth2.RefreshTokenStorage: Refresh token persistence
  • oauth2.TokenRevocationStorage: Token revocation (RFC 7009)
  • pkce.PKCERequestStorage: PKCE challenge storage (RFC 7636)
  • fosite.ClientManager: OAuth client lookup and JWT assertion tracking

fosite.Requester: The Central Type

fosite.Requester is the core abstraction representing an OAuth request context. All token storage methods store the full Requester, not just the token value, because:

  • Context preservation: Token validation requires the original request context (client, scopes, audience, session) to make authorization decisions
  • Introspection support: RFC 7662 token introspection returns metadata about the token (client_id, scope, exp, etc.) which lives in the Requester
  • Revocation support: Revoking by request ID requires finding all tokens from the same authorization grant, which means storing the grant context
  • Session data: The embedded Session contains expiration times per token type, subject, username, and custom claims needed for token generation

A Requester contains:

  • ID: Unique identifier for the authorization grant (request ID)
  • Client: The OAuth client that initiated the request
  • RequestedScopes/GrantedScopes: What scopes were requested and granted
  • RequestedAudience/GrantedAudience: What audiences were requested and granted
  • Session: Token expiration times, subject, and custom data
  • Form: Original request form values (sanitized for storage)

Signature vs Request ID: Two Lookup Keys

Storage methods use two different keys for different operations:

Signature (token-specific operations):

  • Used by: CreateAccessTokenSession, GetAccessTokenSession, DeleteAccessTokenSession
  • What it is: A cryptographic signature or hash derived from the token value
  • Purpose: Look up a specific token when you have the token value
  • Example flow: Client sends access token -> derive signature -> look up session

Request ID (grant-wide operations):

  • Used by: RevokeAccessToken, RevokeRefreshToken, RotateRefreshToken
  • What it is: The unique identifier of the original authorization grant
  • Purpose: Find ALL tokens issued from the same authorization grant
  • Example flow: Revoke refresh token -> find request ID -> delete all related tokens

Why two keys? RFC 7009 requires that revoking a refresh token SHOULD also revoke associated access tokens. This requires finding tokens by their common origin (request ID) rather than by their individual values. The request ID ties together:

  • The authorization code (one-time use)
  • All access tokens issued from that grant
  • All refresh tokens issued from that grant

Our implementation stores tokens keyed by signature for O(1) token lookup, but revocation requires O(n) scan by request ID. Production implementations often maintain a reverse index (request_id -> signatures) for efficient revocation.

fosite.Session: Token Metadata Container

fosite.Session is an interface for storing session data between OAuth requests. Key design points:

Why GetExpiresAt lives on Session:

  • Different token types have different lifetimes (access: hours, refresh: days)
  • Expiration is metadata ABOUT the token, not the token itself
  • Session is the natural place for token metadata
  • Usage: session.GetExpiresAt(fosite.AccessToken) vs session.GetExpiresAt(fosite.RefreshToken)

Session vs Requester:

  • Session: Token-specific metadata (expiration, subject, username, claims)
  • Requester: Full request context including Session, Client, scopes, etc.
  • Session is embedded in Requester: requester.GetSession() returns the Session

Our session.Session type extends fosite's oauth2.JWTSession to add:

  • UpstreamSessionID: Links to tokens from our upstream IDP
  • JWT claims: Custom claims like "tsid" for token session lookup

fosite.Client vs fosite.Requester

Client and Requester serve different roles in the OAuth lifecycle:

fosite.Client represents the registered OAuth application:

  • Static data: client_id, client_secret, redirect_uris, allowed scopes/grants
  • Loaded from ClientRegistry (our extension) or fosite.ClientManager
  • Used to validate incoming requests against client configuration

fosite.Requester represents a specific authorization request:

  • Dynamic data: specific scopes requested/granted, session, form values
  • Created during authorization, stored with tokens
  • Contains a reference to Client via GetClient()

The relationship:

Client (static config) <--- Requester (instance) ---> Session (token metadata)
      |                           |                         |
   "What can this app do?"   "What did this request grant?"   "When does it expire?"

When to use each:

  • GetClient: Validate client_id/secret, check allowed scopes/redirects
  • Requester: Issue tokens, check what was actually granted, introspect tokens

Get Methods Accept Session Parameter

Methods like GetAccessTokenSession(ctx, signature, session) accept a Session parameter. This session is a "prototype" that may be used for deserialization:

  • Some storage backends serialize the full Requester (including Session)
  • On retrieval, they need a session instance to deserialize into
  • The prototype provides the concrete type for JSON/gob deserialization
  • If your storage keeps Requesters in memory, this parameter may be unused

Our in-memory implementation ignores this parameter since we store live Requester objects. Persistent backends (SQL, Redis) would use it for deserialization.

ToolHive Extensions

Beyond fosite's interfaces, we add ToolHive-specific storage:

  • UpstreamTokenStorage: Store tokens from upstream IDPs for proxy token swap
  • PendingAuthorizationStorage: Track in-flight authorizations during IDP redirect
  • ClientRegistry: Dynamic client registration (RFC 7591) via RegisterClient

These integrate with fosite's token storage to provide end-to-end OAuth proxy functionality: store upstream tokens, link them to issued tokens via session IDs, and enable transparent token swap for backend requests.

Implementation Notes

Thread safety: MemoryStorage uses sync.RWMutex for all map access. Persistent backends should use appropriate transaction isolation.

Expiration: We use timedEntry wrapper to track creation and expiration times. A background goroutine periodically cleans expired entries. Production backends might use database TTL features or scheduled jobs.

Defensive copies: Store and retrieve methods make deep copies to prevent aliasing issues where callers might modify returned data.

Error mapping: Storage errors are wrapped with both our sentinel errors (ErrNotFound, ErrExpired) and fosite errors (fosite.ErrNotFound) for compatibility with fosite's error handling.

References

Package storage provides storage interfaces and implementations for the OAuth authorization server.

Index

Constants

View Source
const (
	// TypeMemory uses in-memory storage (default).
	TypeMemory Type = "memory"

	// DefaultCleanupInterval is how often the background cleanup runs.
	DefaultCleanupInterval = 5 * time.Minute

	// DefaultAccessTokenTTL is the default TTL for access tokens when not extractable from session.
	DefaultAccessTokenTTL = 1 * time.Hour

	// DefaultRefreshTokenTTL is the default TTL for refresh tokens when not extractable from session.
	DefaultRefreshTokenTTL = 30 * 24 * time.Hour // 30 days

	// DefaultAuthCodeTTL is the default TTL for authorization codes (RFC 6749 recommendation).
	DefaultAuthCodeTTL = 10 * time.Minute

	// DefaultInvalidatedCodeTTL is how long invalidated codes are kept for replay detection.
	DefaultInvalidatedCodeTTL = 30 * time.Minute

	// DefaultPKCETTL is the default TTL for PKCE requests (same as auth codes).
	DefaultPKCETTL = 10 * time.Minute
)
View Source
const DefaultPendingAuthorizationTTL = 10 * time.Minute

DefaultPendingAuthorizationTTL is the default TTL for pending authorization requests.

Variables

View Source
var (
	// ErrNotFound is returned when an item is not found in storage.
	ErrNotFound = errors.New("storage: item not found")

	// ErrExpired is returned when an item exists but has expired.
	ErrExpired = errors.New("storage: item expired")

	// ErrAlreadyExists is returned when attempting to create an item that already exists.
	ErrAlreadyExists = errors.New("storage: item already exists")

	// ErrInvalidBinding is returned when token binding validation fails
	// (e.g., subject or client ID mismatch).
	ErrInvalidBinding = errors.New("storage: token binding validation failed")
)

Sentinel errors for storage operations. Use errors.Is() to check for these error types.

Functions

This section is empty.

Types

type ClientRegistry

type ClientRegistry interface {
	// ClientManager provides client lookup (GetClient)
	fosite.ClientManager

	// RegisterClient registers a new OAuth client.
	// This supports both static configuration and dynamic client registration (RFC 7591).
	// Returns ErrAlreadyExists if a client with the same ID already exists.
	RegisterClient(ctx context.Context, client fosite.Client) error
}

ClientRegistry provides client registration and lookup operations. It embeds fosite.ClientManager for client lookup (GetClient) and adds RegisterClient for dynamic client registration (RFC 7591).

type Config

type Config struct {
	// Type specifies the storage backend type. Defaults to memory.
	Type Type
}

Config configures the storage backend.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns sensible defaults.

type MemoryStorage added in v0.7.2

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

MemoryStorage implements the Storage interface with in-memory maps. This implementation is thread-safe and suitable for development and testing. For production use, consider implementing a persistent storage backend.

Fosite Storage Design

Token maps store fosite.Requester (not just token strings) because fosite needs the full authorization context for validation and introspection. The Requester contains the Client, granted scopes, Session (with expiration times), and more.

Maps are keyed by "signature" (cryptographic token identifier) for O(1) token lookup. Revocation by "request ID" requires O(n) scan; production implementations should maintain a reverse index for efficiency.

func NewMemoryStorage added in v0.7.2

func NewMemoryStorage(opts ...MemoryStorageOption) *MemoryStorage

NewMemoryStorage creates a new MemoryStorage instance with initialized maps and starts the background cleanup goroutine.

func (*MemoryStorage) ClientAssertionJWTValid added in v0.7.2

func (s *MemoryStorage) ClientAssertionJWTValid(_ context.Context, jti string) error

ClientAssertionJWTValid returns an error if the JTI is known or the DB check failed, and nil if the JTI is not known (meaning it can be used).

func (*MemoryStorage) Close added in v0.7.2

func (s *MemoryStorage) Close() error

Close stops the background cleanup goroutine and waits for it to finish. This should be called when the storage is no longer needed.

func (*MemoryStorage) CreateAccessTokenSession added in v0.7.2

func (s *MemoryStorage) CreateAccessTokenSession(_ context.Context, signature string, request fosite.Requester) error

CreateAccessTokenSession stores the access token session.

func (*MemoryStorage) CreateAuthorizeCodeSession added in v0.7.2

func (s *MemoryStorage) CreateAuthorizeCodeSession(_ context.Context, code string, request fosite.Requester) error

CreateAuthorizeCodeSession stores the authorization request for a given authorization code.

func (*MemoryStorage) CreatePKCERequestSession added in v0.7.2

func (s *MemoryStorage) CreatePKCERequestSession(_ context.Context, signature string, request fosite.Requester) error

CreatePKCERequestSession stores the PKCE request session.

func (*MemoryStorage) CreateRefreshTokenSession added in v0.7.2

func (s *MemoryStorage) CreateRefreshTokenSession(_ context.Context, signature string, _ string, request fosite.Requester) error

CreateRefreshTokenSession stores the refresh token session. The accessSignature parameter is used to link the refresh token to its access token. TODO: Store the accessSignature in a refreshToAccess map to enable direct lookup during token rotation instead of O(n) scan by request ID in RotateRefreshToken.

func (*MemoryStorage) DeleteAccessTokenSession added in v0.7.2

func (s *MemoryStorage) DeleteAccessTokenSession(_ context.Context, signature string) error

DeleteAccessTokenSession removes the access token session.

func (*MemoryStorage) DeletePKCERequestSession added in v0.7.2

func (s *MemoryStorage) DeletePKCERequestSession(_ context.Context, signature string) error

DeletePKCERequestSession removes the PKCE request session.

func (*MemoryStorage) DeletePendingAuthorization added in v0.7.2

func (s *MemoryStorage) DeletePendingAuthorization(_ context.Context, state string) error

DeletePendingAuthorization removes a pending authorization.

func (*MemoryStorage) DeleteRefreshTokenSession added in v0.7.2

func (s *MemoryStorage) DeleteRefreshTokenSession(_ context.Context, signature string) error

DeleteRefreshTokenSession removes the refresh token session.

func (*MemoryStorage) DeleteUpstreamTokens added in v0.7.2

func (s *MemoryStorage) DeleteUpstreamTokens(_ context.Context, sessionID string) error

DeleteUpstreamTokens removes the upstream IDP tokens for a session.

func (*MemoryStorage) GetAccessTokenSession added in v0.7.2

func (s *MemoryStorage) GetAccessTokenSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error)

GetAccessTokenSession retrieves the access token session by its signature.

The session parameter is a prototype for deserialization in persistent backends. Our in-memory implementation ignores it since we store live Requester objects. Persistent backends (SQL, Redis) use it to know what concrete type to deserialize into.

func (*MemoryStorage) GetAuthorizeCodeSession added in v0.7.2

func (s *MemoryStorage) GetAuthorizeCodeSession(_ context.Context, code string, _ fosite.Session) (fosite.Requester, error)

GetAuthorizeCodeSession retrieves the authorization request for a given code. If the authorization code has been invalidated, it returns ErrInvalidatedAuthorizeCode along with the request (as required by fosite).

func (*MemoryStorage) GetClient added in v0.7.2

func (s *MemoryStorage) GetClient(_ context.Context, id string) (fosite.Client, error)

GetClient loads the client by its ID or returns an error if the client does not exist.

func (*MemoryStorage) GetPKCERequestSession added in v0.7.2

func (s *MemoryStorage) GetPKCERequestSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error)

GetPKCERequestSession retrieves the PKCE request session by its signature.

func (*MemoryStorage) GetRefreshTokenSession added in v0.7.2

func (s *MemoryStorage) GetRefreshTokenSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error)

GetRefreshTokenSession retrieves the refresh token session by its signature.

func (*MemoryStorage) GetUpstreamTokens added in v0.7.2

func (s *MemoryStorage) GetUpstreamTokens(_ context.Context, sessionID string) (*UpstreamTokens, error)

GetUpstreamTokens retrieves the upstream IDP tokens for a session. Returns a defensive copy to prevent aliasing issues.

func (*MemoryStorage) InvalidateAuthorizeCodeSession added in v0.7.2

func (s *MemoryStorage) InvalidateAuthorizeCodeSession(_ context.Context, code string) error

InvalidateAuthorizeCodeSession marks an authorization code as used/invalid. Subsequent calls to GetAuthorizeCodeSession will return ErrInvalidatedAuthorizeCode.

func (*MemoryStorage) LoadPendingAuthorization added in v0.7.2

func (s *MemoryStorage) LoadPendingAuthorization(_ context.Context, state string) (*PendingAuthorization, error)

LoadPendingAuthorization retrieves a pending authorization by internal state. Returns a defensive copy to prevent aliasing issues.

func (*MemoryStorage) RegisterClient added in v0.7.2

func (s *MemoryStorage) RegisterClient(_ context.Context, client fosite.Client) error

RegisterClient adds or updates a client in the storage. This is useful for setting up test clients.

func (*MemoryStorage) RevokeAccessToken added in v0.7.2

func (s *MemoryStorage) RevokeAccessToken(_ context.Context, requestID string) error

RevokeAccessToken marks an access token as revoked. This method implements the oauth2.TokenRevocationStorage interface.

Note: This takes requestID, not signature. Per RFC 7009, revoking by request ID enables revoking ALL tokens from the same authorization grant. This is why we store the full Requester (with its ID) rather than just token values.

The O(n) scan by request ID is acceptable for in-memory storage. Production implementations should maintain a reverse index (request_id -> signatures).

func (*MemoryStorage) RevokeRefreshToken added in v0.7.2

func (s *MemoryStorage) RevokeRefreshToken(_ context.Context, requestID string) error

RevokeRefreshToken marks a refresh token as revoked. This method implements the oauth2.TokenRevocationStorage interface.

Like RevokeAccessToken, this takes requestID to find all refresh tokens from the same authorization grant. Per RFC 7009 Section 2.1, implementations SHOULD also revoke associated access tokens, which RotateRefreshToken handles.

func (*MemoryStorage) RevokeRefreshTokenMaybeGracePeriod added in v0.7.2

func (s *MemoryStorage) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestID string, _ string) error

RevokeRefreshTokenMaybeGracePeriod marks a refresh token as revoked, optionally allowing a grace period during which the old token is still valid. For this implementation, we don't support grace periods and revoke immediately.

func (*MemoryStorage) RotateRefreshToken added in v0.7.2

func (s *MemoryStorage) RotateRefreshToken(_ context.Context, requestID string, refreshTokenSignature string) error

RotateRefreshToken invalidates a refresh token and all its related token data. This is called during token refresh to implement refresh token rotation.

func (*MemoryStorage) SetClientAssertionJWT added in v0.7.2

func (s *MemoryStorage) SetClientAssertionJWT(_ context.Context, jti string, exp time.Time) error

SetClientAssertionJWT marks a JTI as known for the given expiry time. Before inserting the new JTI, it will clean up any existing JTIs that have expired.

func (*MemoryStorage) Stats added in v0.7.2

func (s *MemoryStorage) Stats() Stats

Stats returns current statistics about storage contents. This is useful for testing and monitoring.

func (*MemoryStorage) StorePendingAuthorization added in v0.7.2

func (s *MemoryStorage) StorePendingAuthorization(_ context.Context, state string, pending *PendingAuthorization) error

StorePendingAuthorization stores a pending authorization request. The pending authorization is keyed by the internal state used to correlate the upstream IDP callback.

func (*MemoryStorage) StoreUpstreamTokens added in v0.7.2

func (s *MemoryStorage) StoreUpstreamTokens(_ context.Context, sessionID string, tokens *UpstreamTokens) error

StoreUpstreamTokens stores the upstream IDP tokens for a session. A defensive copy is made to prevent aliasing issues.

type MemoryStorageOption added in v0.7.2

type MemoryStorageOption func(*MemoryStorage)

MemoryStorageOption configures a MemoryStorage instance.

func WithCleanupInterval added in v0.7.2

func WithCleanupInterval(interval time.Duration) MemoryStorageOption

WithCleanupInterval sets a custom cleanup interval.

type PendingAuthorization

type PendingAuthorization struct {
	// ClientID is the ID of the OAuth client making the authorization request.
	ClientID string

	// RedirectURI is the client's callback URL where we'll redirect after authentication.
	RedirectURI string

	// State is the client's original state parameter for CSRF protection.
	State string

	// PKCEChallenge is the client's PKCE code challenge.
	PKCEChallenge string

	// PKCEMethod is the PKCE challenge method (must be "S256").
	PKCEMethod string

	// Scopes are the OAuth scopes requested by the client.
	Scopes []string

	// InternalState is our randomly generated state for correlating upstream callback.
	InternalState string

	// UpstreamPKCEVerifier is the PKCE code_verifier for the upstream IDP authorization.
	// This is generated when redirecting to the upstream IDP and used when exchanging
	// the authorization code. See RFC 7636.
	UpstreamPKCEVerifier string

	// UpstreamNonce is the OIDC nonce parameter sent to the upstream IDP for ID Token
	// replay protection. This is validated against the nonce claim in the returned
	// ID Token. See OIDC Core Section 3.1.2.1.
	UpstreamNonce string

	// CreatedAt is when the pending authorization was created.
	CreatedAt time.Time
}

PendingAuthorization tracks a client's authorization request while they authenticate with the upstream IDP.

type PendingAuthorizationStorage

type PendingAuthorizationStorage interface {
	// StorePendingAuthorization stores a pending authorization request.
	// The state is used to correlate the upstream IDP callback.
	StorePendingAuthorization(ctx context.Context, state string, pending *PendingAuthorization) error

	// LoadPendingAuthorization retrieves a pending authorization by internal state.
	// Returns ErrNotFound if the state does not exist.
	// Returns ErrExpired if the pending authorization has expired.
	LoadPendingAuthorization(ctx context.Context, state string) (*PendingAuthorization, error)

	// DeletePendingAuthorization removes a pending authorization.
	// Returns ErrNotFound if the state does not exist.
	DeletePendingAuthorization(ctx context.Context, state string) error
}

PendingAuthorizationStorage provides storage operations for pending authorization requests. These track the state of in-flight authorization requests while users authenticate with the upstream IDP, correlating the upstream callback with the original client request.

type RunConfig

type RunConfig struct {
	// Type specifies the storage backend type. Defaults to "memory".
	Type string `json:"type,omitempty" yaml:"type,omitempty"`
}

RunConfig is the serializable storage configuration for RunConfig. This is used when the config needs to be passed across process boundaries (e.g., in Kubernetes operator).

type Stats added in v0.7.2

type Stats struct {
	Clients               int
	AuthCodes             int
	AccessTokens          int
	RefreshTokens         int
	PKCERequests          int
	UpstreamTokens        int
	PendingAuthorizations int
	InvalidatedCodes      int
	ClientAssertionJWTs   int
}

Stats contains statistics about the storage contents.

type Storage

type Storage interface {
	// Embed segregated interfaces for IDP tokens, pending authorizations, and client registry.
	UpstreamTokenStorage
	PendingAuthorizationStorage
	ClientRegistry

	// AuthorizeCodeStorage provides authorization code storage for the Authorization Code
	// Grant (RFC 6749 Section 4.1). Authorization codes are one-time-use and short-lived.
	// CreateAuthorizeCodeSession stores by code, GetAuthorizeCodeSession retrieves by code,
	// InvalidateAuthorizeCodeSession marks as used (subsequent Gets return ErrInvalidatedAuthorizeCode).
	oauth2.AuthorizeCodeStorage

	// AccessTokenStorage provides access token session storage. Methods use "signature"
	// (derived from token value) as the key for O(1) lookup when validating tokens.
	// The stored fosite.Requester contains the full authorization context including
	// the Session with expiration times via session.GetExpiresAt(fosite.AccessToken).
	oauth2.AccessTokenStorage

	// RefreshTokenStorage provides refresh token session storage. CreateRefreshTokenSession
	// accepts an accessSignature to link refresh tokens to their access tokens for rotation.
	// RotateRefreshToken uses requestID to invalidate both the refresh token and all
	// related access tokens from the same authorization grant.
	oauth2.RefreshTokenStorage

	// TokenRevocationStorage provides token revocation per RFC 7009. RevokeAccessToken
	// and RevokeRefreshToken take requestID (not signature) because RFC 7009 requires
	// revoking a refresh token SHOULD also invalidate associated access tokens, which
	// requires finding all tokens by their common grant identifier.
	oauth2.TokenRevocationStorage

	// PKCERequestStorage provides PKCE challenge/verifier storage (RFC 7636).
	// Stores the code_challenge during authorization, validates code_verifier during
	// token exchange. Keyed by the same code/signature as the authorization code.
	pkce.PKCERequestStorage

	// Close releases any resources held by the storage implementation.
	// This should be called when the storage is no longer needed.
	Close() error
}

Storage combines fosite storage interfaces with ToolHive-specific storage for upstream IDP tokens, pending authorization requests, and client registration. The auth server requires a Storage implementation; use NewMemoryStorage() for single-instance deployments or NewRedisStorage() for distributed deployments.

Fosite Interface Segregation

Fosite splits storage into separate interfaces (AuthorizeCodeStorage, AccessTokenStorage, RefreshTokenStorage, PKCERequestStorage) following the Interface Segregation Principle. This enables:

  • Feature composition: Only enable OAuth features you need
  • Testing isolation: Mock specific interfaces for focused tests
  • Clear contracts: Each interface documents its requirements

Key Design Patterns

All token storage methods store fosite.Requester (not just token values) because token validation requires the full authorization context (client, scopes, session).

Methods use two key types:

  • Signature: Cryptographic token identifier for token-specific operations
  • Request ID: Grant identifier for finding all tokens from one authorization

See doc.go for comprehensive documentation of fosite's storage design.

type Type

type Type string

Type defines the type of storage backend.

type UpstreamTokenStorage

type UpstreamTokenStorage interface {
	// StoreUpstreamTokens stores the upstream IDP tokens for a session.
	StoreUpstreamTokens(ctx context.Context, sessionID string, tokens *UpstreamTokens) error

	// GetUpstreamTokens retrieves the upstream IDP tokens for a session.
	// Returns ErrNotFound if the session does not exist.
	// Returns ErrExpired if the tokens have expired.
	// Returns ErrInvalidBinding if binding validation fails.
	GetUpstreamTokens(ctx context.Context, sessionID string) (*UpstreamTokens, error)

	// DeleteUpstreamTokens removes the upstream IDP tokens for a session.
	// Returns ErrNotFound if the session does not exist.
	DeleteUpstreamTokens(ctx context.Context, sessionID string) error
}

UpstreamTokenStorage provides storage for tokens obtained from upstream identity providers. The auth server exposes this interface via Server.UpstreamTokenStorage() for use by middleware that needs to retrieve upstream tokens (e.g., token swap middleware that replaces JWT auth with upstream IDP tokens for backend requests).

type UpstreamTokens

type UpstreamTokens struct {
	// AccessToken is the access token from the upstream IDP.
	AccessToken string

	// RefreshToken is the refresh token from the upstream IDP (if provided).
	RefreshToken string

	// IDToken is the ID token from the upstream IDP (for OIDC).
	IDToken string

	// ExpiresAt is when the access token expires.
	ExpiresAt time.Time

	// Subject is the user identifier from the IDP.
	// This binding field is validated on lookup to prevent cross-session attacks
	// by ensuring the JWT "sub" claim matches this value.
	Subject string

	// ClientID is the OAuth client that initiated the authorization.
	// This binding field is validated on lookup to prevent cross-session attacks
	// by ensuring the JWT "client_id" or "azp" claim matches this value.
	ClientID string
}

UpstreamTokens represents the tokens obtained from an upstream Identity Provider, stored with binding fields for security validation.

func (*UpstreamTokens) IsExpired

func (t *UpstreamTokens) IsExpired(now time.Time) bool

IsExpired returns true if the access token has expired. The now parameter allows for deterministic testing.

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

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