@absolutejs/auth 0.56.12 → 0.56.14
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 +14 -0
- package/dist/agents/config.d.ts +5 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.js +132 -8
- package/dist/agents/index.js.map +8 -7
- package/dist/agents/oauthGuide.d.ts +18 -0
- package/dist/agents/oidcAdapter.d.ts +11 -3
- package/dist/agents/routes.d.ts +1 -66
- package/dist/index.d.ts +4 -22
- package/dist/index.js +172 -24
- package/dist/index.js.map +13 -12
- package/dist/oidc/config.d.ts +5 -1
- package/dist/oidc/index.d.ts +1 -1
- package/dist/oidc/index.js +23 -1
- package/dist/oidc/index.js.map +3 -3
- package/dist/oidc/keys.d.ts +22 -7
- package/dist/server.js +167 -24
- package/dist/server.js.map +13 -12
- package/docs/AGENT-AUTH.md +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,6 +97,12 @@ adds a scoped `protectAgent` guard. It can also serve a generated `/auth.md`
|
|
|
97
97
|
registration guide and matching structured OAuth metadata. This is native to
|
|
98
98
|
`@absolutejs/auth`; no WorkOS service or separate package is required.
|
|
99
99
|
|
|
100
|
+
Applications using ordinary OAuth dynamic client registration can publish an
|
|
101
|
+
agent-readable `/auth.md` without enabling the separate claim/ID-JAG profile.
|
|
102
|
+
Set `agentAuth.oauthGuide` to the exact enabled protected resources, metadata
|
|
103
|
+
URLs, and scopes. Auth serves the guide and advertises it through RFC 8414
|
|
104
|
+
`service_documentation`; the structured OAuth metadata remains authoritative.
|
|
105
|
+
|
|
100
106
|
Protocol-specific credentials are normalized by verifier adapters:
|
|
101
107
|
|
|
102
108
|
```ts
|
|
@@ -167,6 +173,14 @@ can therefore keep private key material non-exportable in a KMS or HSM. The
|
|
|
167
173
|
adapter must return the 64-byte JOSE ES256 signature (`r || s`); DER conversion
|
|
168
174
|
belongs at the KMS boundary.
|
|
169
175
|
|
|
176
|
+
OIDC providers can retain bounded `previousSigningKeys` containing public
|
|
177
|
+
identity only. The JWKS endpoint publishes the active key first and the
|
|
178
|
+
previous keys behind it, while every new token remains signed exclusively by
|
|
179
|
+
the active key. Provider token exchange, introspection, userinfo, logout hints,
|
|
180
|
+
and agent credential verification select the exact verification key named by
|
|
181
|
+
the JWT `kid`. Remove each previous key only after the longest issued token
|
|
182
|
+
using it has expired; duplicate key IDs fail closed.
|
|
183
|
+
|
|
170
184
|
### Features
|
|
171
185
|
|
|
172
186
|
- **Authorization**: Handles the authorization process by generating the authorization URL and redirecting the user to the authentication provider.
|
package/dist/agents/config.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RouteString } from '../types';
|
|
2
2
|
import type { AgentCredentialVerifier, AgentDelegationStore, AgentRegistrationStore } from './types';
|
|
3
3
|
import type { AgentRegistrationProtocolConfig } from './registration';
|
|
4
|
+
import type { AgentOAuthGuideConfig } from './oauthGuide';
|
|
4
5
|
export declare const DEFAULT_AGENT_RESOURCE_METADATA_ROUTE: RouteString;
|
|
5
6
|
export type AgentRegistrationDiscoveryMetadata = {
|
|
6
7
|
claim_endpoint: string;
|
|
@@ -18,6 +19,10 @@ export type AgentAuthConfig = {
|
|
|
18
19
|
/** Open auth.md agent-registration support. Implemented natively by Absolute
|
|
19
20
|
* Auth and projected through OAuth discovery; no WorkOS service is required. */
|
|
20
21
|
agentRegistration?: AgentRegistrationProtocolConfig;
|
|
22
|
+
/** Agent-readable OAuth onboarding derived from the same protected resources
|
|
23
|
+
* and scopes the application actually enables. This is separate from the
|
|
24
|
+
* optional claim/ID-JAG registration profile. */
|
|
25
|
+
oauthGuide?: AgentOAuthGuideConfig;
|
|
21
26
|
authorizationServer: string;
|
|
22
27
|
delegationStore: AgentDelegationStore;
|
|
23
28
|
logoUri?: string;
|
package/dist/agents/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './types';
|
|
|
3
3
|
export * from './registration';
|
|
4
4
|
export * from './registrationClient';
|
|
5
5
|
export * from './idJag';
|
|
6
|
+
export * from './oauthGuide';
|
|
6
7
|
export * from '../oidc/clientIdMetadata';
|
|
7
8
|
export { createOidcAgentCredentialVerifier } from './oidcAdapter';
|
|
8
9
|
export { agentHasScopes, resolveAgentPrincipal } from './principal';
|
package/dist/agents/index.js
CHANGED
|
@@ -274,6 +274,26 @@ var verifyJwt = async (token, publicJwk) => {
|
|
|
274
274
|
payload
|
|
275
275
|
};
|
|
276
276
|
};
|
|
277
|
+
var signingVerificationKeys = (active, previous = []) => {
|
|
278
|
+
const keys = [active, ...previous];
|
|
279
|
+
const keyIds = new Set(keys.map(({ kid }) => kid));
|
|
280
|
+
if (keyIds.size !== keys.length)
|
|
281
|
+
throw new Error("OIDC signing key IDs must be unique");
|
|
282
|
+
return keys;
|
|
283
|
+
};
|
|
284
|
+
var verifyJwtWithKeys = async (token, keys) => {
|
|
285
|
+
const [headerSegment] = token.split(".");
|
|
286
|
+
if (!headerSegment)
|
|
287
|
+
return;
|
|
288
|
+
const header = decodeSegment(headerSegment);
|
|
289
|
+
const kid = header?.kid;
|
|
290
|
+
if (typeof kid !== "string" || kid.length === 0)
|
|
291
|
+
return;
|
|
292
|
+
const key = keys.find((candidate) => candidate.kid === kid);
|
|
293
|
+
if (!key)
|
|
294
|
+
return;
|
|
295
|
+
return verifyJwt(token, key.publicJwk);
|
|
296
|
+
};
|
|
277
297
|
|
|
278
298
|
// src/agents/registration.ts
|
|
279
299
|
var AGENT_CLAIM_GRANT_TYPE = "urn:workos:agent-auth:grant-type:claim";
|
|
@@ -1145,8 +1165,96 @@ var createAgentIdentityAssertionVerifier = ({
|
|
|
1145
1165
|
subject
|
|
1146
1166
|
};
|
|
1147
1167
|
};
|
|
1168
|
+
// src/agents/oauthGuide.ts
|
|
1169
|
+
var DEFAULT_GUIDE_ROUTE2 = "/auth.md";
|
|
1170
|
+
var secureUrl2 = (value, label) => {
|
|
1171
|
+
const url = new URL(value);
|
|
1172
|
+
const loopback = url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
|
|
1173
|
+
if (url.protocol !== "https:" && !loopback)
|
|
1174
|
+
throw new Error(`${label} must use HTTPS outside loopback development`);
|
|
1175
|
+
if (url.username || url.password || url.hash)
|
|
1176
|
+
throw new Error(`${label} cannot contain credentials or a fragment`);
|
|
1177
|
+
return url.toString();
|
|
1178
|
+
};
|
|
1179
|
+
var agentOAuthGuideRoute = (config) => config.route ?? DEFAULT_GUIDE_ROUTE2;
|
|
1180
|
+
var agentOAuthGuideUrl = (config) => new URL(agentOAuthGuideRoute(config), secureUrl2(config.authorizationServer, "OAuth authorization server")).toString();
|
|
1181
|
+
var normalizedResources = (config) => {
|
|
1182
|
+
if (config.resources.length === 0)
|
|
1183
|
+
throw new Error("Agent OAuth guide requires at least one resource");
|
|
1184
|
+
const resources = config.resources.map((resource) => ({
|
|
1185
|
+
metadataUrl: secureUrl2(resource.metadataUrl, "Protected-resource metadata URL"),
|
|
1186
|
+
name: resource.name.trim(),
|
|
1187
|
+
resource: secureUrl2(resource.resource, "OAuth protected resource"),
|
|
1188
|
+
scopes: [
|
|
1189
|
+
...new Set(resource.scopes.map((scope) => scope.trim()))
|
|
1190
|
+
].filter(Boolean)
|
|
1191
|
+
}));
|
|
1192
|
+
if (resources.some(({ name, scopes }) => !name || scopes.length === 0))
|
|
1193
|
+
throw new Error("Every agent OAuth guide resource requires a name and at least one scope");
|
|
1194
|
+
if (new Set(resources.map(({ resource }) => resource)).size !== resources.length)
|
|
1195
|
+
throw new Error("Agent OAuth guide resources must be unique");
|
|
1196
|
+
return resources;
|
|
1197
|
+
};
|
|
1198
|
+
var resourceSections = (resources) => resources.map((resource) => `### ${resource.name}
|
|
1199
|
+
|
|
1200
|
+
- Protected resource: \`${resource.resource}\`
|
|
1201
|
+
- Metadata: ${resource.metadataUrl}
|
|
1202
|
+
- Allowed scopes: ${resource.scopes.map((scope) => `\`${scope}\``).join(", ")}
|
|
1203
|
+
`).join(`
|
|
1204
|
+
`);
|
|
1205
|
+
var generateAgentOAuthGuide = (config) => {
|
|
1206
|
+
if (!config.serviceName.trim())
|
|
1207
|
+
throw new Error("Agent OAuth guide requires a service name");
|
|
1208
|
+
const authorizationServer = secureUrl2(config.authorizationServer, "OAuth authorization server");
|
|
1209
|
+
const resources = normalizedResources(config);
|
|
1210
|
+
const authorizationMetadata = new URL("/.well-known/oauth-authorization-server", authorizationServer).toString();
|
|
1211
|
+
return `# OAuth access for ${config.serviceName.trim()}
|
|
1212
|
+
|
|
1213
|
+
This guide is the agent-readable companion to the service's standards-based
|
|
1214
|
+
OAuth metadata. Discovery metadata is authoritative. Do not infer endpoints,
|
|
1215
|
+
scopes, audiences, or capabilities that are not advertised.
|
|
1216
|
+
|
|
1217
|
+
## 1. Discover
|
|
1218
|
+
|
|
1219
|
+
Fetch the protected-resource metadata for the exact interface you intend to
|
|
1220
|
+
use, then fetch the authorization-server metadata at
|
|
1221
|
+
${authorizationMetadata}.
|
|
1222
|
+
|
|
1223
|
+
${resourceSections(resources)}
|
|
1224
|
+
## 2. Register the OAuth client
|
|
1225
|
+
|
|
1226
|
+
Use an existing registered client or the advertised \`registration_endpoint\`.
|
|
1227
|
+
Request only scopes listed for the selected protected resource. Never register
|
|
1228
|
+
redirect URIs, grant types, or authentication methods the metadata rejects.
|
|
1229
|
+
|
|
1230
|
+
## 3. Obtain user delegation
|
|
1231
|
+
|
|
1232
|
+
For an interactive user, use authorization code with PKCE. For a device or
|
|
1233
|
+
headless client, use the device authorization grant only when
|
|
1234
|
+
\`device_authorization_endpoint\` is advertised. Send the selected protected
|
|
1235
|
+
resource as the OAuth \`resource\` value and show the exact requested scopes to
|
|
1236
|
+
the user before consent.
|
|
1237
|
+
|
|
1238
|
+
## 4. Call the selected interface
|
|
1239
|
+
|
|
1240
|
+
Present the access token in the Authorization header. The token must name the
|
|
1241
|
+
selected resource as its audience and contain the required scope. A token for
|
|
1242
|
+
one resource or transport is not authority for another.
|
|
1243
|
+
|
|
1244
|
+
## Safety
|
|
1245
|
+
|
|
1246
|
+
- Never ask a user to send a password, passkey response, MFA code, device code,
|
|
1247
|
+
authorization code, client secret, refresh token, or access token to the
|
|
1248
|
+
agent or place one in model context.
|
|
1249
|
+
- Stop on issuer, signature, audience, expiry, delegation, or scope failure.
|
|
1250
|
+
- Treat consent denial, revocation, and disabled interfaces as final until the
|
|
1251
|
+
user explicitly starts a new authorization flow.
|
|
1252
|
+
- Discovery is not authorization, and this guide grants no capability by
|
|
1253
|
+
itself.
|
|
1254
|
+
`;
|
|
1255
|
+
};
|
|
1148
1256
|
// src/oidc/clientIdMetadata.ts
|
|
1149
|
-
var
|
|
1257
|
+
var secureUrl3 = (value) => {
|
|
1150
1258
|
try {
|
|
1151
1259
|
return new URL(value).protocol === "https:";
|
|
1152
1260
|
} catch {
|
|
@@ -1157,7 +1265,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
1157
1265
|
const errors = [];
|
|
1158
1266
|
if (document.client_id !== expectedClientId)
|
|
1159
1267
|
errors.push("client_id does not match the metadata document URL");
|
|
1160
|
-
if (!
|
|
1268
|
+
if (!secureUrl3(document.client_id))
|
|
1161
1269
|
errors.push("client_id must use HTTPS");
|
|
1162
1270
|
if (!Array.isArray(document.redirect_uris) || document.redirect_uris.length === 0)
|
|
1163
1271
|
errors.push("redirect_uris is required");
|
|
@@ -1177,7 +1285,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
1177
1285
|
["tos_uri", document.tos_uri],
|
|
1178
1286
|
["jwks_uri", document.jwks_uri]
|
|
1179
1287
|
]) {
|
|
1180
|
-
if (value !== undefined && !
|
|
1288
|
+
if (value !== undefined && !secureUrl3(value))
|
|
1181
1289
|
errors.push(`${name} must use HTTPS`);
|
|
1182
1290
|
}
|
|
1183
1291
|
return errors;
|
|
@@ -1200,7 +1308,7 @@ var createClientIdMetadataResolver = ({
|
|
|
1200
1308
|
}) => {
|
|
1201
1309
|
const cache = new Map;
|
|
1202
1310
|
return async (clientId) => {
|
|
1203
|
-
if (!
|
|
1311
|
+
if (!secureUrl3(clientId) || !await allow(clientId))
|
|
1204
1312
|
return;
|
|
1205
1313
|
const cached = cache.get(clientId);
|
|
1206
1314
|
if (cached !== undefined && cached.expiresAt > now())
|
|
@@ -1336,6 +1444,7 @@ var createOidcAgentCredentialVerifier = ({
|
|
|
1336
1444
|
isUsedDpopJti,
|
|
1337
1445
|
maxDpopAgeMs,
|
|
1338
1446
|
publicJwk,
|
|
1447
|
+
publicKeys,
|
|
1339
1448
|
requireDpop = false,
|
|
1340
1449
|
resource
|
|
1341
1450
|
}) => {
|
|
@@ -1347,13 +1456,14 @@ var createOidcAgentCredentialVerifier = ({
|
|
|
1347
1456
|
const token = authorization.slice(authorization.indexOf(" ") + 1).trim();
|
|
1348
1457
|
if (token.length === 0)
|
|
1349
1458
|
return;
|
|
1350
|
-
const verified = await verifyJwt(token, publicJwk);
|
|
1459
|
+
const verified = publicKeys ? await verifyJwtWithKeys(token, publicKeys) : await verifyJwt(token, publicJwk);
|
|
1351
1460
|
const payload = verified?.payload;
|
|
1352
1461
|
if (payload === undefined || payload.iss !== issuer || typeof payload.exp !== "number" || payload.exp <= Math.floor(Date.now() / MS_PER_SECOND) || !readAudience(payload.aud).includes(resource) || typeof payload.client_id !== "string") {
|
|
1353
1462
|
return;
|
|
1354
1463
|
}
|
|
1355
1464
|
const confirmation = payload.cnf;
|
|
1356
|
-
const
|
|
1465
|
+
const boundJktValue = typeof confirmation === "object" && confirmation !== null ? Reflect.get(confirmation, "jkt") : undefined;
|
|
1466
|
+
const boundJkt = typeof boundJktValue === "string" ? boundJktValue : undefined;
|
|
1357
1467
|
if (boundJkt !== undefined || requireDpop) {
|
|
1358
1468
|
if (!authorization.startsWith("DPoP "))
|
|
1359
1469
|
return;
|
|
@@ -1604,7 +1714,18 @@ var agentAuthRoutes = (config) => {
|
|
|
1604
1714
|
if (config === undefined)
|
|
1605
1715
|
return plugin.as("global");
|
|
1606
1716
|
if (config.agentRegistration === undefined) {
|
|
1607
|
-
|
|
1717
|
+
plugin.get(config.metadataRoute ?? DEFAULT_AGENT_RESOURCE_METADATA_ROUTE, () => agentProtectedResourceMetadata(config));
|
|
1718
|
+
const { oauthGuide } = config;
|
|
1719
|
+
if (oauthGuide === undefined)
|
|
1720
|
+
return plugin.as("global");
|
|
1721
|
+
const guide = generateAgentOAuthGuide(oauthGuide);
|
|
1722
|
+
plugin.get(agentOAuthGuideRoute(oauthGuide), () => new Response(guide, {
|
|
1723
|
+
headers: {
|
|
1724
|
+
"cache-control": "public, max-age=300",
|
|
1725
|
+
"content-type": "text/markdown; charset=utf-8"
|
|
1726
|
+
}
|
|
1727
|
+
}));
|
|
1728
|
+
return plugin.as("global");
|
|
1608
1729
|
}
|
|
1609
1730
|
const registration = config.agentRegistration;
|
|
1610
1731
|
const identityRoute = registration.identityRoute ?? "/agent/identity";
|
|
@@ -13563,6 +13684,7 @@ export {
|
|
|
13563
13684
|
issueAgentIdentityAssertion,
|
|
13564
13685
|
handleAgentTokenGrant,
|
|
13565
13686
|
generateAgentRegistrationGuide,
|
|
13687
|
+
generateAgentOAuthGuide,
|
|
13566
13688
|
discoverAgentRegistration,
|
|
13567
13689
|
createPostgresAgentRegistrationStore,
|
|
13568
13690
|
createPostgresAgentIdentityRegistrationStore,
|
|
@@ -13586,6 +13708,8 @@ export {
|
|
|
13586
13708
|
agentRegistrationEndpoints,
|
|
13587
13709
|
agentRegistrationDiscoveryMetadata,
|
|
13588
13710
|
agentProtectedResourceMetadata,
|
|
13711
|
+
agentOAuthGuideUrl,
|
|
13712
|
+
agentOAuthGuideRoute,
|
|
13589
13713
|
agentIdentityRegistrationsTable,
|
|
13590
13714
|
agentHasScopes,
|
|
13591
13715
|
agentDelegationsTable,
|
|
@@ -13598,5 +13722,5 @@ export {
|
|
|
13598
13722
|
AGENT_CLAIM_GRANT_TYPE
|
|
13599
13723
|
};
|
|
13600
13724
|
|
|
13601
|
-
//# debugId=
|
|
13725
|
+
//# debugId=DE3C9B11A35558B764756E2164756E21
|
|
13602
13726
|
//# sourceMappingURL=index.js.map
|