@mcp-abap-adt/auth-broker 4.0.0 → 5.0.0
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/CHANGELOG.md +273 -1
- package/README.md +1028 -908
- package/dist/AuthBroker.d.ts +353 -99
- package/dist/AuthBroker.d.ts.map +1 -1
- package/dist/AuthBroker.js +806 -258
- package/dist/DestinationConfigError.d.ts +35 -6
- package/dist/DestinationConfigError.d.ts.map +1 -1
- package/dist/DestinationConfigError.js +51 -9
- package/dist/SessionWriter.d.ts +70 -33
- package/dist/SessionWriter.d.ts.map +1 -1
- package/dist/SessionWriter.js +118 -94
- package/dist/binding.d.ts +137 -64
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +330 -131
- package/dist/bindingOf.d.ts +33 -14
- package/dist/bindingOf.d.ts.map +1 -1
- package/dist/bindingOf.js +50 -20
- package/dist/buildIdentity.d.ts +137 -0
- package/dist/buildIdentity.d.ts.map +1 -0
- package/dist/buildIdentity.js +393 -0
- package/dist/clientAuthentication.d.ts +124 -0
- package/dist/clientAuthentication.d.ts.map +1 -0
- package/dist/clientAuthentication.js +218 -0
- package/dist/contractShape.d.ts +24 -0
- package/dist/contractShape.d.ts.map +1 -0
- package/dist/contractShape.js +24 -0
- package/dist/destinations.d.ts +174 -53
- package/dist/destinations.d.ts.map +1 -1
- package/dist/destinations.js +460 -150
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/quietLogger.d.ts +24 -0
- package/dist/quietLogger.d.ts.map +1 -0
- package/dist/quietLogger.js +63 -0
- package/package.json +9 -7
package/CHANGELOG.md
CHANGED
|
@@ -9,7 +9,279 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
Thank you to all contributors! See [CONTRIBUTORS.md](../../CONTRIBUTORS.md) for the complete list.
|
|
11
11
|
|
|
12
|
-
## [
|
|
12
|
+
## [5.0.0] - 2026-10-09
|
|
13
|
+
|
|
14
|
+
The broker on the auth chain's 6.0 contracts (auth-providers 6, interfaces-auth 7, auth-errors
|
|
15
|
+
2). A major: what every failure is changes class, two options become required for token
|
|
16
|
+
destinations, the session binding gains a versioned record, and every wait can be cancelled.
|
|
17
|
+
Released with `@mcp-abap-adt/auth-broker-cli` 3.0.0. What a 4.x consumer must do, row by row:
|
|
18
|
+
the README's *Migrating to 5.0.0*.
|
|
19
|
+
|
|
20
|
+
### Breaking
|
|
21
|
+
|
|
22
|
+
- **`renewal` is required for every token row.** `AuthBrokerConfig.renewal` — `(destination,
|
|
23
|
+
grant: TokenGrant) => IRenewalStrategy` — is called once per build of every UAA, OIDC and
|
|
24
|
+
SAML row, after every other check and before the provider's constructor; its answer is the
|
|
25
|
+
provider's `renewal`, unchanged. No default: a token row without it is a
|
|
26
|
+
`DestinationConfigError` naming `renewal`, beside every other missing field. A `renewal` that
|
|
27
|
+
throws is `DestinationConfigError(['renewal'])`, "the renewal option failed", carrying what it
|
|
28
|
+
threw. Not called for `basic`, `snc`, `none`, nor for the token API's `provider`.
|
|
29
|
+
`() => refreshThenLogin()` is 4.x's behaviour.
|
|
30
|
+
- **`onWriteFailure: 'fail' | 'continue'` is required** for every destination that writes a
|
|
31
|
+
secret — a token row `getProvider` builds, and every call of the token API with a `provider`.
|
|
32
|
+
Without it: `DestinationConfigError` naming `onWriteFailure`. `'fail'`: the call whose write
|
|
33
|
+
did not land fails (`unknown`, `persisting-tokens`), and while the destination's last write is
|
|
34
|
+
pending (failed, queued or in flight) `getProvider`, `getToken` and `refreshToken` retry it on
|
|
35
|
+
entry and once more before success, and are refused while it fails. `'continue'`: logged, the
|
|
36
|
+
call goes on. A moment of a provider already handed out that commits nothing is not refused.
|
|
37
|
+
- **Failures are `AuthProviderFailure`s, read through `@mcp-abap-adt/auth-errors`.** A
|
|
38
|
+
provider's failure reaches `getToken()` / `refreshToken()` as the same object. The broker reads
|
|
39
|
+
a caught value only through `readFailure` / `isAuthProviderFailure` — no `instanceof`,
|
|
40
|
+
`message` or `name` of an error is read in `src`. The broker's own failures are minted by
|
|
41
|
+
auth-errors: an aborted wait (`interactive-login`, `aborted`), a write that did not land under
|
|
42
|
+
`'fail'` (`unknown`, `persisting-tokens`, an allowlisted `code`), a provider that answered no
|
|
43
|
+
token (`request-failed`, `token-source`, `no-access-token` — replacing "Token provider did not
|
|
44
|
+
return authorization token …").
|
|
45
|
+
- **`DestinationConfigError` carries the provider's failure.** New `error?: IAuthProviderError`
|
|
46
|
+
(kind, facts, rendered words, admitted diagnostics; no `cause`) when a provider's or a
|
|
47
|
+
strategy's failure caused the refusal; the message then ends `: <error.reason>`. New
|
|
48
|
+
`isDestinationConfigError(value)` — structural, no `instanceof`; true for a JSON copy. A
|
|
49
|
+
provider constructor that throws inside a build — any row, not only SNC — is a
|
|
50
|
+
`DestinationConfigError` naming the store fields its configuration facts name. The SNC row
|
|
51
|
+
decides by kind (`configuration` → its fields), not by `instanceof ValidationError`. The
|
|
52
|
+
broker's own copy of the three certificate phrases is gone: `clientAuthentication`'s refusal
|
|
53
|
+
reads `the clientAuthentication strategy failed: <reason>` with auth-providers'
|
|
54
|
+
`client-certificate` failure carried. New rows: `renewal`, `onWriteFailure`, `provider` (an
|
|
55
|
+
instance after the destination's identity changed), a store field of a shape the broker
|
|
56
|
+
cannot take, `samlIdpInitiated`, `oidcScopes` and `oidcActorTokenType` of the wrong shape.
|
|
57
|
+
- **`issuedBy` is a versioned binding record**: `mcp-abap-adt-binding/2;<row>;<eleven address
|
|
58
|
+
fields>;<trust>` — the row (`authType/grantType`, or `provider/…` for the token API's consumer
|
|
59
|
+
provider), the exact string of every server address the row hands its provider
|
|
60
|
+
(`encodeURIComponent`-encoded, never canonicalised: `clientId`, `uaaUrl`, `oidcIssuerUrl`, the
|
|
61
|
+
three OIDC endpoints, `oidcAudience`, `samlIdpSsoUrl`, `samlAcsUrl`, `samlTokenUrl`, the
|
|
62
|
+
certificate client's `certUrl`) and the SHA-256 of the row's non-secret trust input (SAML
|
|
63
|
+
trust, OIDC scopes, the password grant's user, the token-exchange token types, the certificate
|
|
64
|
+
client's public certificate). Compared by exact equality, never parsed; no secret and no hash
|
|
65
|
+
of one in it. `issuedFor` keeps 4.x's canonical form. **Every session written before 5.0.0
|
|
66
|
+
reads as unbound**: its stored token and refresh token are not used, and the provider's first
|
|
67
|
+
renewal is a login (or a token request). A `none` row compares `issuedBy` always and refuses a
|
|
68
|
+
4.x binding naming `issuedBy`. `bindingOf(means, client)` keeps its signature and returns the
|
|
69
|
+
version-2 record.
|
|
70
|
+
- **A stored secret seeds only a fully stated binding**: one whose record holds the client the
|
|
71
|
+
row authenticates and every address its provider sends a credential to — so an issuer-less
|
|
72
|
+
OIDC row with explicit endpoints and a client is now seeded, and `token_exchange` and the
|
|
73
|
+
token API's consumer provider never are.
|
|
74
|
+
- **A provider is never changed.** Every call of `getProvider` and the token API re-reads what
|
|
75
|
+
the destination's provider was built from (the means, the client, the certificate client when
|
|
76
|
+
read) and compares everything the build read, secrets included (in memory only); anything
|
|
77
|
+
changed builds a new provider that starts with nothing. 4.x cached a destination's provider
|
|
78
|
+
for the broker's life. An instance `provider` whose destination's identity changed is refused
|
|
79
|
+
until a new broker.
|
|
80
|
+
- **Two paths, resolved and cached apart.** The row path (`getProvider`, and the token API
|
|
81
|
+
without `provider`) and the consumer path (the token API with `provider`) each have their own
|
|
82
|
+
resolution, cache, identity and binding; only the destination's write queue is shared.
|
|
83
|
+
- **The token API's factory is handed no stored secret**: `authConfig` is the client
|
|
84
|
+
(`refreshToken` a key, never a value), `connConfig` the allowlisted connection means
|
|
85
|
+
(`serviceUrl`, `sapClient`, `language`, `authType`, `grantType`), the fourth argument the
|
|
86
|
+
strategy's answer and the client identity (`refreshToken` never set). The consumer path is
|
|
87
|
+
never seeded. A consumer provider's result without a refresh token writes `refreshToken: ''`
|
|
88
|
+
(the stored one is no longer carried).
|
|
89
|
+
- **Session writes state every field** (auth-stores 4 merges): a credential write states the
|
|
90
|
+
token or cookies, `expiresAt` (the provider's report), the refresh token the provider owns or
|
|
91
|
+
`''`, `issuedFor` (`''` when the means state no `serviceUrl`) and `issuedBy`; `saml2_pure`'s
|
|
92
|
+
cookies write `refreshToken: ''`; a refresh token discarded before any credential writes only
|
|
93
|
+
`refreshToken: ''`. A refresh token is written only by the provider that obtained it or was
|
|
94
|
+
seeded with it and has not discarded it since — never read from the store at write time.
|
|
95
|
+
- **The write queue has no retry timer.** One queue per destination: one write at a time, in
|
|
96
|
+
order; a replaced provider's late write is dropped once the new one has written; a failed
|
|
97
|
+
write stays pending until the destination's next write or `flush()`. The 1 s → 60 s retry
|
|
98
|
+
timer is gone. The store's contract, documented: `saveSession` settles.
|
|
99
|
+
- **`flush()` rejects with an `AggregateError` of `SessionWriteFailure`s** (`destination`,
|
|
100
|
+
`error`: the store's error classified `persisting-tokens`) instead of
|
|
101
|
+
`Error("<dest>": <class>)`; the message keeps 4.x's lead.
|
|
102
|
+
- **The built providers persist through auth-providers' `refreshStatePersistence`** (`onTokens`
|
|
103
|
+
is gone from every row), with the consumer's `onWriteFailure`.
|
|
104
|
+
- **`AuthBrokerConfig`'s optional fields are declared `?: T | undefined`.**
|
|
105
|
+
|
|
106
|
+
### Added
|
|
107
|
+
|
|
108
|
+
- **Cancellation.** `BrokerCallOptions { signal? }` on `getProvider`, `getToken`,
|
|
109
|
+
`refreshToken`, `flush` and `createTokenRefresher`. Every wait is a waiter of auth-errors'
|
|
110
|
+
`sharedAttempt`: an abort releases that caller alone (`aborted`), the work runs on; when every
|
|
111
|
+
caller of a build aborted, the build is not cached and writes nothing. `getProvider`'s signal
|
|
112
|
+
is attached to the token or SNC provider it answers, so a login that provider starts later is
|
|
113
|
+
aborted once every holder's signal aborted; the token API never attaches and passes its signal
|
|
114
|
+
to `getTokens({ signal })` / `refreshTokens({ signal })`. `ClientAuthenticationContext.signal`:
|
|
115
|
+
the build's attempt. The broker sets no timeout and has no timer.
|
|
116
|
+
- **`authDebug?: boolean`** — passed to every token provider the broker builds, on only for
|
|
117
|
+
`true`, never read from the environment.
|
|
118
|
+
- Exports: `isDestinationConfigError`, `DestinationConfigErrorLike`, `SessionWriteFailure`,
|
|
119
|
+
`BrokerCallOptions`, `TokenGrant`, and the re-exported `IRenewalStrategy`.
|
|
120
|
+
|
|
121
|
+
### Changed
|
|
122
|
+
|
|
123
|
+
- **Logging**: every line goes through a guard — a logger that throws or rejects changes no
|
|
124
|
+
outcome. A failed write is one `warn` line with auth-errors' `logFields`, never a class name or
|
|
125
|
+
message. A stored secret not used is a `warn` only where a stored secret could ever be seeded,
|
|
126
|
+
a `debug` line otherwise.
|
|
127
|
+
- **The certificate client is re-read on every call** when the build read it: a rotated
|
|
128
|
+
certificate builds a new provider at the next call (4.x pinned it for the broker's life).
|
|
129
|
+
- **Built under a stricter compiler.** Both packages, sources and tests, compile with
|
|
130
|
+
`noImplicitReturns`, `noFallthroughCasesInSwitch`, `noImplicitOverride`,
|
|
131
|
+
`noUncheckedIndexedAccess` and `exactOptionalPropertyTypes`; objects handed to a consumer keep
|
|
132
|
+
exactly their own keys (`src/contractShape.ts`). Lint: `noExplicitAny` is an error outside the
|
|
133
|
+
tests, and `lint:check` fails on any warning.
|
|
134
|
+
- **The broker stand and the live suites** run on the 6.0 chain and on
|
|
135
|
+
`@mcp-abap-adt/connection` 14; each states `renewal` and `onWriteFailure`.
|
|
136
|
+
- `tools/check-provider-shape.mjs`, copied byte for byte from auth-errors 2.1.1, runs with
|
|
137
|
+
rules 4, 5, 6 over both packages (`npm run check:shape`).
|
|
138
|
+
|
|
139
|
+
### Dependencies
|
|
140
|
+
|
|
141
|
+
- `@mcp-abap-adt/auth-providers` `^5.3.0` → `^6.0.0`; `@mcp-abap-adt/interfaces-auth` `^3.2.0`
|
|
142
|
+
→ `^7.5.0`; `@mcp-abap-adt/interfaces-auth-sap` `^2.0.0` → `^3.3.0`;
|
|
143
|
+
`@mcp-abap-adt/interfaces-auth-broker` `^1.2.0` → `^1.3.0`; new `@mcp-abap-adt/auth-errors`
|
|
144
|
+
`^2.1.1`. Dev: `@mcp-abap-adt/auth-stores` `^4.0.0`, `@mcp-abap-adt/connection` `^14.0.0`.
|
|
145
|
+
|
|
146
|
+
### Measured, and not
|
|
147
|
+
|
|
148
|
+
- Not yet re-run on 5.0.0: the x509 live check (`npm run test:live:x509`, last run 2026-10-05 on
|
|
149
|
+
4.1.0) and the live suite (`npm run test:live`).
|
|
150
|
+
|
|
151
|
+
## [4.1.0] - 2026-10-05
|
|
152
|
+
|
|
153
|
+
**x509 service keys.** A client that authenticates to the authorization
|
|
154
|
+
server with a certificate instead of a secret — an XSUAA service key created
|
|
155
|
+
with `{"credential-type": "x509"}` — when the consumer says so, through a
|
|
156
|
+
strategy. Additive: without the new option every destination is served
|
|
157
|
+
exactly as by 4.0.0, and nothing certificate-related is read. Released with
|
|
158
|
+
`@mcp-abap-adt/auth-broker-cli` 2.1.0.
|
|
159
|
+
|
|
160
|
+
### Added
|
|
161
|
+
|
|
162
|
+
- **`AuthBrokerConfig.clientAuthentication`** — a `ClientAuthenticationStrategy`
|
|
163
|
+
`(context) => Promise<IClientAuthentication>`: how the client of every grant
|
|
164
|
+
that authenticates one (`authorization_code`, `client_credentials`,
|
|
165
|
+
`passcode`, `oidc_authorization_code`, `device_code`, `password`,
|
|
166
|
+
`token_exchange`, `saml2_bearer` — `ClientAuthenticationGrant`)
|
|
167
|
+
authenticates. Called once per build with a `ClientAuthenticationContext`
|
|
168
|
+
(`destination`, `grant`, the key store's secret `client` — `uaaUrl`,
|
|
169
|
+
`uaaClientId`, `uaaClientSecret` only, never a refresh token — or `null`, and a
|
|
170
|
+
lazy, memoised `readCertificate()`); its answer goes to the provider as
|
|
171
|
+
`clientAuthentication`, with no client secret. No default and no "nothing"
|
|
172
|
+
answer: a stated choice never falls back to the secret.
|
|
173
|
+
- **`fromServiceKeyCertificate()`** — auth-providers' `tlsClientCertificate`
|
|
174
|
+
from the key store's certificate client, against `<certUrl>/oauth/token`
|
|
175
|
+
(a trailing `/` of `certUrl` dropped), the material checked before it
|
|
176
|
+
answers. **`fromServiceKeySecret({ encoding: 'raw' | 'form' })`** —
|
|
177
|
+
`clientSecretBasic` with the secret client; `encoding` required (`raw` for
|
|
178
|
+
XSUAA, measured). Each fails closed; a fallback is the consumer's
|
|
179
|
+
composition, decided by what the key holds (`readCertificate()`), never by
|
|
180
|
+
catching a refusal.
|
|
181
|
+
- **The certificate client** is read through the optional
|
|
182
|
+
`IServiceKeyStore.getClientCertificate` (`@mcp-abap-adt/interfaces-auth-broker`
|
|
183
|
+
1.2.0) — auth-stores 3.3.0 answers it for an x509 XSUAA key and for an
|
|
184
|
+
`EnvDestinationStore` stating `SAP_UAA_CLIENT_CERT_PATH`,
|
|
185
|
+
`SAP_UAA_CLIENT_KEY_PATH` and `SAP_UAA_CERT_URL` (`XSUAA_UAA_*`) — only
|
|
186
|
+
when the strategy asks, at most once per build. The row's client is then
|
|
187
|
+
its identity: the secret client's `uaaUrl` / `uaaClientId`, else the
|
|
188
|
+
certificate client's.
|
|
189
|
+
- **The token API factory's fourth argument, `TokenProviderClient`** —
|
|
190
|
+
beside a strategy, for a grant that authenticates a client: the strategy's
|
|
191
|
+
`clientAuthentication`, the client identity (`uaaUrl`, `clientId`) and the
|
|
192
|
+
stored `refreshToken` when the session is bound here — taken only from the
|
|
193
|
+
session read whose binding was checked, never from a client's own read.
|
|
194
|
+
Never a certificate, key or secret. Additive: a 4.0.0 factory keeps working.
|
|
195
|
+
- **Fixed-words refusals**: whatever a strategy, a store it reads or the
|
|
196
|
+
certificate check throws — and an answer that is no `IClientAuthentication`
|
|
197
|
+
— is a `DestinationConfigError` naming `clientAuthentication` before any
|
|
198
|
+
provider exists, its words chosen by the error's class (no certificate, no
|
|
199
|
+
secret, incomplete / expired / unusable certificate, no client
|
|
200
|
+
authentication, the certificate could not be read, the strategy failed); a
|
|
201
|
+
factory that throws beside a strategy is one naming `provider`,
|
|
202
|
+
`clientAuthentication`. No `cause`, nothing of the thrown message, no PEM
|
|
203
|
+
anywhere — log, refusal, error, session store.
|
|
204
|
+
- Exports: `fromServiceKeyCertificate`, `fromServiceKeySecret`, and the
|
|
205
|
+
types `ClientAuthenticationStrategy`, `ClientAuthenticationContext`,
|
|
206
|
+
`ClientAuthenticationGrant`, `FromServiceKeySecretOptions`,
|
|
207
|
+
`TokenProviderClient`, `IClientCertificate`, `IClientAuthentication`.
|
|
208
|
+
- `npm run test:live:x509`: the live check against a BTP subaccount (exact
|
|
209
|
+
`cf target` guard, a GUID ledger, teardown also on failure). Not in CI.
|
|
210
|
+
|
|
211
|
+
### Changed
|
|
212
|
+
|
|
213
|
+
- **A client row with no client and no strategy names the way out.** The
|
|
214
|
+
`DestinationConfigError` of a UAA, OIDC or `saml2_bearer` destination whose
|
|
215
|
+
key store answers no client (or one without a client id) keeps its
|
|
216
|
+
`missingFields`; its message adds `; a certificate client needs a
|
|
217
|
+
clientAuthentication strategy`.
|
|
218
|
+
|
|
219
|
+
### On the strategy path (only with `clientAuthentication`)
|
|
220
|
+
|
|
221
|
+
- **The binding is the client's identity**: `issuedBy` from the identity's
|
|
222
|
+
`uaaUrl` and `client_id`, so a certificate destination's tokens are reused
|
|
223
|
+
after the broker is recreated and dropped when the issuer or client id
|
|
224
|
+
changes — and a secret key and an x509 key of the same client id share one
|
|
225
|
+
session (switching keeps its token and refresh token).
|
|
226
|
+
- **A resource neither side states matches**: means with no `serviceUrl`, or
|
|
227
|
+
one that does not parse (the CLI's XSUAA placeholder), and a session stored
|
|
228
|
+
without `issuedFor`; a resource stated on one side only never matches.
|
|
229
|
+
Without a strategy an absent resource still matches nothing.
|
|
230
|
+
- **The token API with a factory**: the destination must state `authType`
|
|
231
|
+
(`jwt` / `saml`) and `grantType` (else `DestinationConfigError` naming
|
|
232
|
+
both); `saml2_pure` and `none` get three arguments; every stored secret —
|
|
233
|
+
the refresh token in `authConfig` and the fourth argument, the token,
|
|
234
|
+
cookies and expiry in `connConfig` — reaches the factory only when the
|
|
235
|
+
session is bound here, and a result without a refresh token carries forward
|
|
236
|
+
only a bound one. (Without a strategy, 4.0.0's carry-over stands.) An
|
|
237
|
+
instance `provider` gets no strategy. What the strategy and the factory
|
|
238
|
+
are handed is built from allowlists, never a store's answer passed whole:
|
|
239
|
+
the client (`uaaUrl`, `uaaClientId`, `uaaClientSecret`), the certificate
|
|
240
|
+
client (its five declared fields), and the seed (`serviceUrl`, `sapClient`,
|
|
241
|
+
`language`, `authType`, `grantType`, and the session secret's fields only
|
|
242
|
+
from a bound read).
|
|
243
|
+
|
|
244
|
+
### Dependencies
|
|
245
|
+
|
|
246
|
+
`@mcp-abap-adt/interfaces-auth-broker` `^1.2.0` (was `^1.1.0`),
|
|
247
|
+
`@mcp-abap-adt/interfaces-auth` `^3.2.0` (was `^3.0.0`),
|
|
248
|
+
`@mcp-abap-adt/auth-providers` `^5.3.0` (was `^5.2.1`); dev:
|
|
249
|
+
`@mcp-abap-adt/auth-stores` `^3.3.0`.
|
|
250
|
+
|
|
251
|
+
### Migrating from 4.0.0
|
|
252
|
+
|
|
253
|
+
Nothing is required. Code that matched a `DestinationConfigError`'s message
|
|
254
|
+
for a destination without a client should match `missingFields` or the class
|
|
255
|
+
instead. To use an x509 key: auth-stores `^3.3.0` and
|
|
256
|
+
`clientAuthentication: fromServiceKeyCertificate()` — then read the strategy
|
|
257
|
+
path's rules above (identity binding, bound secrets only, the token API's
|
|
258
|
+
grant refusal, the factory's fourth argument). See *Migrating from 4.0.0* in
|
|
259
|
+
the README.
|
|
260
|
+
|
|
261
|
+
### Measured, and not
|
|
262
|
+
|
|
263
|
+
Measured: `client_credentials` with an x509 XSUAA key on a BTP trial,
|
|
264
|
+
2026-10-05 (`credential-types: ["binding-secret", "x509"]`) — the broker
|
|
265
|
+
(`getProvider` and the token API), `mcp-auth --client-auth certificate` and
|
|
266
|
+
`generate-env-from-service-key --client-auth certificate`, each followed by a
|
|
267
|
+
fresh broker over the written destination; all five live cases passed. Not
|
|
268
|
+
measured: `authorization_code` and `passcode` over x509, the OIDC grants and
|
|
269
|
+
`saml2_bearer` with a strategy, ABAP environment service keys with x509.
|
|
270
|
+
|
|
271
|
+
### Known limitations
|
|
272
|
+
|
|
273
|
+
- **The certificate is pinned for the broker's lifetime.** It is read when a
|
|
274
|
+
destination's provider is built, and the provider is cached per
|
|
275
|
+
destination for as long as the `AuthBroker` lives. An XSUAA x509
|
|
276
|
+
certificate lives about seven days: a long-running process then gets "the
|
|
277
|
+
client certificate has expired" until it builds a new `AuthBroker`, and
|
|
278
|
+
replacing the key or the PEM files changes nothing before that. Rotation is
|
|
279
|
+
out of scope.
|
|
280
|
+
- The certificate refusals use the broker's own three phrases (incomplete /
|
|
281
|
+
expired / could not be used), classified by auth-providers'
|
|
282
|
+
`CertificateMaterialError` flags; the provider's own hint for each is not
|
|
283
|
+
relayed. auth-providers' exported refusal words (a later minor) would
|
|
284
|
+
replace them.
|
|
13
285
|
|
|
14
286
|
## [4.0.0] - 2026-10-02
|
|
15
287
|
|