@lunch-money/developer-docs 2.11.2-preview.1 → 2.11.2-preview.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/oauth/authorization-code.md +2 -0
- package/docs/oauth/concepts.md +3 -1
- package/docs/oauth/development.md +2 -0
- package/docs/oauth/index.md +1 -1
- package/docs/oauth/register-client.md +4 -0
- package/docs/oauth/sample-applications.md +199 -0
- package/docs/oauth/security.md +11 -1
- package/docs/oauth/tokens.md +5 -1
- package/docs/oauth/troubleshooting.md +2 -0
- package/manifest.json +9 -0
- package/package.json +1 -1
- package/v2/spec/lunch-money-api-v2.yaml +1 -1
|
@@ -15,6 +15,8 @@ OAuth libraries handle much of the protocol work for you, including generating P
|
|
|
15
15
|
|
|
16
16
|
For Node.js and TypeScript, [`openid-client`](https://github.com/panva/openid-client) is one maintained option. Despite its name, using it does not mean that Lunch Money supports OpenID Connect identity scopes, ID tokens, or UserInfo.
|
|
17
17
|
|
|
18
|
+
The [confidential Node.js sample application](/oauth/sample-applications#understand-and-apply-the-teaching-code) shows this library in a complete Lunch Money flow. Begin with its [`src/oauth` guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/src/oauth/README.md), then follow the listed reading order from protocol types and discovery through authorization, callback validation, credential storage, API access, optional refresh, and revocation.
|
|
19
|
+
|
|
18
20
|
## Configure Lunch Money with discovery
|
|
19
21
|
|
|
20
22
|
Your OAuth library needs to know where to send the user for authorization, where to exchange codes for tokens, and which protocol features Lunch Money supports. Instead of configuring each value separately, give the library the Lunch Money issuer:
|
package/docs/oauth/concepts.md
CHANGED
|
@@ -7,7 +7,7 @@ These terms describe who participates in Lunch Money OAuth and what each credent
|
|
|
7
7
|
- **User (resource owner):** the Lunch Money user who authorizes a third-party application to make API requests on their behalf for one budgeting account.
|
|
8
8
|
- **Application:** the complete product or service the developer builds. It may contain many features unrelated to Lunch Money; only its Lunch Money OAuth client is managed in the Developer Portal.
|
|
9
9
|
- **Client:** the application's OAuth registration with Lunch Money: its `client_id`, type, redirect URIs, selected scopes, and—when applicable—client secret. The developer must register this client in the Developer Portal before the application can ask Lunch Money users to authorize API access.
|
|
10
|
-
- **Authorization server:** the Lunch Money-owned service that presents the Lunch Money sign-in and consent screens, records the user's decision, and issues authorization codes and tokens. The user signs in directly with Lunch Money; the third-party application never receives or sees their Lunch Money login credentials.
|
|
10
|
+
- **Authorization server:** the Lunch Money-owned service that presents the Lunch Money sign-in and consent screens, records the user's decision, and issues authorization codes and tokens. It also handles token requests, including exchanging authorization codes and refresh tokens for new tokens. The user signs in directly with Lunch Money; the third-party application never receives or sees their Lunch Money login credentials.
|
|
11
11
|
- **Lunch Money API service:** the Lunch Money-owned V2 API, which receives the application's API requests, accepts access tokens, and checks that each request is permitted by the client's scopes.
|
|
12
12
|
|
|
13
13
|
## Client types and credentials
|
|
@@ -17,6 +17,8 @@ When a developer registers a client, they must choose a client type based on whe
|
|
|
17
17
|
- **Confidential web client:** Runs on a server and can securely store client secrets. In OAuth terminology, this is called a *confidential client*. Choose this type for a web application—or any other application architecture with a centralized server. Its client secret helps Lunch Money verify that a token request came from the application that registered the client. The client secret and refresh tokens must stay on the server. The application must protect access tokens wherever its architecture uses them, and associate each authorization with the correct application user and authorized Lunch Money budgeting account.
|
|
18
18
|
- **Native or public client:** Runs on a user-controlled device and uses PKCE without a client secret because installed application code cannot keep a secret secure. In OAuth terminology, this is called a *native public client*. Choose this type for a standalone mobile or desktop application. The app stores each user's tokens securely on that user's device—for example, in Keychain on Apple platforms or appropriate Keystore-backed storage on Android—and must avoid exposing them through backups, logs, or application data shared with other apps.
|
|
19
19
|
|
|
20
|
+
These client families are not interchangeable. A confidential server-side sample cannot be moved into a native application by embedding its client secret or server-held credentials; use a native-client flow and platform secure storage instead.
|
|
21
|
+
|
|
20
22
|
Every client receives a client ID that identifies its Lunch Money registration. The client ID is not secret. Both client types also use Proof Key for Code Exchange (PKCE) to bind the authorization response to the application that started the flow.
|
|
21
23
|
|
|
22
24
|
## Redirect URIs
|
|
@@ -13,6 +13,8 @@ Use a separate Lunch Money test budgeting account when exercising create, update
|
|
|
13
13
|
|
|
14
14
|
Keep production client secrets, tokens, and user data out of test fixtures, logs, screenshots, and bug reports.
|
|
15
15
|
|
|
16
|
+
If you want a working reference flow for comparison, [try OAuth with the confidential Node.js sample](/oauth/sample-applications#try-oauth-with-your-lunch-money-account). Its `me:read` walkthrough helps validate client registration, exact local callback configuration, discovery, authorization, `GET /v2/me`, and revocation before you debug application-specific features. Add `offline_access` when registering the sample client only if you also want to exercise its optional refresh branch.
|
|
17
|
+
|
|
16
18
|
## Local loopback callbacks
|
|
17
19
|
|
|
18
20
|
When you run a confidential web application on your own computer, its callback handler may not have a public HTTPS address yet. A loopback redirect lets the browser return the authorization result directly to the local process you are developing.
|
package/docs/oauth/index.md
CHANGED
|
@@ -25,4 +25,4 @@ An OAuth client is an application's registration with Lunch Money. When a develo
|
|
|
25
25
|
|
|
26
26
|
You need an active Lunch Money account to [register and manage an OAuth client](/oauth/applications) for your application. During development, only the client owner can authorize the application. Approval is required before other Lunch Money users can authorize it.
|
|
27
27
|
|
|
28
|
-
Start with [OAuth concepts](/oauth/concepts), [register a client](/oauth/register-client), and then [implement the authorization-code flow](/oauth/authorization-code). Use the [scope catalog](/oauth/scopes) to plan permissions and [troubleshooting](/oauth/troubleshooting) when a flow fails.
|
|
28
|
+
Start with [OAuth concepts](/oauth/concepts), [register a client](/oauth/register-client), and then [implement the authorization-code flow](/oauth/authorization-code). You can also run the [confidential Node.js sample application](/oauth/sample-applications) to see registration, authorization, an API call, revocation, and local reset working together. Use the [scope catalog](/oauth/scopes) to plan permissions and [troubleshooting](/oauth/troubleshooting) when a flow fails.
|
|
@@ -11,6 +11,8 @@ Before you add OAuth to your application code, register an OAuth client with Lun
|
|
|
11
11
|
|
|
12
12
|
List the Lunch Money features your application will provide, then use the [scope catalog](/oauth/scopes) to choose the smallest complete set of permissions those features require. Select `offline_access` only if the application needs to continue making API requests after the initial access token expires and while the user is away.
|
|
13
13
|
|
|
14
|
+
For the confidential Node.js sample, always select `me:read`. Add `offline_access` only if you want to exercise its optional refresh branch; the rest of the walkthrough does not require it.
|
|
15
|
+
|
|
14
16
|
Scopes cannot be changed after registration. While you are developing your application, creating a replacement client is inexpensive, so let the scope set evolve as you learn what the application needs. Aim to have the application's core functionality—and its required permissions—settled before you submit the client for review.
|
|
15
17
|
|
|
16
18
|
If functionality added after launch needs a scope that is not already registered, you must register a replacement client with the new complete scope set and have it reviewed before rollout. Existing authorizations do not transfer to the replacement client, so users must authorize it before they can use the new functionality. Treat that reauthorization as part of the feature launch: explain what the application can now do with their Lunch Money data and invite them to authorize the replacement client when they want to enable it.
|
|
@@ -49,4 +51,6 @@ A confidential web client can have multiple active secrets. This allows you to i
|
|
|
49
51
|
|
|
50
52
|
You now have the client settings needed to connect your application to Lunch Money.
|
|
51
53
|
|
|
54
|
+
To exercise those settings with a focused confidential-client implementation, [try OAuth with the Node.js sample](/oauth/sample-applications#try-oauth-with-your-lunch-money-account).
|
|
55
|
+
|
|
52
56
|
Next: [Implement authorization](/oauth/authorization-code).
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# OAuth sample applications
|
|
2
|
+
|
|
3
|
+
Use a sample application to see Lunch Money OAuth working end to end before you adapt the flow to your own architecture. Samples are separated by OAuth client type and platform because confidential and native clients have different credential boundaries.
|
|
4
|
+
|
|
5
|
+
> [!TIP] Open-source sample
|
|
6
|
+
> [View the confidential Node.js sample on GitHub](https://github.com/lunch-money/lm-oauth-confidential-node-example) or begin with its [README](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/README.md).
|
|
7
|
+
|
|
8
|
+
## Confidential Node.js and TypeScript sample
|
|
9
|
+
|
|
10
|
+
`lm-oauth-confidential-node-example` is a teaching sample for a **confidential, server-side web client**. It uses Node.js, TypeScript, and the maintained [`openid-client`](https://github.com/panva/openid-client) library to demonstrate:
|
|
11
|
+
|
|
12
|
+
- configuring a registered Lunch Money OAuth client and discovering authorization-server metadata;
|
|
13
|
+
- starting authorization with single-use `state` and S256 Proof Key for Code Exchange (PKCE);
|
|
14
|
+
- validating the callback and exchanging the code on the server;
|
|
15
|
+
- calling `GET /v2/me` with a server-held access token;
|
|
16
|
+
- optionally refreshing and atomically replacing the credential set for a client registered with `offline_access`;
|
|
17
|
+
- revoking the access token and verifying that Lunch Money rejects it; and
|
|
18
|
+
- resetting only the sample's local state so the flow can be repeated.
|
|
19
|
+
|
|
20
|
+
> [!WARNING] Teaching sample, not a production application
|
|
21
|
+
> The sample uses a fixed demonstration user and in-memory stores. It does not provide real application authentication, durable multi-user isolation, production credential storage, deployment configuration, or operational controls. Review its [security guidance](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/SECURITY.md) and [production checklist](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/PRODUCTION_CHECKLIST.md) before adapting any part of it.
|
|
22
|
+
|
|
23
|
+
<!-- BEGIN GENERATED OAUTH SAMPLE WALKTHROUGH -->
|
|
24
|
+
<!-- Source: lunch-money/lm-oauth-confidential-node-example@fbf03343847032ef256994758adf397218d63f74:docs/WALKTHROUGH.md -->
|
|
25
|
+
## Try OAuth with your Lunch Money account
|
|
26
|
+
|
|
27
|
+
Use this walkthrough to authorize a real Lunch Money OAuth client, call the Lunch Money API, optionally refresh its credentials, revoke access, and repeat the flow. The sample is a confidential, server-side web client built with Node.js and TypeScript.
|
|
28
|
+
|
|
29
|
+
[`openid-client`](https://github.com/panva/openid-client) is the recommended and supported third-party Node.js library for working with Lunch Money's OAuth interfaces. The sample uses it for discovery, authorization, callback validation, token exchange, refresh, and revocation while keeping every credential on the server.
|
|
30
|
+
|
|
31
|
+
> [!IMPORTANT]
|
|
32
|
+
> This is a real OAuth flow. You will create a real client, authorize access to a real Lunch Money budgeting account, receive real credentials, and call the real Lunch Money API. Only the automated tests use mocked Lunch Money responses.
|
|
33
|
+
|
|
34
|
+
### Before you begin
|
|
35
|
+
|
|
36
|
+
You need:
|
|
37
|
+
|
|
38
|
+
- a Lunch Money account;
|
|
39
|
+
- Node.js 20 or newer;
|
|
40
|
+
- npm 11.6.2; and
|
|
41
|
+
- Git.
|
|
42
|
+
|
|
43
|
+
While a client is in development, only the Lunch Money user who created it can authorize it. Use that same Lunch Money user when the sample sends you through authorization. Other Lunch Money users can authorize the client only after it has been [reviewed and approved](https://lunchmoney.dev/oauth/review-and-approval).
|
|
44
|
+
|
|
45
|
+
Three actions and identities are involved:
|
|
46
|
+
|
|
47
|
+
- **A real Lunch Money user creates the client.** You create a real confidential OAuth client in the Developer Portal using your Lunch Money account. While the client is in development, only that same Lunch Money user can authorize it.
|
|
48
|
+
- **That same Lunch Money user authorizes the client.** When you run the sample, you sign into Lunch Money, select one of your real budgeting accounts, and grant the client real access. The sample receives real tokens and uses them to call the real Lunch Money API.
|
|
49
|
+
- **Only the sample application's user is simulated.** Most confidential web applications have their own users and login system. They must associate each Lunch Money authorization and its credentials with the correct application user. To demonstrate that boundary without building an unrelated login system, the sample uses a fixed internal identity named `local-demo-user`. A production application replaces it with an application-specific ID obtained from its authenticated server-side session. The ID should identify the user without using an email address or other personally identifiable information.
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
Your real Lunch Money account
|
|
53
|
+
creates and owns the OAuth client
|
|
54
|
+
authorizes access to a real budgeting account
|
|
55
|
+
↓
|
|
56
|
+
Sample OAuth callback
|
|
57
|
+
↓
|
|
58
|
+
Credentials stored under local-demo-user
|
|
59
|
+
(the stand-in for your application's authenticated user)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The sample uses the client you register in the steps below; it does not create or modify that client. Client management remains in the Developer Portal. The automated tests use mocked HTTP responses, but the running sample connects to the configured real Lunch Money authorization and API services.
|
|
63
|
+
|
|
64
|
+
### 1. Download and install the sample
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
git clone https://github.com/lunch-money/lm-oauth-confidential-node-example.git
|
|
68
|
+
cd lm-oauth-confidential-node-example
|
|
69
|
+
npm ci
|
|
70
|
+
npm run build
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Register a confidential client
|
|
74
|
+
|
|
75
|
+
In the Lunch Money Developer Portal, create a **Confidential web client** with:
|
|
76
|
+
|
|
77
|
+
- the exact redirect URI `http://localhost:4002/oauth/callback`;
|
|
78
|
+
- the required `me:read` scope;
|
|
79
|
+
- optionally, `offline_access` if you also want to exercise refresh; and
|
|
80
|
+
- a client secret that you save securely when Lunch Money displays it.
|
|
81
|
+
|
|
82
|
+
Lunch Money uses the complete scope set registered for the client. The sample therefore does not send a `scope` parameter during authorization.
|
|
83
|
+
|
|
84
|
+
Registered scopes cannot be changed. Decide whether you want to test refresh before creating the client: adding `offline_access` later requires a replacement client registered with both `me:read` and `offline_access`, followed by a new authorization.
|
|
85
|
+
|
|
86
|
+
You do not need `offline_access` to authorize the client, call `/v2/me`, revoke access, or authorize again.
|
|
87
|
+
|
|
88
|
+
### 3. Configure the local process
|
|
89
|
+
|
|
90
|
+
In the same terminal, set the client ID and secret from the Developer Portal:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
export OAUTH_CLIENT_ID='YOUR_CLIENT_ID'
|
|
94
|
+
export OAUTH_CLIENT_SECRET='YOUR_CLIENT_SECRET'
|
|
95
|
+
export OAUTH_REDIRECT_URI='http://localhost:4002/oauth/callback'
|
|
96
|
+
export LUNCH_MONEY_API_BASE_URL='https://api-alpha.lunchmoney.dev/'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The sample uses `LUNCH_MONEY_API_BASE_URL` for OAuth discovery, token operations, and Lunch Money API requests. A real application should also use [OAuth discovery](https://lunchmoney.dev/oauth/authorization-code#configure-lunch-money-with-discovery) instead of hard-coding authorization, token, and revocation endpoint URLs. `PORT` is optional and defaults to `4002`.
|
|
100
|
+
|
|
101
|
+
`SESSION_SECRET` is also optional for this local sample. If it is absent, the process creates a new random cookie-signing secret when it starts. Because all browser sessions, authorization attempts, and credentials are held in memory and cleared on restart, the generated secret can be cleared at the same time. A production application must instead provide a strong `SESSION_SECRET`, keep it stable across restarts, and not rotate it for each OAuth authorization.
|
|
102
|
+
|
|
103
|
+
> [!WARNING]
|
|
104
|
+
> **Keep credentials private**
|
|
105
|
+
> Keep the client secret in a private local environment or secret manager. Never paste it into documentation, AI chats or prompts, committed files, screenshots, browser code, logs, support requests, or commands retained in shared shell history.
|
|
106
|
+
|
|
107
|
+
### 4. Start the sample
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
npm run dev
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Open [http://localhost:4002](http://localhost:4002) in your browser.
|
|
114
|
+
|
|
115
|
+
If a form reports **Invalid CSRF token**, reload the page and try again. This commonly happens when the development server restarts and clears its in-memory browser session while an older page remains open. Simply leaving the unchanged running sample open does not expire the form token.
|
|
116
|
+
|
|
117
|
+
### 5. Authorize and call `/v2/me`
|
|
118
|
+
|
|
119
|
+
1. Choose **Connect Lunch Money**.
|
|
120
|
+
2. Sign into Lunch Money as the user who created the development client.
|
|
121
|
+
3. Select one of that user's budgeting accounts and approve access.
|
|
122
|
+
4. After the browser returns to `http://localhost:4002/oauth/callback`, choose **Call /v2/me**.
|
|
123
|
+
|
|
124
|
+
The sample calls `GET /v2/me` from its Node.js server using the server-held access token. It validates the response against the documented `userObject` schema and displays the returned profile fields. The access token is never sent to the browser.
|
|
125
|
+
|
|
126
|
+
Open Lunch Money's [Connected Apps](https://my.lunchmoney.app/connected-apps) page in another tab, keeping the sample available so you can return to it. The client you just authorized should appear with the details you registered. If you plan to exercise refresh, return to the sample without revoking access yet; revocation ends this authorization's continuing access.
|
|
127
|
+
|
|
128
|
+
### 6. Optionally refresh access
|
|
129
|
+
|
|
130
|
+
If you registered the client with `offline_access`, choose **Refresh access token**, then choose **Call /v2/me** again.
|
|
131
|
+
|
|
132
|
+
Lunch Money replaces both the access token and refresh token after a successful refresh. The sample stores the complete replacement credential set on the server before reporting success. Calling `/v2/me` again confirms that it is using the replacement access token.
|
|
133
|
+
|
|
134
|
+
If you registered only `me:read`, Lunch Money does not issue a refresh token and the refresh button does not appear. Skip this step.
|
|
135
|
+
|
|
136
|
+
The sample allows an immediate refresh so you can observe the flow. A production application normally decides when to refresh using expiration information from the token response or after an API authentication failure.
|
|
137
|
+
|
|
138
|
+
### 7. Revoke and verify access
|
|
139
|
+
|
|
140
|
+
Choose **Revoke and verify**.
|
|
141
|
+
|
|
142
|
+
- If the connection has a refresh token, the sample revokes it to revoke the entire grant.
|
|
143
|
+
- If the connection has only an access token, the sample revokes that access token.
|
|
144
|
+
|
|
145
|
+
The sample then calls `/v2/me` with the old access token, requires Lunch Money to return `401`, and deletes its locally stored credential. This verification demonstrates that the old access token no longer works.
|
|
146
|
+
|
|
147
|
+
To repeat the flow, choose **Connect Lunch Money** and authorize the client again.
|
|
148
|
+
|
|
149
|
+
### 8. Try user-initiated revocation
|
|
150
|
+
|
|
151
|
+
To see what happens when a Lunch Money user disconnects the client:
|
|
152
|
+
|
|
153
|
+
1. Authorize the sample again.
|
|
154
|
+
2. Open [Connected Apps](https://my.lunchmoney.app/connected-apps). Keep the sample available in another tab so you can return to it after revoking the authorization.
|
|
155
|
+
3. Revoke the client's access there.
|
|
156
|
+
4. Return to the sample and choose **Call /v2/me**.
|
|
157
|
+
|
|
158
|
+
The request should fail because Lunch Money no longer accepts the stored access token. Choose **Local reset only** to remove the now-unusable local credential and browser session before starting again.
|
|
159
|
+
|
|
160
|
+
### Local reset is not revocation
|
|
161
|
+
|
|
162
|
+
**Local reset only** clears the sample's browser session and locally stored credential. It does not contact Lunch Money and does not revoke active access.
|
|
163
|
+
|
|
164
|
+
When access is still active, revoke it first through **Revoke and verify** or Lunch Money's Connected Apps page. Use local reset by itself only when the remote authorization has already been revoked or you deliberately want to clear this disposable local demonstration.
|
|
165
|
+
|
|
166
|
+
### Why `localhost` works
|
|
167
|
+
|
|
168
|
+
> [!NOTE]
|
|
169
|
+
> Lunch Money redirects your browser to the registered callback, and your browser connects to the sample running on your computer. Lunch Money's server does not initiate a connection to `localhost`, so this walkthrough does not require a public deployment or tunnel.
|
|
170
|
+
|
|
171
|
+
### Keep the sample local
|
|
172
|
+
|
|
173
|
+
> [!WARNING]
|
|
174
|
+
> The sample uses a fixed application identity and keeps browser sessions, authorization attempts, credentials, and refresh coordination in memory. Run it locally and do not expose it as a public application. Use a test budgeting account when practical. Restarting the sample clears its in-memory state.
|
|
175
|
+
|
|
176
|
+
After the flow succeeds, connect each action you performed to the code that implemented it, then decide how those responsibilities fit into your own application.
|
|
177
|
+
|
|
178
|
+
Before adapting the code, read the repository's [OAuth code guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/fbf03343847032ef256994758adf397218d63f74/src/oauth/README.md), [security model](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/fbf03343847032ef256994758adf397218d63f74/SECURITY.md), [production checklist](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/fbf03343847032ef256994758adf397218d63f74/PRODUCTION_CHECKLIST.md), and [troubleshooting guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/fbf03343847032ef256994758adf397218d63f74/TROUBLESHOOTING.md).
|
|
179
|
+
<!-- END GENERATED OAUTH SAMPLE WALKTHROUGH -->
|
|
180
|
+
|
|
181
|
+
## Understand and apply the teaching code
|
|
182
|
+
|
|
183
|
+
The open-source sample you just ran is designed to make the Lunch Money integration code easy to find, understand, and adapt. The modules under `src/oauth` show the OAuth responsibilities that a confidential server-side application needs: discovery, authorization attempts, callback validation, credential ownership and storage, API access, optional refresh, revocation, and safe error handling.
|
|
184
|
+
|
|
185
|
+
Treat these modules as a working reference, not as a package to copy unchanged into production. The in-memory implementation demonstrates serialized refresh and complete credential replacement for one `applicationUserId` and `connectionId`, but it does not provide durable encrypted storage, database transactions, distributed per-connection locking, failed-persistence recovery across restarts, multi-instance coordination, or tenant isolation. The sample keeps those application-specific decisions outside the teaching code so you can see exactly where your implementation must provide them.
|
|
186
|
+
|
|
187
|
+
When refresh succeeds, the sample stores the replacement access token, refresh token, scope, and expiration metadata together without displaying token values. Your refresh-capable application must preserve that boundary, serialize refreshes for each application user and connection, and atomically store the complete rotated credential set. Retry transient failures according to your application's policy, but treat `invalid_grant` as terminal: stop refreshing, discard unusable credentials, and require reauthorization. Disconnect must invalidate the refresh token as well as the current access token so continuing access cannot resume.
|
|
188
|
+
|
|
189
|
+
### Follow the flow through the source
|
|
190
|
+
|
|
191
|
+
Start with the [`src/oauth` guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/src/oauth/README.md). It provides the maintained reading order for the framework-independent OAuth modules and connects each file to the stage of the flow it implements.
|
|
192
|
+
|
|
193
|
+
The OAuth teaching code is isolated under `src/oauth`. Hono, sessions, cookies, pages, and other runnable presentation code live under `src/scaffolding`, so you can replace the framework without obscuring the protocol flow. The sample's [architecture guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/ARCHITECTURE.md) explains this boundary, and its [OAuth tests](https://github.com/lunch-money/lm-oauth-confidential-node-example/tree/main/tests/oauth) mirror the recommended reading order, including focused [refresh tests](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/tests/oauth/refresh.test.ts). If your result differs, compare the stage that failed with the sample's [troubleshooting guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/TROUBLESHOOTING.md) and the public [OAuth troubleshooting guide](/oauth/troubleshooting).
|
|
194
|
+
|
|
195
|
+
## Native applications
|
|
196
|
+
|
|
197
|
+
A separate native/public-client sample is planned but is not yet available. Native mobile and desktop applications use PKCE **without a client secret** and store tokens in platform secure storage. Follow the [native application guidance](/oauth/native-apps); do not adapt the confidential sample by placing its client secret or server-held credentials in installed application code.
|
|
198
|
+
|
|
199
|
+
Next: [Develop and test your OAuth application](/oauth/development) and [operate it securely](/oauth/security).
|
package/docs/oauth/security.md
CHANGED
|
@@ -22,6 +22,16 @@ In the browser-based model, the short-lived access token is also visible to the
|
|
|
22
22
|
|
|
23
23
|
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 budgeting account.
|
|
24
24
|
|
|
25
|
+
## Associate credentials with your application user
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
Store the verified credential set under a stable internal application-user ID. If one application user may connect more than one Lunch Money context, also assign each connection its own stable internal connection ID. These identifiers belong to your application; do not use an email address, callback parameter, form field, or any other browser-supplied value as the credential owner.
|
|
30
|
+
|
|
31
|
+
Enforce the same ownership boundary every time your backend reads credentials, replaces them after authorization or refresh, revokes them, or deletes them. Durable credentials must be encrypted at rest, and backend access should be limited to the services and operators that need it. Never return refresh tokens or client secrets to browser JavaScript.
|
|
32
|
+
|
|
33
|
+
The [confidential Node.js sample](/oauth/sample-applications#understand-and-apply-the-teaching-code) makes this boundary explicit in its [`CredentialStore`](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/src/oauth/tokens.ts) abstraction. Its [security guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/SECURITY.md) explains the threat model, and its [production checklist](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/PRODUCTION_CHECKLIST.md) 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.
|
|
34
|
+
|
|
25
35
|
## Secure authorization and callbacks
|
|
26
36
|
|
|
27
37
|
- Generate unpredictable, session-bound, single-use `state` and verify it before exchanging a code.
|
|
@@ -37,7 +47,7 @@ Choose only the resource scopes needed for expected features. Add `offline_acces
|
|
|
37
47
|
|
|
38
48
|
## Operate credentials safely
|
|
39
49
|
|
|
40
|
-
Rotate client secrets on a schedule appropriate for your application's risk and immediately when one may have been exposed. 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.
|
|
50
|
+
Rotate client secrets on a schedule appropriate for your application's risk and immediately when one may have been exposed. 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 logout or disconnect invalidate continuing refresh access as well as clear local state.
|
|
41
51
|
|
|
42
52
|
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.
|
|
43
53
|
|
package/docs/oauth/tokens.md
CHANGED
|
@@ -14,9 +14,11 @@ Access tokens are deliberately short-lived. Record when the token response arriv
|
|
|
14
14
|
|
|
15
15
|
A refresh token is issued after a successful authorization-code exchange only when the registered client includes `offline_access`. It lasts longer than an access token, but it is not permanent.
|
|
16
16
|
|
|
17
|
+
The [confidential Node.js sample](/oauth/sample-applications#try-oauth-with-your-lunch-money-account) keeps refresh optional within its first-run walkthrough. Register the sample client with `offline_access` to exercise the refresh branch, or omit it to focus on authorization, `GET /v2/me`, revocation, and reauthorization.
|
|
18
|
+
|
|
17
19
|
Send a form-encoded `grant_type=refresh_token` request to the discovered token endpoint. Confidential clients authenticate with `client_secret_basic`; public clients send their client ID without a secret. A refresh never expands or switches the grant's registered scopes.
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
Atomically persist the complete replacement token set before discarding the previous value, and serialize refreshes for each application-user connection. Lunch Money rotates refresh tokens; replay of a consumed token can invalidate the token family. Treat transient failures according to a bounded retry policy, but treat `invalid_grant` as terminal: delete unusable credentials and start a fresh authorization.
|
|
20
22
|
|
|
21
23
|
## Revoke access
|
|
22
24
|
|
|
@@ -55,6 +57,8 @@ Do not paste a real client secret or token directly into a command where it may
|
|
|
55
57
|
|
|
56
58
|
Revocation is different from expiration: expiration ends one credential naturally, while revocation deliberately removes access. Deleting or disabling an application and loss of user access can also make tokens unusable.
|
|
57
59
|
|
|
60
|
+
A Lunch Money user can also revoke an application's access from the [Connected Apps page](https://my.lunchmoney.app/connected-apps). Your application should treat the resulting authentication failure as terminal and ask the user to authorize again when they want to reconnect.
|
|
61
|
+
|
|
58
62
|
## Recovery rules
|
|
59
63
|
|
|
60
64
|
- A rejected API request may mean the access token expired, was revoked, lacks the required scope, targets the wrong resource, or no longer maps to an accessible account. The `401` response does not identify which cause applies. Follow the [API failure decision tree](/oauth/troubleshooting#api-failures).
|
|
@@ -8,6 +8,8 @@ OAuth problems are usually easiest to resolve once you identify where the flow s
|
|
|
8
8
|
|
|
9
9
|
When collecting diagnostic details, preserve nonsensitive information such as timestamps and error names, but leave credentials and full callback URLs out of logs and support messages.
|
|
10
10
|
|
|
11
|
+
For a runnable comparison, [try OAuth with the confidential Node.js sample](/oauth/sample-applications#try-oauth-with-your-lunch-money-account). Reproduce the same stage in its end-to-end flow, then compare the result with the sample repository's [troubleshooting guide](https://github.com/lunch-money/lm-oauth-confidential-node-example/blob/main/TROUBLESHOOTING.md) without copying credentials or full callback URLs.
|
|
12
|
+
|
|
11
13
|
> [!NOTE] Two error formats
|
|
12
14
|
> OAuth protocol endpoints under `/oauth/` use lower-case OAuth error names such as `invalid_grant`. The v2 API uses the Lunch Money response format, with a `message` and an `errors` array whose entries contain `errMsg`.
|
|
13
15
|
|
package/manifest.json
CHANGED
|
@@ -105,6 +105,14 @@
|
|
|
105
105
|
"type": "markdown",
|
|
106
106
|
"aliases": ["/v2/oauth/authorization-code"]
|
|
107
107
|
},
|
|
108
|
+
{
|
|
109
|
+
"path": "/oauth/sample-applications",
|
|
110
|
+
"file": "docs/oauth/sample-applications.md",
|
|
111
|
+
"title": "OAuth Sample Applications",
|
|
112
|
+
"section": "OAUTH",
|
|
113
|
+
"type": "markdown",
|
|
114
|
+
"aliases": ["/v2/oauth/sample-applications"]
|
|
115
|
+
},
|
|
108
116
|
{
|
|
109
117
|
"path": "/oauth/development",
|
|
110
118
|
"file": "docs/oauth/development.md",
|
|
@@ -304,6 +312,7 @@
|
|
|
304
312
|
{ "label": "Concepts", "path": "/oauth/concepts" },
|
|
305
313
|
{ "label": "Register a Client", "path": "/oauth/register-client" },
|
|
306
314
|
{ "label": "Implement Authorization", "path": "/oauth/authorization-code" },
|
|
315
|
+
{ "label": "Sample Applications", "path": "/oauth/sample-applications" },
|
|
307
316
|
{ "label": "Develop and Test", "path": "/oauth/development" },
|
|
308
317
|
{ "label": "Token Lifecycle", "path": "/oauth/tokens" },
|
|
309
318
|
{ "label": "Security", "path": "/oauth/security" },
|
package/package.json
CHANGED