cboxid

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 20 Imported by: 0

README

cboxid — Cbox ID client for Go

Go Reference

Turnkey Cbox ID client for Go, built for command-line tools: log a CLI in with the device authorization grant (RFC 8628) — the flow the GitHub CLI uses — where the user approves a short code in a browser on any device while your program polls.

It also supports the standard authorization-code + PKCE flow for server apps, plus machine tokens, UserInfo, RFC 7662 introspection and webhook verification. The id_token and OIDC plumbing are handled by the vetted go-oidc and x/oauth2 — no hand-rolled crypto.

Install

go get github.com/cboxdk/id-go

CLI login (device flow)

client, _ := cboxid.New(ctx, cboxid.Config{
    Issuer:      "https://id.acme.com",
    ClientID:    "client_...",
    RedirectURI: "http://localhost", // unused by the device flow, but required
    Scopes:      []string{"openid", "profile", "email", "offline_access"},
})

auth, _ := client.RequestDeviceAuthorization(ctx, cboxid.DeviceParams{})
fmt.Printf("Visit %s and enter code %s\n", auth.VerificationURI, auth.UserCode)

// Blocks until the user approves (or the code expires); honors the poll interval.
user, err := client.PollDeviceToken(ctx, auth)
fmt.Printf("Signed in as %s\n", user.Email)
// Persist user.Token (with its refresh token) to your CLI config for next time.

A complete, runnable example is in examples/cli.

Server login (authorization code + PKCE)

req := client.CreateAuthorizationRequest(cboxid.AuthParams{})
// persist req.State, req.CodeVerifier, req.Nonce; redirect the user to req.URL

// on the callback:
user, err := client.Authenticate(ctx,
    cboxid.Callback{Code: code, State: state},
    cboxid.Stored{State: req.State, CodeVerifier: req.CodeVerifier, Nonce: req.Nonce},
)

Back-channel calls

token, _ := client.MachineToken(ctx, cboxid.MachineTokenParams{Scopes: []string{"reports.read"}})
claims, _ := client.UserInfo(ctx, user.AccessToken)
result, _ := client.Introspect(ctx, someToken) // RFC 7662

Verify webhooks

ok := cboxid.VerifyWebhook(rawBody, r.Header.Get("X-Cbox-Signature"), webhookSecret, 300)

Declare roles & permissions, publish a manifest

Declare your app's authorization roles and permissions in code and push them to Cbox ID on deploy. Cbox ID owns identity and who holds what; your app owns what a role means. Assigned roles then arrive in the token's claims for you to enforce. Requires a ClientSecret and a client that holds the apps.manifest scope.

client, _ := cboxid.New(ctx, cboxid.Config{
    Issuer:       "https://id.acme.com",
    ClientID:     "client_...",
    ClientSecret: "secret_...",
    RedirectURI:  "http://localhost", // unused when only publishing, but required
    Permissions: []cboxid.Permission{
        {Key: "invoices:create", Description: "Create invoices"},
        {Key: "invoices:read", Description: "View invoices"},
    },
    Roles: []cboxid.Role{
        {Key: "billing-admin", Name: "Billing Admin", Description: "Full billing access",
            Permissions: []string{"invoices:create", "invoices:read"}},
    },
})

// Run on deploy. Idempotent — republishing an unchanged manifest is a no-op.
summary, err := client.PublishManifest(ctx)
// summary.Unchanged, summary.RolesDeclared, summary.PermissionsDeclared

PublishManifest mints a client-credentials token scoped to apps.manifest, then POSTs the manifest to {issuer}/api/v1/apps/manifest. A rejected push wraps cboxid.ErrManifestRejected. A complete example is in examples/publish-manifest. This mirrors the Laravel client's php artisan cbox-id:publish-manifest, so the manifest contract is identical across SDKs.

Errors

Errors wrap the sentinels cboxid.ErrInvalidState, cboxid.ErrAuthentication and cboxid.ErrConfiguration — match them with errors.Is. A state mismatch is ErrInvalidState; treat it as a fresh start, not a user-facing error.

Security & scope

Login is hardened by default — PKCE, state, nonce (auth-code flow), and full id_token verification (signature/issuer/audience) via go-oidc. Key accounts on user.ID (the stable subject), not on email.

This is a client. It authenticates users and calls a Cbox ID instance's standard endpoints; it does not configure SSO, run SCIM, or manage organizations — those are platform capabilities of cboxdk/laravel-id.

Report vulnerabilities via this repo's GitHub Private Vulnerability Reporting.

License

MIT © Cbox.

Documentation

Overview

Package cboxid is a turnkey Cbox ID client for Go. It speaks standard OpenID Connect against a Cbox ID instance — so integrating is a redirect and a callback, not a rewrite — and adds the conveniences a hosted-identity product needs: a redirect to the instance's hosted profile page, and back-channel helpers (machine tokens, userinfo, RFC 7662 introspection, webhook verification).

Login is hardened by default: PKCE (S256), a CSRF state check, a nonce, and full id_token signature + issuer + audience verification against the instance's JWKS. The heavy lifting is delegated to the vetted github.com/coreos/go-oidc/v3 and golang.org/x/oauth2 — no hand-rolled crypto or JWT parsing.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrConfiguration indicates a missing or invalid configuration value.
	ErrConfiguration = errors.New("cboxid: configuration error")
	// ErrInvalidState indicates the login state did not match — a forged or stale
	// callback. Treat it as a fresh start, not an error to show the user.
	ErrInvalidState = errors.New("cboxid: login state did not match")
	// ErrAuthentication indicates login could not be completed, or a token failed
	// verification.
	ErrAuthentication = errors.New("cboxid: authentication failed")
	// ErrManifestRejected indicates Cbox ID refused an authorization-manifest push —
	// e.g. the client lacks the apps.manifest scope, or the manifest was malformed.
	// The wrapped message carries the HTTP status and the server's response body.
	ErrManifestRejected = errors.New("cboxid: manifest push rejected")
)

Functions

func VerifyWebhook

func VerifyWebhook(payload string, signatureHeader string, secret string, toleranceSeconds int) bool

VerifyWebhook verifies a Cbox ID webhook / inline-action signature. The header is "t={unix},v1={hex hmac}" — an HMAC-SHA256 over "{timestamp}.{raw body}", valid within toleranceSeconds. Pass the RAW request body, not a re-serialized copy. It returns false (never panics) on any problem.

Types

type AuthParams

type AuthParams struct {
	Scopes    []string // overrides the configured default scopes
	Prompt    string   // e.g. "login" or "consent"
	LoginHint string
}

AuthParams optionally customizes a single authorization request.

type AuthorizationRequest

type AuthorizationRequest struct {
	URL          string
	State        string
	CodeVerifier string
	Nonce        string
}

AuthorizationRequest is returned by CreateAuthorizationRequest. Persist State, CodeVerifier and Nonce (e.g. in the session) and hand them back to Authenticate.

type Callback

type Callback struct {
	Code  string
	State string
	Error string
}

Callback holds the query parameters as they arrive on your callback route.

type CboxUser

type CboxUser struct {
	ID             string
	Email          string
	Name           string
	OrganizationID string
	Claims         map[string]any
	AccessToken    string
	RefreshToken   string
	IDToken        string
	Expiry         time.Time
	// Token is the raw oauth2 token, e.g. for building an authenticated client.
	Token *oauth2.Token
}

CboxUser is the authenticated user. ID is the stable subject (sub) you key your local account on. Claims is the full verified id_token + userinfo claim set.

type Client

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

Client is a Cbox ID client. Construct it with New and share it; it is safe for concurrent use.

func New

func New(ctx context.Context, cfg Config) (*Client, error)

New builds a Client, discovering the instance's endpoints and JWKS from {issuer}/.well-known/openid-configuration.

func (*Client) Authenticate

func (c *Client) Authenticate(ctx context.Context, cb Callback, stored Stored) (*CboxUser, error)

Authenticate completes login on your callback route: it verifies the state, exchanges the code with the PKCE verifier, verifies the id_token, and returns the user. It returns ErrInvalidState on a state mismatch and wraps ErrAuthentication on any other failure.

func (*Client) CreateAuthorizationRequest

func (c *Client) CreateAuthorizationRequest(params AuthParams) AuthorizationRequest

CreateAuthorizationRequest begins login. Redirect the user to the returned URL and persist State, CodeVerifier and Nonce for Authenticate.

func (*Client) Introspect

func (c *Client) Introspect(ctx context.Context, token string) (map[string]any, error)

Introspect performs RFC 7662 token introspection (confidential-client auth). The returned map's "active" field tells you if the token is currently valid.

func (*Client) LogoutURL

func (c *Client) LogoutURL(returnTo string) string

LogoutURL is the RP-initiated logout URL, or "" when the instance advertises none.

func (*Client) MachineToken

func (c *Client) MachineToken(ctx context.Context, params MachineTokenParams) (string, error)

MachineToken returns a machine (client-credentials) access token for calling Cbox ID APIs as your app, not on a user's behalf. Requires a client secret.

func (*Client) Manifest added in v0.2.0

func (c *Client) Manifest() Manifest

Manifest returns the manifest that PublishManifest would send: the configured permissions and roles with a stable content hash as its version. The hash is the first 16 hex chars of the SHA-256 over the JSON-encoded permissions+roles, so an unchanged catalog yields an unchanged version.

func (*Client) PollDeviceToken

func (c *Client) PollDeviceToken(ctx context.Context, auth *DeviceAuth) (*CboxUser, error)

PollDeviceToken blocks until the user approves the DeviceAuth (or it expires), honoring the poll interval and the authorization_pending / slow_down signals, then verifies the id_token and returns the user. Pass a context with a deadline to bound the wait. There is no nonce in the device flow, so the nonce check is skipped; signature, issuer and audience are still verified.

func (*Client) ProfileURL

func (c *Client) ProfileURL(returnTo string) string

ProfileURL is the URL of the instance's hosted account/profile page (self-service password, MFA, passkeys, sessions). A signed-in user is authenticated there by their Cbox ID session; returnTo, when non-empty, is passed so the page can link back to your app.

func (*Client) PublishManifest added in v0.2.0

func (c *Client) PublishManifest(ctx context.Context) (Summary, error)

PublishManifest pushes this app's declared roles and permissions (Config.Roles and Config.Permissions) to Cbox ID, and returns the server's sync summary. Run it on deploy so the Cbox ID console always reflects the app's current catalog; it is idempotent — republishing an unchanged manifest is a no-op.

It mints a client-credentials token scoped to apps.manifest, then POSTs the manifest to {issuer}/api/v1/apps/manifest with that bearer token. Requires a ClientSecret and a client that holds the apps.manifest scope. It returns ErrConfiguration when nothing is declared or credentials are missing, and wraps ErrManifestRejected when Cbox ID refuses the push.

func (*Client) RequestDeviceAuthorization

func (c *Client) RequestDeviceAuthorization(ctx context.Context, params DeviceParams) (*DeviceAuth, error)

RequestDeviceAuthorization starts the device authorization grant — the flow a CLI or a TV app uses: the user authorizes on a second device (phone/laptop) while your program polls. Requires the instance to support the device grant.

func (*Client) UserInfo

func (c *Client) UserInfo(ctx context.Context, accessToken string) (map[string]any, error)

UserInfo returns the OIDC userinfo claims for an access token.

type Config

type Config struct {
	// Issuer is the base URL of the Cbox ID instance, e.g. https://id.acme.com.
	Issuer string
	// ClientID is your registered OAuth client id.
	ClientID string
	// ClientSecret is required for confidential apps, machine tokens and
	// introspection. Leave empty for public clients doing only PKCE login.
	ClientSecret string
	// RedirectURI is your callback URL, registered on the client.
	RedirectURI string
	// Scopes requested at login. Defaults to openid, profile, email.
	Scopes []string
	// AccountPath is the instance's hosted account page. Defaults to /settings.
	AccountPath string
	// HTTPClient, when set, is used for all back-channel calls (else http.DefaultClient).
	HTTPClient *http.Client
	// Permissions and Roles declare this app's authorization catalog in code, for
	// PublishManifest to push to Cbox ID on deploy. Cbox ID owns identity and who
	// holds what; the app owns what a role means. Leave empty if you don't publish a
	// manifest. Requires the client to hold the apps.manifest scope.
	Permissions []Permission
	Roles       []Role
}

Config configures a Client.

type DeviceAuth

type DeviceAuth struct {
	// DeviceCode is the secret your CLI polls with; don't show it to the user.
	DeviceCode string
	// UserCode is the short code the user types on the verification page.
	UserCode string
	// VerificationURI is where the user goes to authorize.
	VerificationURI string
	// VerificationURIComplete embeds the user code, for a clickable link / QR.
	VerificationURIComplete string
	// Expiry is when this authorization stops being valid.
	Expiry time.Time
	// Interval is the minimum seconds between polls.
	Interval int64
	// contains filtered or unexported fields
}

DeviceAuth is a pending device authorization (RFC 8628). Show UserCode to the person and send them to VerificationURI (or VerificationURIComplete, which embeds the code), then call PollDeviceToken.

type DeviceParams

type DeviceParams struct {
	Scopes []string
}

DeviceParams optionally scopes a device authorization.

type MachineTokenParams

type MachineTokenParams struct {
	Scopes   []string
	Resource string // RFC 8707 resource indicator
}

MachineTokenParams optionally scopes a machine token.

type Manifest added in v0.2.0

type Manifest struct {
	Permissions []Permission `json:"permissions"`
	Roles       []Role       `json:"roles"`
	Version     string       `json:"version"`
}

Manifest is the app's declared authorization catalog as it is sent to Cbox ID: its permissions and roles plus a content-derived Version, so republishing an unchanged catalog is a server-side no-op. Its JSON shape is the contract shared by every Cbox ID SDK.

type Permission added in v0.2.0

type Permission struct {
	Key         string `json:"key"`
	Description string `json:"description,omitempty"`
}

Permission is one authorization permission the app declares. Key is a "feature:action" slug (e.g. "invoices:create"); Description is human-facing copy shown in the Cbox ID console.

type Role added in v0.2.0

type Role struct {
	Key         string   `json:"key"`
	Name        string   `json:"name,omitempty"`
	Description string   `json:"description,omitempty"`
	Permissions []string `json:"permissions"`
}

Role is one authorization role the app declares. Permissions must reference keys of declared Permissions. Cbox ID assigns roles to users and returns them in the token's claims; the app decides what each role is allowed to do.

type Stored

type Stored struct {
	State        string
	CodeVerifier string
	Nonce        string
}

Stored holds the values you persisted from CreateAuthorizationRequest.

type Summary added in v0.2.0

type Summary struct {
	Unchanged           bool     `json:"unchanged"`
	RolesDeclared       int      `json:"roles_declared"`
	PermissionsDeclared int      `json:"permissions_declared"`
	OrphanedRoles       []string `json:"orphaned_roles,omitempty"`
	OrphanedPermissions []string `json:"orphaned_permissions,omitempty"`
}

Summary is Cbox ID's response to a manifest push. Unchanged is true when the pushed catalog matched what was already synced. Orphaned* list keys the app once declared but dropped — Cbox ID keeps and flags them rather than deleting them.

Directories

Path Synopsis
examples
cli command
Command cli is a minimal example of logging a CLI in to Cbox ID using the device authorization grant (RFC 8628) — the same flow the GitHub CLI uses: the tool prints a short code, the user approves it in a browser on any device, and the CLI receives the tokens.
Command cli is a minimal example of logging a CLI in to Cbox ID using the device authorization grant (RFC 8628) — the same flow the GitHub CLI uses: the tool prints a short code, the user approves it in a browser on any device, and the CLI receives the tokens.
publish-manifest command
Command publish-manifest declares this app's authorization roles and permissions in code and pushes them to Cbox ID — the Go counterpart of Laravel's `php artisan cbox-id:publish-manifest`.
Command publish-manifest declares this app's authorization roles and permissions in code and pushes them to Cbox ID — the Go counterpart of Laravel's `php artisan cbox-id:publish-manifest`.

Jump to

Keyboard shortcuts

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