Overview
The Box resource uses OAuth2 against the Box v2 API to:
/folders/{id}/items, list folder items (root id is 0)
/files/{id}/content, download file bytes
/files/{id} / /folders/{id}, fetch entry metadata
/search, search files by name/content
/oauth2/token, refresh access tokens
Box is similar to Dropbox in shape but uses numeric folder/file IDs rather than paths.
Mirage caches the path → ID mapping in the index store, so once you ls /box/ the IDs are
remembered and subsequent reads don’t pay the lookup cost.
Three credential modes are supported:
- Developer token, fastest first-run path. Click one button in the Box app console, paste
the 32-char access token, and you’re done. Lives 60 minutes; you regenerate by hand. No
client_id / refresh_token / OAuth flow needed. Recommended for
mirage exploration.
- Code flow with client secret, for Node/server long-running use. Needs
client_id,
client_secret, refresh_token.
- PKCE flow, for browsers. Needs only
client_id and refresh_token (no secret in the bundle).
Box rotates the refresh token on every refresh. The token you copy out of the initial code
exchange is only valid until the next refresh, after that, only the new rotated token works.
The BoxTokenManager keeps the latest token in memory; pass onRefreshTokenRotated if you
want to persist it across restarts (the browser example does this with localStorage).
Quick Start: Developer Token (60 minutes, no OAuth)
If you just want to try the resource against your own Box account, the developer token is the
fastest path, no redirect URI, no client secret, no curl exchange:
- Go to https://app.box.com/developers/console -> Create New App -> Custom App ->
User Authentication (OAuth 2.0) -> name it “Mirage” -> Create App
- On the Configuration tab, scroll to the Developer Token section near the bottom.
- Click Generate Developer Token. Copy the 32-char token.
- Paste into your
.env.development:
- Run any of the examples in
examples/typescript/box/.
The token expires in 60 minutes and there is no programmatic way to renew it; you regenerate
by hand. For long-running processes, use the OAuth flow below instead.
When BOX_DEVELOPER_TOKEN is set, the BoxResource skips the OAuth refresh path entirely and
calls Box directly with the token in the Authorization: Bearer header.
Setup (long-running OAuth flow)
1. Create a Box App
- Go to https://app.box.com/developers/console
- Click Create New App
- Choose Custom App
- Authentication Method: User Authentication (OAuth 2.0)
- Name it (e.g. “Mirage”) -> Create App
Open the app’s Configuration tab:
- OAuth 2.0 Redirect URIs: add every destination you’ll redirect back to. Box accepts
multiple values, register them all up front so you don’t bounce between dev and prod:
- For the CLI flow below:
http://localhost:1
- For the browser PKCE example:
http://localhost:5173/box_pkce.html
- For production: e.g.
https://yourapp.com/box/callback
- Allowed Origins (only needed if you’ll call from a browser; older Box docs call this
“CORS Domains”):
http://localhost:5173
- Any production origins (e.g.
https://yourapp.com)
- Application Scopes:
- Read all files and folders stored in Box (required)
- Write all files and folders stored in Box (only if you’ll add write commands later)
- Click Save Changes
3. Submit for Authorization (enterprise apps only)
If your Box account is part of an enterprise, the Authorization tab requires an admin to
approve the app before it can issue tokens. Personal/free Box accounts skip this step.
4. Copy the Client Credentials
Still on Configuration, copy:
- Client ID ->
BOX_CLIENT_ID
- Client Secret ->
BOX_CLIENT_SECRET (skip if you only need PKCE)
5. Get the Refresh Token (CLI flow with client secret)
A) Open this URL in a browser (replace YOUR_CLIENT_ID):
https://account.box.com/api/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:1
B) Authorize: sign in -> click Grant access.
C) Copy the code: browser redirects to http://localhost:1?code=... (page won’t load,
expected). Copy the code from the URL bar.
D) Exchange the code:
curl https://api.box.com/oauth2/token \
-d "code=THE_CODE" \
-d "grant_type=authorization_code" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "redirect_uri=http://localhost:1"
Response shape:
{
"access_token": "T...",
"expires_in": 3884,
"refresh_token": "R...",
"restricted_to": [],
"token_type": "bearer"
}
Save the refresh_token.
6. Set Environment Variables
# .env.development
BOX_CLIENT_ID=...
BOX_CLIENT_SECRET=...
BOX_REFRESH_TOKEN=...
Alternative: PKCE Flow (browser, no client secret)
- Add
http://localhost:5173/box_pkce.html to OAuth 2.0 Redirect URIs
- Add
http://localhost:5173 to Allowed Origins
- Set only
BOX_CLIENT_ID in .env.development
- From
examples/typescript/browser/, run pnpm dev
- Open
http://localhost:5173/box_pkce.html and click Connect Box
The example persists the rotated refresh token to localStorage via the
onRefreshTokenRotated callback so subsequent page loads work without re-auth.
Token Lifetime
| Token | Lifetime |
|---|
| Access token | ~1 hour (expires_in: ~3600) |
| Refresh token | 60 days from issue, OR 60 days from last use (whichever) |
A refresh token gets revoked when:
- 60 days pass without use
- The user revokes the app at https://app.box.com/account/security
- The Box account password changes
- A new refresh token is issued (the old one expires after a short grace period)
The BoxTokenManager always uses the latest token. For long-running processes restart-safe,
provide onRefreshTokenRotated to persist the new token to disk / a vault.
CORS Notes
Unlike Dropbox, Box has first-class CORS but only for origins you’ve explicitly allowlisted
in the Allowed Origins section of the app configuration. If you see browser-side
Access-Control-Allow-Origin errors, double-check the origin matches exactly (no trailing
slash; protocol matters).
Troubleshooting
| Issue | Fix |
|---|
400 invalid_grant on token exchange | The code is single-use and short-lived, redo Step 5A with a fresh URL |
403 access_denied | Enterprise admin hasn’t approved the app on the Authorization tab |
401 unauthorized after a few hours | Refresh token rotation, the original token is invalid; persist via onRefreshTokenRotated |
Browser: CORS error even though origin set | Check that the origin in Allowed Origins is exact (no trailing slash, http vs https) |
path/not_found on ls /box/<folder>/ | The folder name was constructed wrong, use ls /box/ first, then drill into a name from the list |
For TypeScript usage:
import { BoxResource } from '@struktoai/mirage-node'
const box = new BoxResource({
clientId: process.env.BOX_CLIENT_ID!,
clientSecret: process.env.BOX_CLIENT_SECRET!,
refreshToken: process.env.BOX_REFRESH_TOKEN!,
})
Or browser (PKCE):
import { BoxResource } from '@struktoai/mirage-browser'
const box = new BoxResource({
clientId: 'YOUR_CLIENT_ID',
refreshToken: refreshTokenFromLocalStorage,
onRefreshTokenRotated: (next) => localStorage.setItem('box-refresh', next),
})