OAuth gives your application permission to work with a user's Lunch Money data. A few deliberate choices about credential storage, callbacks, logging, and recovery help you protect that access and earn users' trust.
Before a client can be approved for use by other Lunch Money users, its application must have a publicly accessible privacy policy. The policy gives users a place to understand how the application handles their data before they authorize it. See application review and approval for the complete review requirements.
A web application can keep access tokens on its server and have browser code call its own backend. Where direct browser access to the API is supported, it can instead make access tokens available to browser code and call the Lunch Money API from there. OAuth does not require one architecture for every application.
Keeping access tokens on the server reduces their exposure to browser code. Using them in the browser can simplify some applications and makes it easier for the signed-in user or support team to inspect Lunch Money API requests and responses in the browser's Network panel.
In the browser-based model, the short-lived access token is also visible to the signed-in user in the request's Authorization header. That visibility is expected and is not, by itself, a credential leak. The tradeoff is that a malicious script running on the page—or someone with access to that browser session—could copy the bearer token and use it until it expires or is revoked.
If your browser code uses access tokens, keep them available for no longer than necessary, avoid persistent browser storage when practical, and protect the application against cross-site scripting. Regardless of the architecture, keep the client secret and refresh tokens on the server, never put bearer tokens in URLs or logs, and associate each authorization with the correct application user and Lunch Money budget account.
A confidential application must authenticate its own user before starting Lunch Money OAuth. Bind each authorization attempt to that authenticated user's server-side session, then recover the owner from the consumed server-side attempt after the callback is verified. Do not let the callback choose who owns the resulting credentials.
Store each budget-account connection under a stable internal application-user ID. Within that owner, use both the Lunch Money user id and budget-account account_id returned by GET /v2/me as the provider identity. Your database may assign a separate internal connection ID for routing and references; it does not replace the (application user ID, Lunch Money user id, account_id) fields used to create or update a provider connection. Do not use an email address, callback parameter, form field, browser cookie, or other browser-supplied value to establish the credential owner or provider identity.
An application user may own connections for more than one Lunch Money user, although applications do not have to expose that capability. If your application supports only one Lunch Money identity per application user, detect a different id after GET /v2/me and handle it explicitly; never overwrite the existing identity's credentials.
Authenticated application user
-> owned connection (Lunch Money user id + account_id)
-> encrypted credential set
When the user returns to your application, authenticate them through your application's own sign-in system before listing or using their saved connections. Knowing a Lunch Money id, account_id, or internal connection ID does not prove ownership. Limit connection lookups to the authenticated application user's records, and reject requests for a connection they do not own before loading its credentials.
For example, if Alex and Sam both use your application, Sam must not be able to use Alex's saved credentials by submitting Alex's connection ID. This remains true even if they both have access to the same Lunch Money budget account. Each application user must authorize their own connection. If Alex has authorized two budget accounts, both connections belong to Alex's application user and may be listed and selected within Alex's authenticated session. Each connection still uses the credential set for its own Lunch Money id/account_id pair.
Enforce this ownership and identity check for every API request, refresh, reauthorization, revocation, and deletion involving a saved connection. Background jobs must carry the connection's stored owner and identity so they use the same checks without an interactive session. Updating credentials after reauthorization must remain within the original application user's connections and match both the returned Lunch Money id and account_id. A different Lunch Money user authorizing the same shared account must not silently overwrite an existing connection.
Durable credentials must be encrypted at rest, and backend access should be limited to the services and operators that need it. Encryption protects stored secrets; your application must still enforce ownership when it reads or uses them. Never return refresh tokens or client secrets to browser JavaScript. List connections using non-secret metadata such as budget_name, and keep their credentials in the protected store.
Apply API calls, refresh, revocation, local deletion, and caches only to the active Lunch Money user and budget-account connection. Disconnecting a budget account should revoke continuing access for that connection and remove its locally stored credentials while keeping the active user's other connections intact. If the active user's final connection is removed, enter a disconnected state rather than falling back to another Lunch Money user's hidden connection.
On application sign-out or Lunch Money user switching, clear session-specific selections and displayed or cached data so the newly active identity cannot see the previous identity's information. An application may retain the previous Lunch Money user's encrypted credentials when its ownership model permits it, but those connections must remain hidden and unavailable until that identity becomes active again. It may instead delete credentials during its own sign-out or switching lifecycle; that is an application policy.
Keep remote revocation and local credential deletion separate. A local-only “forget” action does not end the remote authorization, so confirm that action and explain that access remains active at Lunch Money. Signing out of your application, hiding a retained connection, forgetting a local credential, and disconnecting Lunch Money are distinct lifecycle events.
The confidential Node.js sample makes this boundary explicit in its CredentialStore abstraction. Its security guide explains the threat model, and its production checklist lists the storage, tenant-isolation, lifecycle, and operational work the teaching sample deliberately omits. Those boundaries matter, but their implementation and database schema are specific to your application.
state and verify it before exchanging a code.S256 for every authorization.Cache-Control: no-store for sensitive responses.Choose only the resource scopes needed for expected features. Add offline_access only for genuine unattended operation. Remember that scope changes require a replacement client and reauthorization; they cannot be requested dynamically.
Rotate client secrets on a schedule appropriate for your application's risk and immediately when one may have been exposed. A client can have up to three non-revoked secrets, so confirm that it has room before starting a rotation. Expired secrets still count toward this limit until you revoke them. Use overlapping active secrets during a controlled rotation: create the replacement, deploy and verify it, and then revoke the old secret. Coordinate refreshes so two workers do not replay the same rotating token, and atomically persist every complete replacement credential set. Production storage needs database transactions, distributed per-connection locking, recovery when persistence fails, multi-instance coordination, and tenant isolation. Make disconnect invalidate continuing refresh access as well as clear local state.
If a credential may have been exposed, stop using it, revoke it, rotate the relevant client secret, review logs without copying secret values, and require reauthorization when the grant can no longer be trusted.
Next: Review token recovery and troubleshoot failures.