@lunch-money/developer-docs 2.11.2 → 2.11.3-preview.1
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/README.md +13 -1
- package/docs/branding-your-app.md +1 -1
- package/docs/getting-started.md +21 -17
- package/docs/introduction.md +3 -2
- package/docs/oauth/authorization-code.md +110 -0
- package/docs/oauth/concepts.md +116 -0
- package/docs/oauth/development.md +73 -0
- package/docs/oauth/index.md +33 -0
- package/docs/oauth/native-apps.md +89 -0
- package/docs/oauth/oauth-scope-catalog-design.md +370 -0
- package/docs/oauth/register-client.md +64 -0
- package/docs/oauth/review-and-approval.md +84 -0
- package/docs/oauth/sample-applications.md +263 -0
- package/docs/oauth/scopes.md +104 -0
- package/docs/oauth/security.md +78 -0
- package/docs/oauth/tokens.md +83 -0
- package/docs/oauth/troubleshooting.md +159 -0
- package/docs/rate-limiting.md +6 -0
- package/docs/using-with-ai.md +2 -2
- package/manifest.json +110 -0
- package/package.json +3 -3
- package/v2/docs/intro-to-v2.md +1 -1
- package/v2/docs/version-history.md +7 -0
- package/v2/images/oauth-authorization-flow.svg +35 -0
- package/v2/images/oauth-token-lifecycle.svg +14 -0
- package/v2/spec/AGENTS.md +6 -0
- package/v2/spec/lunch-money-api-v2.yaml +1081 -1733
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# OAuth for native iOS and Android apps
|
|
2
|
+
|
|
3
|
+
Native apps are public clients: they cannot keep a client secret. Use authorization code with PKCE and launch Lunch Money sign-in and consent in a trusted platform authentication browser—not an embedded WebView.
|
|
4
|
+
|
|
5
|
+
Lunch Money requires PKCE with the `S256` challenge method for every client, public or confidential. Native clients send no client secret.
|
|
6
|
+
|
|
7
|
+
## Recommended libraries
|
|
8
|
+
|
|
9
|
+
- iOS and macOS: [`AppAuth-iOS`](https://github.com/openid/AppAuth-iOS), presented through `ASWebAuthenticationSession`.
|
|
10
|
+
- Android: [`AppAuth-Android`](https://github.com/openid/AppAuth-Android), which uses a browser Custom Tab.
|
|
11
|
+
- Bare or non-Expo React Native: [`react-native-app-auth`](https://github.com/FormidableLabs/react-native-app-auth).
|
|
12
|
+
- Expo: Use [`expo-auth-session`](https://docs.expo.dev/versions/latest/sdk/auth-session/) for browser-based OAuth flows.
|
|
13
|
+
|
|
14
|
+
Use Lunch Money's authorization-server discovery document and configure `token_endpoint_auth_method=none`. Send the client ID at the token and revocation endpoints, but never invent or embed a client secret. You do not need to configure authorization-request scopes because the registered client receives its complete fixed set. If your OAuth library sends `scope`, Lunch Money ignores that value and uses the registered set.
|
|
15
|
+
|
|
16
|
+
## Redirects
|
|
17
|
+
|
|
18
|
+
Use a redirect mechanism that returns control to your app and that your platform can bind to it. Claimed HTTPS redirects or Android App Links provide stronger app ownership than a custom URI scheme when correctly configured. A private-use scheme must contain a dot, such as `app.example.demo:/oauth/callback`, and must be protected against interception.
|
|
19
|
+
|
|
20
|
+
Lunch Money accepts a well-formed non-loopback HTTPS redirect URI but does not fetch or validate an Apple `apple-app-site-association` file or Android `assetlinks.json` file. Platform association remains your responsibility:
|
|
21
|
+
|
|
22
|
+
- For AppAuth-iOS HTTPS redirects, use iOS 17.4 or later, add an Associated Domains entitlement for the redirect host with the `webcredentials` service, and publish an `apple-app-site-association` file that lists the app under `webcredentials`. An `applinks` association by itself is not sufficient for AppAuth-iOS.
|
|
23
|
+
- For Android App Links, add an HTTPS intent filter with `android:autoVerify="true"` for the redirect host and publish `https://HOST/.well-known/assetlinks.json` with the app package name and signing-certificate fingerprint.
|
|
24
|
+
|
|
25
|
+
Test the installed application with the production-signed association before release. Successful client registration proves only that the redirect URI is syntactically eligible; it does not prove that the operating system will return the authorization response to your app.
|
|
26
|
+
|
|
27
|
+
Native clients may use an HTTP loopback redirect with `localhost`, `127.0.0.1`, or `[::1]`. The scheme, host, path, and query string must exactly match the registered URI, but the port may differ so the application can bind an ephemeral port at runtime. The exception applies only to loopback redirects for native clients; native HTTPS and private-use scheme redirects must match every component of the registered URI exactly. The loopback hosts are not interchangeable: a client registered with `http://127.0.0.1/cb` cannot authorize with `http://localhost:4000/cb` or `http://[::1]:4000/cb`, so register the host your app actually binds to.
|
|
28
|
+
|
|
29
|
+
## Store and renew tokens
|
|
30
|
+
|
|
31
|
+
Store tokens in Keychain on Apple platforms. If an app and its extensions both refresh, place the complete credential set in a Keychain access group they can share. On Android, use current Keystore-backed storage guidance from your maintained library; do not start new work with deprecated `EncryptedSharedPreferences` APIs.
|
|
32
|
+
|
|
33
|
+
Serialize refreshes with a cross-process lock for each connection. After acquiring the lock, re-read the stored credential set because another process may already have refreshed it. Persist the complete replacement set before using the new access token or releasing the lock. If a successful refresh response is lost before it is saved, do not retry with the consumed refresh token: replay revokes the token family, so the user must authorize again.
|
|
34
|
+
|
|
35
|
+
After every code exchange, call and strictly validate `GET /v2/me` before adding the credentials to the app's normal connection store. For an app without its own user accounts, identify each local connection by the returned Lunch Money user `id` and budget-account `account_id` pair. Atomically create a record for a new pair or replace the credential set for an existing pair. Two Lunch Money users may authorize the same shared `account_id`; their credentials must remain in separate records. If identification or persistence fails, discard the temporary credentials instead of saving a partial or unidentified connection. Retain `budget_name` for display, but never use it as a key because different budget accounts can share a name.
|
|
36
|
+
|
|
37
|
+
The platform authentication browser may reuse an existing Lunch Money login, and Lunch Money may let the person use a different user from the authorization screen. An ephemeral-session request is only an intent to avoid shared browser state; it does not prove which user completed authorization. Do not infer identity from browser cookies, callback fields, budget account names, or token contents. The validated `/v2/me` response is authoritative.
|
|
38
|
+
|
|
39
|
+
Show the active Lunch Money identity when the app may retain more than one, show which authorized budget account is active, and provide one separate **Authorize another budget account** action that starts OAuth. Do not require another client-side action for switching Lunch Money users. Selecting an existing connection must not start authorization again, and the app must allow selection only among saved budget accounts for the active Lunch Money user.
|
|
40
|
+
|
|
41
|
+
All API calls, refreshes, revocations, local deletions, and cached data must apply to the active user/account connection. Clear or partition profile, configuration, and cached financial data when the identity or budget account changes. When the active connection is removed, choose a deterministic remaining connection for that Lunch Money user or enter a disconnected state; never expose another user's hidden connection as a fallback. Use a secondary label such as the account ID only when duplicate visible budget account names need disambiguation.
|
|
42
|
+
|
|
43
|
+
The app may retain credentials for inactive Lunch Money users in secure storage. Switching users hides the previous identity's connections without revoking or deleting them, and authorizing as that identity later may restore its saved budget accounts. Alternatively, an app may delete credentials during its own sign-out or user-switch lifecycle. Make the chosen behavior understandable without prescribing a particular native control.
|
|
44
|
+
|
|
45
|
+
Present remote disconnect or revocation separately from forgetting a credential only on the device. Confirm local-only deletion and explain that it leaves the remote authorization active.
|
|
46
|
+
|
|
47
|
+
### Keep application users' connections separate
|
|
48
|
+
|
|
49
|
+
A native app can support multiple application users on the same device. If your app has its own sign-in system, keep each user's saved Lunch Money connections in a separate storage namespace. Bind the authorization attempt to the application user who started it, and retain the Lunch Money `id` and `account_id` returned by `GET /v2/me` with each credential set. Only load a connection after verifying that it belongs to the currently authenticated application user. Secure device storage does not perform that application-level ownership check for you.
|
|
50
|
+
|
|
51
|
+
On application sign-out or application-user switching, stop work using the previous application user's credentials and clear in-memory tokens, the active connection selection, and cached financial data. Remove saved credentials if your app does not retain connections across sign-ins; otherwise, make them available only after their application owner signs in again. Do not attach an OAuth callback or refresh result from the previous session to the newly signed-in application user.
|
|
52
|
+
|
|
53
|
+
If your app has no separate user accounts, the device's OS account and app storage protections provide the local access boundary. Anyone allowed to use the unlocked app may be able to use its saved connections; separate budget-account records alone do not authenticate different people.
|
|
54
|
+
|
|
55
|
+
Sending tokens to an application backend introduces server-side credential storage. If your architecture does that, apply the [credential ownership rules](/oauth/security#associate-credentials-with-your-application-user) on the backend as well as protecting credentials on the device.
|
|
56
|
+
|
|
57
|
+
Platform code should delegate protocol validation to the maintained library while your application supplies safe configuration, lifecycle storage, API calls, revocation, and user-facing recovery.
|
|
58
|
+
|
|
59
|
+
## AppAuth-iOS outline
|
|
60
|
+
|
|
61
|
+
Use AppAuth-iOS to discover Lunch Money's authorization-server metadata from `https://api.lunchmoney.dev`, then create an `OIDAuthorizationRequest` with the discovered configuration, your client ID, no client secret, the registered redirect URI, and the authorization-code response type. AppAuth-iOS presents the request with `ASWebAuthenticationSession`, generates an `S256` PKCE challenge, handles the callback, and exchanges the code when you use its authorization-state convenience flow.
|
|
62
|
+
|
|
63
|
+
The following outline is illustrative; adapt ownership, error handling, and persistence to your application:
|
|
64
|
+
|
|
65
|
+
```swift
|
|
66
|
+
let issuer = URL(string: "https://api.lunchmoney.dev")!
|
|
67
|
+
|
|
68
|
+
OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in
|
|
69
|
+
guard let configuration else { return handle(error) }
|
|
70
|
+
let request = OIDAuthorizationRequest(
|
|
71
|
+
configuration: configuration,
|
|
72
|
+
clientId: clientID,
|
|
73
|
+
clientSecret: nil,
|
|
74
|
+
scopes: nil,
|
|
75
|
+
redirectURL: redirectURL,
|
|
76
|
+
responseType: OIDResponseTypeCode,
|
|
77
|
+
additionalParameters: nil
|
|
78
|
+
)
|
|
79
|
+
authorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: viewController) { state, error in
|
|
80
|
+
// Temporarily hold state, then validate GET /v2/me before saving it.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
After exchange, call `GET /v2/me` with the access token and strictly validate the Lunch Money user and budget-account identity before saving the credential set. For refresh, acquire the connection's cross-process lock, re-read the Keychain access-group record, ask `OIDAuthState` for a fresh access token only if the stored state still needs refresh, save the complete replacement state, and then make the API request. Treat a terminal refresh failure as a request for interactive authorization.
|
|
86
|
+
|
|
87
|
+
This is integration guidance, not a complete Swift sample. The upstream AppAuth-iOS project remains the source for current library APIs and platform setup. Expo developers can use the [native Expo sample](/oauth/sample-applications#native-expo-sample) for a complete Lunch Money walkthrough and reference implementation.
|
|
88
|
+
|
|
89
|
+
Next: [Review OAuth security guidance](/oauth/security).
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# OAuth scope catalog design proposal
|
|
2
|
+
|
|
3
|
+
Status: first-cut ENG-616 implementation and inventory, pending consumer review.
|
|
4
|
+
|
|
5
|
+
This document proposes a published, shared source of truth for OAuth scope
|
|
6
|
+
definitions and scope-group presets published with the canonical V2 OpenAPI
|
|
7
|
+
specification. It must still be reconciled with the server OAuth management API
|
|
8
|
+
and database implementation before the catalog is finalized.
|
|
9
|
+
|
|
10
|
+
## Context and decisions
|
|
11
|
+
|
|
12
|
+
The catalog is application code, not persistence. OAuth clients, grants, and
|
|
13
|
+
tokens store scope-name strings. Server, Developer Portal, and authorization UI
|
|
14
|
+
reconcile those strings with their installed catalog version.
|
|
15
|
+
|
|
16
|
+
The detailed OAuth technical specification currently contains an exploratory
|
|
17
|
+
read/write scope list and says write implies read. Those points are superseded
|
|
18
|
+
for this proposal: the final inventory will use independent `:read`, `:create`,
|
|
19
|
+
`:update`, and `:delete` authorities where CRUD applies, and no authority implies
|
|
20
|
+
another. Presets express common combinations without changing authorization
|
|
21
|
+
semantics. Names such as `resource:read` in this document are generic examples,
|
|
22
|
+
not production decisions.
|
|
23
|
+
|
|
24
|
+
This proposal makes these additional decisions:
|
|
25
|
+
|
|
26
|
+
- Keep `OAuthScopeCategory` as `identity | offline | api`. The category
|
|
27
|
+
distinguishes protocol scopes from API permissions. The published inventory
|
|
28
|
+
includes `offline_access` for requesting continued access through refresh
|
|
29
|
+
tokens, but does not publish OpenID Connect identity scopes (`openid`,
|
|
30
|
+
`email`, or `profile`).
|
|
31
|
+
- Add optional `lifecycleNotice` copy for deprecation and retirement warnings.
|
|
32
|
+
It is display metadata, not runtime authorization policy.
|
|
33
|
+
- Reject groups with identical member sets. This is simpler than display
|
|
34
|
+
priorities and makes exact matching unambiguous.
|
|
35
|
+
- Allow unmapped public operations during rollout, but require callers to make
|
|
36
|
+
the choice explicit through an option such as
|
|
37
|
+
`operationIdCoverage: 'allow-unmapped'`. Validation should switch to
|
|
38
|
+
`require-all-mapped` before OAuth scope enforcement is declared complete.
|
|
39
|
+
- Do not encode scope implication, expansion, grant rules, client approval,
|
|
40
|
+
refresh-token policy, or consent policy in the catalog.
|
|
41
|
+
- Use catalog `operationIds` as the authority for V2 endpoint permissions.
|
|
42
|
+
Server decorators must be generated from or validated against this mapping so
|
|
43
|
+
they cannot become a second source of truth.
|
|
44
|
+
- Scope V1 of this catalog to the V2 API only.
|
|
45
|
+
- Map non-CRUD mutations by effect: Plaid and synced-crypto refresh operations
|
|
46
|
+
use `:update`; transaction split/group and their reversals use
|
|
47
|
+
`transactions:update`; balance-history and budget upserts use `:update`.
|
|
48
|
+
- Keep attachment metadata and download access behind
|
|
49
|
+
`transaction_attachments:read`. The other `transaction_attachments:*` scopes
|
|
50
|
+
separately authorize attaching or deleting files.
|
|
51
|
+
|
|
52
|
+
## Package direction
|
|
53
|
+
|
|
54
|
+
The catalog ships as a browser-safe subpath of the existing
|
|
55
|
+
`@lunch-money/v2-api-spec` package so the catalog and V2 `operationId` values are
|
|
56
|
+
versioned atomically:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
src/oauth-scopes/ authored TypeScript catalog and helpers
|
|
60
|
+
oauth-scopes/ compiled published subpath
|
|
61
|
+
test/oauth-scopes/ catalog and V2 operationId validation
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The runtime entry point has no Node-only dependencies and performs no filesystem
|
|
65
|
+
or YAML work. OpenAPI parsing remains in build/test tooling and is not exported
|
|
66
|
+
from the browser-safe `@lunch-money/v2-api-spec/oauth-scopes` subpath.
|
|
67
|
+
|
|
68
|
+
## Public contract
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
type OAuthScopeName = string
|
|
72
|
+
type OAuthScopeCategory = 'identity' | 'offline' | 'api'
|
|
73
|
+
type OAuthScopeLifecycle = 'active' | 'deprecated' | 'retired'
|
|
74
|
+
type KnownOAuthScopeName = 'me:read' | /* ... */ 'budgets:delete'
|
|
75
|
+
|
|
76
|
+
interface OAuthScopeDefinition {
|
|
77
|
+
readonly name: KnownOAuthScopeName
|
|
78
|
+
readonly description: string
|
|
79
|
+
readonly category: OAuthScopeCategory
|
|
80
|
+
readonly operationIds: readonly string[]
|
|
81
|
+
readonly status: OAuthScopeLifecycle
|
|
82
|
+
readonly lifecycleNotice?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface OAuthScopeGroupDefinition {
|
|
86
|
+
readonly id: string
|
|
87
|
+
readonly name: string
|
|
88
|
+
readonly description: string
|
|
89
|
+
readonly scopes: readonly KnownOAuthScopeName[]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface OAuthCatalogDefinition {
|
|
93
|
+
readonly scopes: readonly OAuthScopeDefinition[]
|
|
94
|
+
readonly groups: readonly OAuthScopeGroupDefinition[]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `oauth-scopes` subpath exports:
|
|
99
|
+
|
|
100
|
+
- The interfaces and aliases above, plus result and issue types.
|
|
101
|
+
- `validateOAuthCatalog(definition, options)` returns all structured issues.
|
|
102
|
+
- `getOAuthScopeDefinition(name)` returns a definition or `undefined`.
|
|
103
|
+
- `getOAuthScopeForOperationId(operationId)` returns the authoritative scope
|
|
104
|
+
definition for a V2 operation or `undefined`.
|
|
105
|
+
- `getOAuthScopeGroup(id)` returns a group or `undefined`.
|
|
106
|
+
- `matchOAuthScopeGroup(names)` returns a discriminated matched/no-match
|
|
107
|
+
result.
|
|
108
|
+
- Immutable `oauthCatalog`, `oauthScopes`, and `oauthScopeGroups` exports.
|
|
109
|
+
|
|
110
|
+
`OAuthScopeName` remains `string` for persisted and API-facing input so consumers
|
|
111
|
+
can preserve names newer than their installed library. `KnownOAuthScopeName`
|
|
112
|
+
provides autocomplete and compile-time checking for this catalog version.
|
|
113
|
+
|
|
114
|
+
## Runtime validation and immutability
|
|
115
|
+
|
|
116
|
+
The published definitions and nested arrays are frozen. Private lookup maps
|
|
117
|
+
support explicit failed lookups, and `validateOAuthCatalog` returns structured
|
|
118
|
+
issues for build/test tooling without importing Node-only code into the runtime.
|
|
119
|
+
|
|
120
|
+
Validation checks:
|
|
121
|
+
|
|
122
|
+
- stable lowercase colon-delimited scope names and lowercase underscore group IDs;
|
|
123
|
+
- required descriptions and valid category/lifecycle values;
|
|
124
|
+
- unique scope names and group IDs;
|
|
125
|
+
- non-empty API `operationIds`, except retired tombstones;
|
|
126
|
+
- no operation IDs on identity or offline scopes;
|
|
127
|
+
- duplicate operation IDs within a scope;
|
|
128
|
+
- conflicting operation ownership unless an ID is deliberately allowlisted;
|
|
129
|
+
- non-empty groups with unique members;
|
|
130
|
+
- group members exist and are not retired;
|
|
131
|
+
- offline scopes are excluded from groups;
|
|
132
|
+
- duplicate group member sets are rejected.
|
|
133
|
+
|
|
134
|
+
The runtime has no mutable singleton. Consumers import the frozen catalog or
|
|
135
|
+
construct catalog fixtures in tests. Public helpers return
|
|
136
|
+
`undefined` or discriminated results rather than using non-null assertions.
|
|
137
|
+
|
|
138
|
+
## Protocol scopes
|
|
139
|
+
|
|
140
|
+
`OAuthScopeCategory` includes `identity` and `offline` so validation can
|
|
141
|
+
distinguish protocol scopes from API permissions. The published catalog ships
|
|
142
|
+
`offline_access` alongside its API scopes.
|
|
143
|
+
|
|
144
|
+
Lunch Money's authorization server exposes OAuth 2.1 authorization-server
|
|
145
|
+
metadata, API scopes, and the `offline_access` protocol scope:
|
|
146
|
+
|
|
147
|
+
- no `openid`, `email`, or `profile` identity scopes;
|
|
148
|
+
- active `offline_access` with no V2 operation IDs;
|
|
149
|
+
- no ID tokens, UserInfo, or public signing keys.
|
|
150
|
+
|
|
151
|
+
`offline_access` represents a request for continued access when the user is not
|
|
152
|
+
actively using the app. The authorization server still owns the decision to
|
|
153
|
+
issue a refresh token based on the requested scope, client capability, consent,
|
|
154
|
+
and server policy.
|
|
155
|
+
|
|
156
|
+
No resource group may contain an offline-category scope. Identity scopes are
|
|
157
|
+
not categorically banned from a group; any future identity-oriented preset
|
|
158
|
+
should be an explicit catalog decision.
|
|
159
|
+
|
|
160
|
+
## Lifecycle and consumer behavior
|
|
161
|
+
|
|
162
|
+
Lifecycle is code-only metadata:
|
|
163
|
+
|
|
164
|
+
| Status | New assignment/request | Existing full replacement | Token issuance | Recognition/display |
|
|
165
|
+
| --- | --- | --- | --- | --- |
|
|
166
|
+
| `active` | allowed | allowed | allowed | yes |
|
|
167
|
+
| `deprecated` | rejected if newly introduced | preserved if already assigned | allowed for existing grants per server rollout | yes, with warning |
|
|
168
|
+
| `retired` | rejected | rejected | rejected | yes, as tombstone |
|
|
169
|
+
|
|
170
|
+
The library provides state and copy; consumers enforce transitions in context.
|
|
171
|
+
For example, only the server can know whether a deprecated name is newly added
|
|
172
|
+
or preserved during a full replacement. Lifecycle must never be copied into a
|
|
173
|
+
client row. A stored name absent from the installed catalog is `unknown`, not
|
|
174
|
+
implicitly retired.
|
|
175
|
+
|
|
176
|
+
Recommended reconciliation status for management APIs:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
type ScopeConfigurationStatus = 'valid' | 'needs_attention'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`needs_attention` applies when stored configuration contains an unknown or
|
|
183
|
+
retired scope. A deprecated-only configuration remains valid but returns warning
|
|
184
|
+
metadata. The exact API response shape is a server-design coordination point.
|
|
185
|
+
|
|
186
|
+
## Compatibility baseline and change classes
|
|
187
|
+
|
|
188
|
+
After the first production catalog is approved, check in a canonical JSON
|
|
189
|
+
snapshot at `compatibility/catalog-baseline.json`. Future CI compares source
|
|
190
|
+
against this file and does not fetch npm. The baseline updates only as part of a
|
|
191
|
+
reviewed release and must represent the most recently published authority map.
|
|
192
|
+
Canonical ordering makes diffs stable.
|
|
193
|
+
|
|
194
|
+
Compatibility checks detect deletion, category changes/name reuse, authority
|
|
195
|
+
broadening, operation remapping, lifecycle changes, and group membership/removal.
|
|
196
|
+
The regular catalog validator separately detects broken references and duplicate
|
|
197
|
+
ownership.
|
|
198
|
+
|
|
199
|
+
Change classification:
|
|
200
|
+
|
|
201
|
+
- Patch-compatible: descriptions, names shown to users, and lifecycle notice
|
|
202
|
+
copy change without changing authority.
|
|
203
|
+
- Minor-compatible: a new scope or group is added. New scopes must not be added
|
|
204
|
+
to an existing group in the same classification; that is a membership change.
|
|
205
|
+
- Migration-requiring: deprecation, retirement, removal of authority from a
|
|
206
|
+
scope, any group membership change, or group removal.
|
|
207
|
+
- Breaking/forbidden without explicit migration: deleting a known scope rather
|
|
208
|
+
than retaining a tombstone, changing its category, reusing its name for a
|
|
209
|
+
different authority, or adding operation IDs to an existing scope.
|
|
210
|
+
|
|
211
|
+
Name reuse cannot be detected from names alone. Preserving every old definition
|
|
212
|
+
as a retired tombstone plus comparing category and operation ownership provides
|
|
213
|
+
the deterministic safeguard. Review policy must treat meaning changes not
|
|
214
|
+
visible in structured data as forbidden. A future `authorityFingerprint` could
|
|
215
|
+
make that stronger if V1 endpoints or non-operation authorities join the model.
|
|
216
|
+
|
|
217
|
+
A future comparison helper should treat a retired tombstone with removed
|
|
218
|
+
operation IDs as migration-requiring rather than breaking. Tombstones should
|
|
219
|
+
remain small while retaining enough description to diagnose old persisted names.
|
|
220
|
+
|
|
221
|
+
## Exact-set group matching
|
|
222
|
+
|
|
223
|
+
Groups are UI presets only. A selected group becomes its concrete member names;
|
|
224
|
+
the group ID is neither sent as authority nor persisted in client, grant, code,
|
|
225
|
+
or token configuration.
|
|
226
|
+
|
|
227
|
+
Matching performs these steps:
|
|
228
|
+
|
|
229
|
+
1. Reject a candidate containing an unknown or duplicate scope name with a
|
|
230
|
+
distinct no-match reason.
|
|
231
|
+
2. Canonically sort the complete candidate set.
|
|
232
|
+
3. Compare it with each complete canonical group member set.
|
|
233
|
+
4. Return exactly one matched group or `no_match`.
|
|
234
|
+
|
|
235
|
+
An additional recognized scope prevents a match. The helper never searches for
|
|
236
|
+
subsets, decomposes a set into overlapping groups, or infers historical preset
|
|
237
|
+
selection. A changed preset causes old clients to fall back to individual scope
|
|
238
|
+
display. Duplicate member sets are invalid, so ambiguity cannot enter a valid
|
|
239
|
+
catalog.
|
|
240
|
+
|
|
241
|
+
Product analytics may separately record that a preset was selected, but that
|
|
242
|
+
event is historical telemetry and has no authorization meaning.
|
|
243
|
+
|
|
244
|
+
## OpenAPI operationId validation
|
|
245
|
+
|
|
246
|
+
The V2 YAML is authoritative for public operation IDs. Node-only test tooling
|
|
247
|
+
parses `v2/spec/lunch-money-api-v2.yaml`, traverses HTTP operations, and returns a set
|
|
248
|
+
while rejecting missing or duplicate IDs. Catalog validation then:
|
|
249
|
+
|
|
250
|
+
- verifies every API-category operation ID exists in the spec;
|
|
251
|
+
- rejects conflicting ownership unless a deliberate `sharedOperationIds`
|
|
252
|
+
allowlist contains the ID;
|
|
253
|
+
- permits identity/offline scopes to have no operation IDs;
|
|
254
|
+
- reports stale and unknown IDs with catalog paths;
|
|
255
|
+
- optionally reports every unmapped public operation.
|
|
256
|
+
|
|
257
|
+
The validation modes are:
|
|
258
|
+
|
|
259
|
+
- `allow-unmapped`: rollout mode; catalog entries must be correct, but endpoints
|
|
260
|
+
can remain unmapped while enforcement is staged.
|
|
261
|
+
- `require-all-mapped`: completion mode; every public operation must have an
|
|
262
|
+
owner or be removed from the public spec.
|
|
263
|
+
|
|
264
|
+
If some public operations are intentionally unscoped (for example a protocol or
|
|
265
|
+
health endpoint represented in the same document), add a named exclusion list
|
|
266
|
+
with a justification per entry rather than silently omitting them. Shared
|
|
267
|
+
operation ownership should likewise be rare and explicit. The tooling never
|
|
268
|
+
modifies the OpenAPI document.
|
|
269
|
+
|
|
270
|
+
V1 endpoints are deliberately outside this catalog. Do not overload V2
|
|
271
|
+
operation IDs with invented V1 values.
|
|
272
|
+
|
|
273
|
+
## Consumer integration
|
|
274
|
+
|
|
275
|
+
### Server
|
|
276
|
+
|
|
277
|
+
- Pin an exact catalog package version in deploys.
|
|
278
|
+
- Persist only scope-name strings.
|
|
279
|
+
- Validate requested/client/grant scopes against the installed catalog.
|
|
280
|
+
- Reject newly introduced deprecated scopes and all retired/unknown scopes.
|
|
281
|
+
- Preserve deprecated scopes on an existing client's full replacement only
|
|
282
|
+
when they were already present.
|
|
283
|
+
- Enforce operation authority from the catalog without implication.
|
|
284
|
+
- Treat `offline_access` as a request for refresh-token-backed access while
|
|
285
|
+
retaining the server-owned issuance decision based on client capability and
|
|
286
|
+
consent policy.
|
|
287
|
+
- Return unknown names so operators and UIs can diagnose drift.
|
|
288
|
+
|
|
289
|
+
### Developer Portal
|
|
290
|
+
|
|
291
|
+
- Render active definitions and groups from the package.
|
|
292
|
+
- Render deprecated existing selections with `lifecycleNotice`; do not offer
|
|
293
|
+
them for new selection.
|
|
294
|
+
- Render retired/unknown stored names as needing attention.
|
|
295
|
+
- Submit concrete scope names, never group IDs.
|
|
296
|
+
- Use exact matching only as a display convenience.
|
|
297
|
+
|
|
298
|
+
### Authorization/consent UI
|
|
299
|
+
|
|
300
|
+
- Resolve the server-approved requested names using the same pinned catalog
|
|
301
|
+
compatibility range.
|
|
302
|
+
- Display groups only on exact match; otherwise display individual definitions.
|
|
303
|
+
- Call out API mutation scopes from structured category and scope data, while
|
|
304
|
+
keeping the actual consent/issuance decision server-owned.
|
|
305
|
+
- Fail closed on unknown or retired requested names.
|
|
306
|
+
|
|
307
|
+
Consumers must not independently copy definitions or group membership. During a
|
|
308
|
+
rolling deploy, the server is authoritative: it must not send a scope newly
|
|
309
|
+
introduced by a catalog version that the UI deployment cannot understand. Pinning
|
|
310
|
+
one exact version across a coordinated release is preferred.
|
|
311
|
+
|
|
312
|
+
## Versioning and publishing
|
|
313
|
+
|
|
314
|
+
Publish the catalog with `@lunch-money/v2-api-spec`. A spec-package version pins
|
|
315
|
+
both the YAML and its catalog, preventing operation mappings from drifting.
|
|
316
|
+
Use the change classes above when selecting the package version:
|
|
317
|
+
|
|
318
|
+
- patch for copy-only and implementation fixes;
|
|
319
|
+
- minor for additive catalog entries or backward-compatible API additions;
|
|
320
|
+
- major only with an approved migration for a public contract break;
|
|
321
|
+
- migration-requiring catalog changes may still use minor versions before 1.0,
|
|
322
|
+
but release notes and server coordination are mandatory.
|
|
323
|
+
|
|
324
|
+
Start with prereleases until all three consumers validate the contract. Existing
|
|
325
|
+
spec-package release tooling builds the subpath and verifies package contents
|
|
326
|
+
before publishing.
|
|
327
|
+
|
|
328
|
+
Each server release should pin an exact package version. Portal and consent UI
|
|
329
|
+
should use the same version or a declared compatibility window validated in CI.
|
|
330
|
+
Never depend on an npm `latest` lookup at application startup.
|
|
331
|
+
|
|
332
|
+
## Reconciliation required with the server design
|
|
333
|
+
|
|
334
|
+
Before this proposal becomes final, compare these exact artifacts:
|
|
335
|
+
|
|
336
|
+
1. Scope string grammar, especially resource separators and full CRUD verbs.
|
|
337
|
+
2. `identity | offline | api` categories and `active | deprecated | retired`
|
|
338
|
+
lifecycle values.
|
|
339
|
+
3. Group schema and the decision to reject duplicate member sets.
|
|
340
|
+
4. The discriminated exact-match result and unknown-scope handling.
|
|
341
|
+
5. `scope_configuration_status` semantics, warning/error details, and whether
|
|
342
|
+
deprecated-only configuration remains `valid`.
|
|
343
|
+
6. Management API request/response shapes: concrete scope names only, with no
|
|
344
|
+
persisted group authority.
|
|
345
|
+
7. Full-replacement rules for preserving deprecated scopes.
|
|
346
|
+
8. `offline_access` request and refresh-token issuance semantics, and
|
|
347
|
+
confirmation that OIDC identity scopes are rejected.
|
|
348
|
+
9. How server `tsoa` operation requirements consume catalog mappings without
|
|
349
|
+
creating a second source of truth.
|
|
350
|
+
10. Exact package versions supported by server, Portal, and consent deployments,
|
|
351
|
+
including rolling-deploy behavior.
|
|
352
|
+
11. The server database's representation of unknown names and its mapping to
|
|
353
|
+
`valid | needs_attention`.
|
|
354
|
+
|
|
355
|
+
The server OpenAPI management schemas should remain string-based so adding a
|
|
356
|
+
catalog name does not require an API schema revision.
|
|
357
|
+
|
|
358
|
+
## Recommended ENG-616 follow-up
|
|
359
|
+
|
|
360
|
+
1. Reconcile this proposal with the server agent's OpenAPI and database artifacts.
|
|
361
|
+
2. Confirm the V2 operation coverage/exclusion policy.
|
|
362
|
+
3. Review and approve the first-cut full-CRUD inventory and descriptions; do not
|
|
363
|
+
derive authority from the older read/write draft.
|
|
364
|
+
4. Check in the first canonical compatibility baseline with that inventory.
|
|
365
|
+
5. Add CI that builds/tests the spec package and validates the production catalog
|
|
366
|
+
against the local V2 YAML in the chosen coverage mode.
|
|
367
|
+
6. Select a prerelease version, verify the tarball, and test it through local
|
|
368
|
+
`file:` or packed dependencies in all consumers.
|
|
369
|
+
7. Publish only after server, Portal, and consent UI agree on the contract and
|
|
370
|
+
rolling-version policy.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Register an OAuth client
|
|
2
|
+
|
|
3
|
+
Before you add OAuth to your application code, register an OAuth client with Lunch Money. The client identifies your application during authorization and tells Lunch Money who is requesting access, where to return the user, and which scopes the application needs.
|
|
4
|
+
|
|
5
|
+
[Register a new OAuth client](/oauth/applications/new) in the Developer Portal when you are ready to begin.
|
|
6
|
+
|
|
7
|
+
> [!NOTE] Development access is owner-only
|
|
8
|
+
> A newly registered client starts in development. Only the Lunch Money user who owns it can authorize it until the client has been reviewed and approved.
|
|
9
|
+
|
|
10
|
+
## Plan the scopes
|
|
11
|
+
|
|
12
|
+
List the Lunch Money features your application will provide, then use the [OAuth scopes](/oauth/scopes) page to choose the smallest complete set of scopes 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
|
+
|
|
14
|
+
If your application may support more than one Lunch Money user or budget account, always include `me:read`. After authorization, `GET /v2/me` lets your application record both the Lunch Money user's `id` and the selected budget account's `account_id`, then store the connection under the complete application-owner and Lunch Money user/account identity. The same scope is also required when your application only needs to identify one connected budget account.
|
|
15
|
+
|
|
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 scopes—settled before you submit the client for review.
|
|
17
|
+
|
|
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.
|
|
19
|
+
|
|
20
|
+
## Choose the client type
|
|
21
|
+
|
|
22
|
+
Select the type based on whether your application code can keep a client secret secure:
|
|
23
|
+
|
|
24
|
+
- **Confidential web client:** Choose this when a trusted server can store a client secret, including for applications that are not traditional websites but use a centralized server.
|
|
25
|
+
- **Native or public client:** Choose this for standalone applications installed on a user-controlled device. These clients use PKCE without a client secret. Review the [native application guidance](/oauth/native-apps) before choosing redirects for this type.
|
|
26
|
+
|
|
27
|
+
The client type cannot be changed after registration. See [OAuth concepts](/oauth/concepts#client-types-and-credentials) for more detail about the two types.
|
|
28
|
+
|
|
29
|
+
## Register redirect URIs
|
|
30
|
+
|
|
31
|
+
A redirect URI tells Lunch Money where to return the user after authorization. Register every callback address your application will use. Confidential web clients must match every component of a registered URI, including its scheme, host, port, path, and query string. Native-client loopback redirects use the same exact matching except that the ephemeral port is ignored.
|
|
32
|
+
|
|
33
|
+
Web applications normally use an HTTPS callback. If you need a callback for local development, review the [loopback guidance](/oauth/development#local-loopback-callbacks) before registering one. Mobile and desktop applications may use a private-use scheme containing a dot, such as `app.example.demo:/oauth/callback`, or a non-loopback HTTPS link. Lunch Money checks that the HTTPS URI is well formed but does not verify an Apple or Android association file when you register it. Configure the URI as a claimed HTTPS redirect on the target platform before relying on it; see the [native redirect guidance](/oauth/native-apps#redirects).
|
|
34
|
+
|
|
35
|
+
After the client is approved, you can add or replace redirect URIs in the Developer Portal without submitting another initial review. An approved confidential web client must keep a production HTTPS callback. See [after approval](/oauth/review-and-approval#after-approval).
|
|
36
|
+
|
|
37
|
+
Each client can have up to 10 redirect URIs, and each URI can be up to 2,048 characters. If you reach the limit, remove a callback the application no longer uses before adding another.
|
|
38
|
+
|
|
39
|
+
## Register the OAuth client
|
|
40
|
+
|
|
41
|
+
Open the [new OAuth client form](/oauth/applications/new) in the Developer Portal. Then:
|
|
42
|
+
|
|
43
|
+
1. Describe the application so Lunch Money users can understand who built it and what it does.
|
|
44
|
+
2. Provide a monitored support email where users can reach you if authorization fails.
|
|
45
|
+
3. Choose its client type.
|
|
46
|
+
4. Add at least one redirect URI.
|
|
47
|
+
5. Select the planned scopes.
|
|
48
|
+
6. Accept the current Lunch Money API Terms of Use and select **Register OAuth client**.
|
|
49
|
+
|
|
50
|
+
You can own up to 25 non-deleted OAuth clients. If you reach the limit, delete a client you no longer use before registering another.
|
|
51
|
+
|
|
52
|
+
The support email must be confirmed before you can submit the client for review. If it matches your own verified Lunch Money account email, Lunch Money confirms it automatically. For any other address—including one used by another Lunch Money account—Lunch Money sends a confirmation link from `support@lunchmoney.app` to that mailbox. The link is valid for 72 hours, so use an address you can access and follow the link promptly.
|
|
53
|
+
|
|
54
|
+
After registration, record the client ID in your application configuration. A client ID identifies the registration but is not a secret.
|
|
55
|
+
|
|
56
|
+
If you registered a **Confidential web client**, create a client secret from its details page. Lunch Money shows the secret value only once. Copy it immediately into a secret manager or protected server configuration; it cannot be retrieved later. Never commit it or expose it in browser or mobile code.
|
|
57
|
+
|
|
58
|
+
A confidential web client can have up to three non-revoked secrets. This allows you to introduce a replacement secret, update and verify the deployed application, and then revoke the old secret without interrupting authorization. Expired secrets still count toward the limit until you revoke them. Revoking an obsolete secret frees a slot, and revoked secrets no longer appear in the client's secret list. See [OAuth security guidance](/oauth/security#operate-credentials-safely) when planning ongoing rotation.
|
|
59
|
+
|
|
60
|
+
You now have the client settings needed to connect your application to Lunch Money.
|
|
61
|
+
|
|
62
|
+
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).
|
|
63
|
+
|
|
64
|
+
Next: [Implement authorization](/oauth/authorization-code).
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# OAuth client review and approval
|
|
2
|
+
|
|
3
|
+
Every new OAuth client starts in development, where only its owner can authorize it. This gives you time to build and test the application without making an unfinished integration available to other Lunch Money users.
|
|
4
|
+
|
|
5
|
+
When the application is ready, submit its client for review in the Developer Portal. A Lunch Money employee reviews the application information, requested scopes, redirect configuration, and other readiness requirements. Approval moves the client to `active`, allowing other Lunch Money users to authorize the application.
|
|
6
|
+
|
|
7
|
+
## Lifecycle states
|
|
8
|
+
|
|
9
|
+
| State | What it means | Owner action |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `development` | The client has not been submitted for review; only its owner can authorize it | Configure and test, then request review. |
|
|
12
|
+
| `pending_review` | Review has been requested and is waiting for or receiving review; review-critical fields are frozen | Continue owner testing or cancel the request. |
|
|
13
|
+
| `active` | The review was approved; other Lunch Money users may authorize the application | Keep configuration current; identity updates reach users after Lunch Money reviews them. |
|
|
14
|
+
| `rejected` | Changes were requested | Read feedback, revise editable configuration, and resubmit. |
|
|
15
|
+
| `disabled` | Lunch Money disabled the client; its owner is notified, authorization is unavailable, and existing grants no longer work | Review the reason provided and contact developer support when appropriate. |
|
|
16
|
+
|
|
17
|
+
Deleted clients disappear from the owner's Developer Portal.
|
|
18
|
+
|
|
19
|
+
## Prepare for review
|
|
20
|
+
|
|
21
|
+
Submitting a client for review is how you make your application authorizable by Lunch Money users other than yourself. Registration already requires basic information, including a support email, but review requires a complete, user-facing application profile and production-ready configuration.
|
|
22
|
+
|
|
23
|
+
Before requesting review, make sure the client has:
|
|
24
|
+
|
|
25
|
+
- an application description, developer name, and logo so users can identify the application and who created it;
|
|
26
|
+
- a confirmed, monitored support email where users and Lunch Money can reach you;
|
|
27
|
+
- a publicly accessible homepage URL where users can learn about the application;
|
|
28
|
+
- a publicly accessible privacy policy URL explaining how the application handles user data;
|
|
29
|
+
- at least one eligible redirect URI—a confidential web client needs a non-loopback production HTTPS redirect, while a native client may use a private-use scheme or loopback redirect;
|
|
30
|
+
- a recognized, supported scope set and an explanation of why the application needs each scope; and
|
|
31
|
+
- acceptance of the current Lunch Money API Terms of Use.
|
|
32
|
+
|
|
33
|
+
The privacy policy must be available at a direct, stable public URL and explain how the application collects, uses, stores, shares, and deletes user data. A published privacy-policy page in a public source-code repository can satisfy this requirement. An in-app page that requires installation or sign-in, or a link to source code without a clear privacy policy, does not.
|
|
34
|
+
|
|
35
|
+
Fully test the client before submission. Testing by the client owner is sufficient; submission does not require beta-tester coverage. A confidential web client also needs at least one active, non-expired secret; a native or public client does not.
|
|
36
|
+
|
|
37
|
+
Explain which features use each requested scope, how that access benefits users, and why narrower scopes are insufficient. If the client includes `offline_access`, explain why the integration must operate while the user is absent. You may also provide documentation, source-code, and demonstration URLs or context about changes made after earlier feedback.
|
|
38
|
+
|
|
39
|
+
Open-source applications are welcome. If your source code is public, include the repository URL in the review request's additional context. It can help the reviewer understand how your application uses Lunch Money data and the scopes it requests. Publishing source code is optional and is not required for approval.
|
|
40
|
+
|
|
41
|
+
A confidential web client may retain owner-only loopback callbacks alongside its production HTTPS callback, but it still needs a non-loopback production HTTPS redirect before review.
|
|
42
|
+
|
|
43
|
+
Confirm the support email from the mailbox before you submit the client. The Developer Portal's readiness checklist shows whether it is confirmed and prevents submission until it is. If you change the address before approval, Lunch Money clears its confirmation; confirm the new address before the client can be submitted or approved.
|
|
44
|
+
|
|
45
|
+
### Native applications
|
|
46
|
+
|
|
47
|
+
You do not need to create a standalone marketing website for a native application. Its public Apple App Store or Google Play listing can serve as the homepage if it clearly explains what the application does and identifies the developer. A public pre-release listing, project page, or README in a public source-code repository can also work.
|
|
48
|
+
|
|
49
|
+
Lunch Money can approve a native client before its public App Store or Google Play release when the reviewer can access a TestFlight, other beta, or pre-release build. Use the review request's additional context to explain how to access it. Include any required opt-in steps, supported platforms or devices, and other information the reviewer needs to evaluate the application. Do not include passwords, client secrets, tokens, or other credentials.
|
|
50
|
+
|
|
51
|
+
> [!NOTE] Prepare for Apple App Review
|
|
52
|
+
> If the iOS app requires Lunch Money authorization, provide Apple with a dedicated test Lunch Money account in the App Store Connect review information rather than a personal account. Make sure the account and its test budget account remain available throughout review. Two-factor authentication can prevent the reviewer from signing in unless you also provide a usable authentication code or another review-ready access path in the review notes.
|
|
53
|
+
|
|
54
|
+
Native clients may be reviewed with loopback or private-use scheme redirects.
|
|
55
|
+
|
|
56
|
+
## Submit, cancel, or revise
|
|
57
|
+
|
|
58
|
+
Submitting the request moves the client to `pending_review` and temporarily freezes review-critical fields. Reviews are usually completed within a couple of business days. You can continue developing and testing the application as the client owner while you wait. If you need to change a frozen field, cancel the request to return the client to `development`, make the change, and submit it again.
|
|
59
|
+
|
|
60
|
+
You can make up to five initial review submissions across all your clients in a rolling 24-hour period. Cancelled submissions and submissions made again after a rejection still count. Deleting a client does not remove its submissions from the window. Reviews opened automatically after you update the identity of an active client do not count toward this limit.
|
|
61
|
+
|
|
62
|
+
If you reach the limit, the Developer Portal tells you when you can submit again. Wait until that time before retrying; cancelling, deleting, or replacing a client does not reset the window.
|
|
63
|
+
|
|
64
|
+
Lunch Money emails you when the review is complete. You can also check the current status and review history on the client's page in the Developer Portal.
|
|
65
|
+
|
|
66
|
+
## If review is denied
|
|
67
|
+
|
|
68
|
+
If the review is denied, the Developer Portal shows feedback explaining what needs to change. Update the client's editable configuration and submit a new review request. Editing the client alone does not resubmit it or clear the `rejected` status.
|
|
69
|
+
|
|
70
|
+
There is no review comment thread, so use the new request's additional context to explain how you addressed the feedback. The Portal keeps the earlier request in the client's review history.
|
|
71
|
+
|
|
72
|
+
## After approval
|
|
73
|
+
|
|
74
|
+
An `active` client can be authorized by other users. Keep its configuration current in the Developer Portal. You do not need to contact Lunch Money to rename the application, move hosting, or update a support address.
|
|
75
|
+
|
|
76
|
+
Changes save immediately, and the client stays `active`. Redirect URIs, description, and developer name take effect as soon as you save them. An approved confidential web client must keep at least one public HTTPS redirect.
|
|
77
|
+
|
|
78
|
+
Name, logo, support email, homepage URL, and privacy policy URL also save immediately, but Lunch Money users continue to see the last approved identity on the consent screen and Connected Apps page until Lunch Money reviews the update. The Developer Portal shows that as an identity update in review; it is not a return to `rejected`, and existing authorizations keep working.
|
|
79
|
+
|
|
80
|
+
When you change the support email, confirm the new address before Lunch Money can approve the identity update. Until it is confirmed and approved, users continue to see the previous approved address, and Lunch Money does not send notifications to the new address. Lunch Money tells the previously confirmed support address that the client was changed.
|
|
81
|
+
|
|
82
|
+
Client-secret rotation and revocation remain available subject to lifecycle and security checks. Scopes and client type cannot be changed after registration. A scope change requires a replacement client and user reauthorization.
|
|
83
|
+
|
|
84
|
+
Next: [Review the security checklist](/oauth/security).
|