feat(api): single bearer auth, /v1/me, RFC 8628 device flow endpoints#527
Merged
Conversation
Aligns rest-sandbox-open-api.yaml with how comparable single-host SaaS
REST APIs actually publish bearer auth (Stripe / Resend / OpenAI /
GitHub PAT pattern). The OAuth2 client_credentials wrapper carried zero
information once the dashboard collapsed to a single secret, and the
spec's bearerFormat: JWT misdescribed the long-lived opaque key the
SDK actually sends.
Spec changes:
- Replace dual securitySchemes (BearerAuth JWT + OAuth2) with single
BearerAuth (opaque dashboard key, no bearerFormat).
- Delete POST /oauth/tokens and its TokenRequest / TokenResponse
schemas.
- Add GET /v1/me returning Principal { sub, principal_type, email,
display_name, prefix, scopes, expires_at }. principal_type is an
enum [user, service_account] so the discriminator is type-driven.
- Update info.description Authentication section + Authentication tag
description.
Research backing (full reports under ~/.claude/plans/):
- 15-spec dual-auth survey: when an API serves both opaque-bearer and
OAuth2 bearer, every observed case uses two distinct securitySchemes
(Linode, Datadog, Cloudflare). bearerFormat: JWT with opaque keys
has zero precedents.
- 9-impl OAuth2 client_id survey: RFC 6749 \xc2\xa72.3.1 + Auth0 / Okta /
Keycloak / Cognito / Azure / Ory / HubSpot all require client_id.
No major service collapses API key -> client_secret inside
/oauth/token. Stripe / GitHub PAT / Postmark put the opaque key
directly in Authorization: Bearer.
- 22-API identity-endpoint survey: /v1/me is principal-agnostic
(matches Auth0 My Account, Spotify) and lives at the API root, above
the {prefix} tenant segment.
- 15-repo OpenAPI organization survey: monolithic dominates below ~5k
LOC; closest peer e2b (3344 LOC) is monolithic. Keep this file
monolithic; revisit at ~3500 LOC or when SDK codegen lands.
Follow-ups (gated on backend):
- Backend implements GET /v1/me + accepts opaque key as Bearer on
resource routes (tracked in project memory).
- SDK / CLI cleanup removes Credentials::ClientCredentials and
--client-id / --client-secret-stdin once /me is live.
Adds the OAuth 2.0 device authorization grant + token revocation endpoints to the BoxLite REST spec: - POST /v1/oauth/device_code (RFC 8628 §3.1) - POST /v1/oauth/token (RFC 6749 §4.4 + RFC 8628 §3.4) - POST /v1/oauth/revoke (RFC 7009) Device flow is preferred over RFC 8252 loopback PKCE for the dev- workstation audience: BoxLite users routinely run the CLI inside containers, SSH sessions, or remote dev pods where binding 127.0.0.1 for a callback fails (Vercel switched to device flow Sept 2025 for this reason). The BearerAuth scheme remains single (no bearerFormat) — its description now documents the four token sources the validation pipeline accepts: BoxLite-issued API keys, BoxLite-issued OAuth tokens, federated SSO JWTs (per-deployment), and customer-issued gateway tokens (per- deployment). Spec-only PR. SDK / CLI / server implementations ship in follow-ups: - feat/auth-rest-credential — Rust SDK + Python/Node FFI - feat/auth-cli-device-flow — boxlite auth login --web - feat/auth-server-stubs — Axum + Python reference servers + NestJS References: - RFC 8628 Device Authorization Grant - RFC 7009 Token Revocation - RFC 6750 §2.1 Bearer Token Usage
This was referenced May 14, 2026
4 tasks
DorianZheng
added a commit
that referenced
this pull request
May 15, 2026
PR #527 added /v1/oauth/device_code, /v1/oauth/token, /v1/oauth/revoke endpoints + their request/response schemas to the spec. On review, those spec entries turn out to be redundant and contract-drifting: Zero code consumers in the BoxLite tree read the OAuth schemas: - Python/Node SDKs expose only api_key (PyBoxliteRestOptions / JsBoxliteRestOptions). Go/C SDKs have no REST surface at all. - The Rust SDK's refresh_oauth() in src/boxlite/src/rest/client.rs posts hand-coded RFC 8628 forms — never reads the spec. - The CLI's commands::auth::device::login() hand-codes RFC 8628 forms. Industry alignment — none of these put OAuth endpoints in their spec: Stripe, GitHub, DigitalOcean, Anthropic, OpenAI. Only identity-provider products (Auth0, Okta) co-locate. Their business is auth; ours isn't. The wire format is fully defined by published IETF RFCs: - RFC 8628 §3.1 — device authorization request/response - RFC 6749 §4.4 / §6 — token exchange + refresh - RFC 7009 — token revocation Restating those in OpenAPI adds maintenance churn with no precision gain — implementers follow the RFC either way. Contract-drift fix: PR #530's BoxliteOAuthController in apps/api returns 503 temporarily_unavailable on every /v1/oauth/* route because the real OAuth server isn't built. With the endpoints out of the spec, the gateway no longer promises something it can't deliver. When the @node-oauth/node-oauth2-server backend lands, that work will add both the controller and the spec paths in one coherent commit. Spec changes: - Remove /v1/oauth/device_code, /v1/oauth/token, /v1/oauth/revoke paths - Remove DeviceAuthorizationRequest/Response, OAuthTokenRequest/Response, OAuthRevokeRequest, OAuthError schemas - Update info.description to point at the IETF RFCs for the wire format - BearerAuth.description: keep the four-token-source list; soften blo_/blr_ prefix descriptions to reference the RFCs not specific paths
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
OpenAPI spec change only. Three logical edits ship together because the wire contract is one coherent story:
BearerAuth(drops the OAuth2client_credentialsfacade that collapsed to "client_secret IS the API key" — RFC 6749 §2.3.1 non-compliant). Description documents the four token sources the validation pipeline accepts: BoxLite-issued API keys, BoxLite-issued OAuth tokens, federated SSO JWTs, customer-issued gateway tokens.GET /v1/me+Principalschema (sub,principal_type,email,display_name,prefix,scopes,expires_at)./v1/oauth/device_code//v1/oauth/token//v1/oauth/revokefor RFC 8628 device flow + RFC 7009 revocation.Device flow over loopback PKCE: BoxLite users run the CLI inside containers / SSH / remote dev pods where binding 127.0.0.1 fails. Vercel switched for the same reason in Sept 2025.
Stacked PRs
This is one of four PRs that together replace the original combined branch:
feat/auth-single-bearer-implfeat/auth-rest-credentialauth login --web—feat/auth-cli-device-flow(stacks on PR 2)feat/auth-server-stubsEach PR is independently reviewable; only PR 3 has a hard merge dependency (on PR 2).
Test plan
spectral,openapi-cli validate)