Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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, RFC 7009 revocation and webhook verification. The id_token and OIDC plumbing are handled by the vetted go-oidc and x/oauth2 — no hand-rolled crypto.

In a browser-facing service (publishable keys)

Everything else here assumes a confidential client. A publishable key is the opposite — public on purpose, useful only from the origins you registered. Reading the environment's own sign-in configuration lets a Go template render a themed sign-in box without shipping a JavaScript SDK to do it:

frontend, err := cboxid.NewFrontendClient("https://id.acme.com", "pk_live_…", nil)
config, err := frontend.Config(ctx)          // endpoints, social buttons, the theme
session, err := frontend.Session(ctx, token) // session.User is nil when nobody is signed in

An empty token yields an empty session rather than an error, and the key grants nothing on its own: the access token is the entire authority. A client secret passed there returns ErrNotPublishableKey at construction.

Migrating off an old login

Cbox ID can ask your service whether an email and password it has never seen are good, and import that person on the yes. You write the lookup; the handler owns the signature, the freshness window and the constant-time compare:

handler, err := cboxid.LegacyLoginHandler(secret, func(email, password string) (*cboxid.LegacyUser, error) {
    row, err := db.FindUser(email)
    if err != nil {
        return nil, err // could not decide → 503
    }
    if row == nil || !bcrypt.Match(row.Hash, password) {
        return nil, nil // no
    }

    return &cboxid.LegacyUser{Email: row.Email, Name: row.Name, PasswordHash: row.Hash}, nil
})

http.Handle("/cbox-legacy", handler)

Returning (nil, nil) is a wrong password. Returning an error is different: your store could not decide, and it answers 503 so Cbox ID refuses the sign-in rather than reading an outage as a bad credential. The secret is checked when the handler is built, so a missing one fails at startup rather than as a 500 that reads as an outage.

Install

Where do issuer, clientId and redirectUri come from? Register an application in your environment console — see Integrate your app.

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
err := client.Revoke(ctx, user.RefreshToken, cboxid.HintRefreshToken) // RFC 7009

Revoking a refresh token drops the whole token family — that's what "sign out everywhere" needs. Both calls are confidential-client, so they require a ClientSecret.

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.

About

Turnkey Cbox ID client for Go — CLI-first with the device authorization grant (RFC 8628), plus auth-code+PKCE, machine tokens, introspection and webhook verification.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages