feat(auth): add self-refreshing token-file auth mode#200
Open
npstewart87 wants to merge 3 commits into
Open
Conversation
Adds a third auth mode (XERO_TOKEN_FILE) that reads an OAuth2 token store from disk and renews the access token via the rolling refresh token when near expiry, persisting the rotated token back atomically with 0600 perms. This gives self-hosted/local users persistent, free authentication that works in every region — without a paid Custom Connection and without the static bearer token expiring mid-session (~30 min). authenticate() runs at the start of every handler, so each tool call gets a valid token. Optional XERO_TENANT_ID pins a single org when the token has multiple tenants connected; otherwise the first connected tenant is used. Refs XeroAPI#163.
Extends the XERO_TOKEN_FILE mode so the initial token file no longer has to be produced by hand. A new `auth` subcommand runs the full OAuth2 authorization-code flow itself: it opens the browser to Xero's authorize URL, catches the redirect on a one-shot local callback server, exchanges the code (HTTP Basic client auth), and writes the token store the self-refreshing client consumes. After this, RefreshingTokenXeroClient keeps it alive. - src/consts/auth.ts: endpoints + resolveScopes(). XERO_SCOPES is required (no built-in default — avoids silently over/under-granting); offline_access is appended automatically since it yields the refresh token. Default redirect port is 53682, not 5000 (macOS AirPlay Receiver owns 5000). - src/auth/token-store.ts: shared readTokens/persistTokens/stampExpiry so the bootstrap and the refreshing client agree on the on-disk shape and cannot drift. xero-client.ts now uses these helpers. - src/auth/bootstrap-auth.ts: runAuthorizationCodeFlow() — state/CSRF check, 5-minute callback timeout, cross-platform browser open with manual-URL fallback, and a tenant listing so multi-org users can set XERO_TENANT_ID. - src/index.ts: `auth` argv branch, lazily importing the bootstrap so a normal run leaves the stdio transport untouched. - README: document the command, required scopes, and the one-time redirect-URI prerequisite on the Xero app.
|
Love this!! I went ahead and also implemented the process to generate the initial Xero token file. The PR for that is posted on the fork repo: npstewart87#1 |
feat(auth): end-to-end OAuth bootstrap (`auth` command) for token-file mode
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.
What this adds
A third auth mode,
XERO_TOKEN_FILE, that reads an OAuth2 token store and self-refreshes via the rolling refresh token. Free, region-agnostic, persistent — no Custom Connection, no 30-minute bearer-token expiry.Why
For a self-hosted/local user (Claude Desktop/Code) both existing modes fall short:
This closes the gap using the standard authorization-code +
offline_accessflow every Xero app already supports — in every region, for free.Behavior
XERO_TOKEN_FILE; takes precedence over bearer/custom. Non-breaking — existing paths untouched.0600).expires_at→_obtained_at + expires_in→ refresh).XERO_TENANT_IDpins one org when multiple tenants are connected (also helps MCP Returns Data from Only One Xero Entity Despite Multiple Authorizations #83 / Enable per-request Xero authentication to support multi-tenant usage #85); otherwise the first connected tenant is used.Notes
Same gap raised in #163 and previously attempted in #140 / #120. I'd genuinely appreciate a maintainer's position: if there's an architectural objection, I'm happy to adapt — but if the only blocker is that free self-refresh overlaps with the paid Custom Connection, please say so explicitly, because today non-AU/NZ/UK/US users have no working long-session auth at all.
Tested locally against a US org via Claude Code — full read + write, including bank transactions.