Help us shape the future of Go! Take the 2026 Go Developer Survey

publicclient

package
v0.24.0 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package publicclient lets a public client, such as a command-line tool, sign a person in to SystemAuth and call SystemForge-powered APIs with DPoP-bound access tokens (RFC 9449).

A public client cannot keep a secret, so it signs in with the authorization code flow and PKCE on a loopback redirect (RFC 8252) and proves possession of a key instead. Nothing is registered ahead of time: Login generates a P-256 key, SystemAuth binds the tokens it issues to the key's thumbprint (cnf.jkt), and every API request a Session sends carries a fresh proof by the key, bound to the access token. The key matters only while tokens bound to it live; logging in again makes a new one.

The client must be registered at SystemAuth as a public client with the redirect URI http://127.0.0.1/callback, which SystemAuth matches on any port as RFC 8252 requires, and the authorization_code and refresh_token grants. An application marked first_party skips the consent screen, which suits a tool shipped to the deployment's own users; any other client asks the person to consent. Never grant a distributed client SystemAuth's allow_bearer_tokens exception: the binding is what makes a leaked token useless.

Index

Constants

View Source
const CallbackPath = "/callback"

CallbackPath is the path of the loopback redirect URI.

View Source
const RegisteredRedirectURI = "http://127.0.0.1" + CallbackPath

RegisteredRedirectURI is the redirect URI to register for the client at SystemAuth; the login listens on a free port, which SystemAuth accepts for a loopback URI.

Variables

View Source
var ErrNotLoggedIn = errors.New("publicclient: not logged in")

ErrNotLoggedIn means the store holds no credentials: the person has not logged in, or has logged out.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Issuer is the SystemAuth issuer URL. Required.
	Issuer string
	// ClientID is the client's OAuth client ID at SystemAuth. Required.
	ClientID string
	// Scopes requested at login. Default: relyingparty.DefaultScopes,
	// which include offline_access for a refresh token.
	Scopes []string
	// Store keeps the key and the tokens between runs. Required.
	Store Store
	// HTTPClient makes the requests to SystemAuth, and is the base of
	// [Session.HTTPClient]. Default: http.DefaultClient.
	HTTPClient *http.Client
	// Metadata is SystemAuth's provider metadata, when already known; nil
	// discovers it from Issuer.
	Metadata *relyingparty.Metadata
	// CallbackPort is the loopback port Login listens on. Default: a free
	// port.
	CallbackPort int
}

Config configures a public client.

type Credentials

type Credentials struct {
	// Issuer and ClientID name the SystemAuth deployment and the client
	// the tokens were issued by and to.
	Issuer   string `json:"issuer"`
	ClientID string `json:"client_id"`
	// DPoPKey is the serialized key pair (dpop.KeyPair.SerializeJSON).
	DPoPKey json.RawMessage `json:"dpop_key"`

	AccessToken  string    `json:"access_token"`
	RefreshToken string    `json:"refresh_token,omitempty"`
	Expiry       time.Time `json:"expiry,omitzero"`

	// Subject and Email identify the person, from the verified ID token.
	Subject string `json:"subject"`
	Email   string `json:"email,omitempty"`
}

Credentials are what a public client keeps between runs: the DPoP key its tokens are bound to, and the tokens. The key is as sensitive as the refresh token: together they act for the person until the refresh token is revoked.

type FileStore

type FileStore struct {
	// Path is the credentials file, e.g. a file under
	// os.UserConfigDir(). Required.
	Path string
}

FileStore keeps credentials in one JSON file readable by its owner only (0600, in a 0700 directory), written atomically and durably. On Unix-like systems it refuses a file that others can read, as ssh does for private keys. Its lock is an operating-system lock on a file next to it (flock, LockFileEx), which the system releases when the holder exits.

func (FileStore) Delete

func (f FileStore) Delete(_ context.Context) error

Delete implements Store.

func (FileStore) Load

func (f FileStore) Load(_ context.Context) (*Credentials, error)

Load implements Store.

func (FileStore) Lock

func (f FileStore) Lock(ctx context.Context) (func() error, error)

Lock implements Locker with an operating-system lock on a file next to Path. The lock file is never removed, so two processes always lock the same file, and the system releases the lock when its holder exits, so a process that dies holding it does not block the others.

func (FileStore) Save

func (f FileStore) Save(_ context.Context, c *Credentials) (err error)

Save implements Store: it writes a temporary file next to Path, flushes it, renames it over Path and flushes the directory, so a reader never sees a partial file and a crash cannot bring back the previous one. That matters: the previous refresh token has already been redeemed, and presenting it again revokes the login.

type Identity

type Identity struct {
	Subject string
	Email   string
}

Identity is the person a session acts for.

type Locker

type Locker interface {
	// Lock waits for the lock and returns the function that releases it.
	Lock(ctx context.Context) (unlock func() error, err error)
}

Locker is implemented by a Store that several processes share, such as a command-line tool and an MCP server it started. A Session holds the lock while it refreshes: SystemAuth rotates refresh tokens and treats a redeemed one presented again as stolen, revoking the whole family, so two processes must never redeem the same refresh token.

type Session

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

Session holds a signed-in person's DPoP key and tokens. It is safe for concurrent use.

func Login

func Login(ctx context.Context, cfg Config, open func(authURL string) error) (s *Session, err error)

Login signs a person in: it listens on a loopback port, calls open with the authorization URL (to open a browser, or print it for the person), waits for SystemAuth's redirect, exchanges the code for tokens bound to a new DPoP key, verifies the ID token, and saves the key and tokens in cfg.Store, replacing what was there.

func Open

func Open(ctx context.Context, cfg Config) (*Session, error)

Open returns the session stored in cfg.Store, or ErrNotLoggedIn. It refuses credentials of another issuer or client.

func (*Session) AccessToken

func (s *Session) AccessToken(ctx context.Context) (string, error)

AccessToken returns an access token valid for at least a minute, refreshing it first when needed. Present it only with a DPoP proof by the session's key, as the Transport does.

func (*Session) DPoPThumbprint

func (s *Session) DPoPThumbprint() string

DPoPThumbprint returns the thumbprint of the key the session's tokens are bound to.

func (*Session) HTTPClient

func (s *Session) HTTPClient() *http.Client

HTTPClient returns an http.Client whose requests the session authenticates (see Transport), based on cfg.HTTPClient.

func (*Session) Identity

func (s *Session) Identity() Identity

Identity returns the person the session acts for.

func (*Session) Logout

func (s *Session) Logout(ctx context.Context) (err error)

Logout revokes the refresh token at SystemAuth, which revokes its token family, and deletes the stored credentials. It holds the store's lock, so a refresh in another process cannot save credentials after they are deleted. The credentials are deleted even when the revocation fails; its error is returned.

func (*Session) Transport

func (s *Session) Transport(base http.RoundTripper) http.RoundTripper

Transport returns an http.RoundTripper that authenticates each request as the session's person: Authorization: DPoP with a valid access token, and a DPoP proof by the session's key for the request's method and URL, bound to the token. It refreshes the token when it is about to expire. A request refused with 401 is retried once, when its body can be sent again (GetBody): with the server's nonce for use_dpop_nonce, or with a refreshed token for invalid_token. base nil is http.DefaultTransport.

Send these requests only to the APIs the person means to use: a server that receives them can present the token for as long as it lives, but cannot make new proofs, so it cannot use the token elsewhere.

type Store

type Store interface {
	// Load returns the stored credentials, or ErrNotLoggedIn.
	Load(ctx context.Context) (*Credentials, error)
	// Save replaces the stored credentials.
	Save(ctx context.Context, c *Credentials) error
	// Delete removes the stored credentials. Deleting none succeeds.
	Delete(ctx context.Context) error
}

Store keeps a public client's Credentials between runs.

Jump to

Keyboard shortcuts

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