@absolutejs/auth 0.75.0 → 0.75.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/README.md +13 -0
- package/dist/agents/config.d.ts +5 -1
- package/dist/agents/index.js +47 -11
- package/dist/agents/index.js.map +5 -5
- package/dist/agents/oidcAdapter.d.ts +3 -1
- package/dist/agents/routes.d.ts +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +64 -24
- package/dist/index.js.map +8 -8
- package/dist/nativePush.d.ts +14 -14
- package/dist/oidc/dpop.d.ts +6 -2
- package/dist/oidc/index.d.ts +1 -0
- package/dist/oidc/index.js +130 -1
- package/dist/oidc/index.js.map +4 -3
- package/dist/server.js +64 -24
- package/dist/server.js.map +8 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -281,8 +281,11 @@ const authPlugin = await auth({
|
|
|
281
281
|
resource: 'https://api.example.com',
|
|
282
282
|
scopes: ['documents:read', 'documents:write'],
|
|
283
283
|
verifyCredential: createOidcAgentCredentialVerifier({
|
|
284
|
+
// Atomically insert a hash of jkt + jti; return false on conflict.
|
|
285
|
+
consumeDpopJti: replayStore.consume,
|
|
284
286
|
issuer: 'https://auth.example.com',
|
|
285
287
|
publicJwk: signingKey.publicJwk,
|
|
288
|
+
requireDpop: true,
|
|
286
289
|
resource: 'https://api.example.com'
|
|
287
290
|
})
|
|
288
291
|
},
|
|
@@ -300,6 +303,16 @@ registration. Approval through the existing RFC 8628 device flow creates the
|
|
|
300
303
|
user-to-agent delegation. The agent can then use RFC 8693 token exchange to get
|
|
301
304
|
a narrowed, audience-bound access token for the protected API.
|
|
302
305
|
|
|
306
|
+
When `requireDpop` is enabled, the adapter accepts only an RFC 9449-bound
|
|
307
|
+
access token using the `DPoP` authorization scheme, verifies its per-request
|
|
308
|
+
proof and `ath` token hash, and requires the proof key to match `cnf.jkt`.
|
|
309
|
+
Provide `consumeDpopJti` as an atomic shared-store insertion in clustered
|
|
310
|
+
deployments; returning `false` rejects a replay. Proofs without `jti`, proofs
|
|
311
|
+
whose JWK contains private key material, oversized identifiers, and `htu`
|
|
312
|
+
claims containing query or fragment components fail closed. Resource servers
|
|
313
|
+
that require RFC 9449 nonces can use the nonce helpers exported by
|
|
314
|
+
`@absolutejs/auth/oidc` to issue a separate resource nonce challenge.
|
|
315
|
+
|
|
303
316
|
```ts
|
|
304
317
|
app.get('/documents', ({ protectAgent }) =>
|
|
305
318
|
protectAgent(['documents:read'], (agent) => ({
|
package/dist/agents/config.d.ts
CHANGED
|
@@ -25,6 +25,9 @@ export type AgentAuthConfig = {
|
|
|
25
25
|
oauthGuide?: AgentOAuthGuideConfig;
|
|
26
26
|
authorizationServer: string;
|
|
27
27
|
delegationStore: AgentDelegationStore;
|
|
28
|
+
/** Advertise RFC 9728 `dpop_bound_access_tokens_required`. Keep this aligned
|
|
29
|
+
* with a credential verifier configured with `requireDpop: true`. */
|
|
30
|
+
dpopBoundAccessTokensRequired?: boolean;
|
|
28
31
|
logoUri?: string;
|
|
29
32
|
metadataRoute?: RouteString;
|
|
30
33
|
/** Authorization-server route prefix. Defaults to `/oauth2`; used to derive
|
|
@@ -39,11 +42,12 @@ export type AgentAuthConfig = {
|
|
|
39
42
|
scopes: string[];
|
|
40
43
|
verifyCredential: AgentCredentialVerifier;
|
|
41
44
|
};
|
|
42
|
-
export declare const agentProtectedResourceMetadata: (config: Pick<AgentAuthConfig, "authorizationServer" | "logoUri" | "resource" | "resourceName" | "scopes">) => {
|
|
45
|
+
export declare const agentProtectedResourceMetadata: (config: Pick<AgentAuthConfig, "authorizationServer" | "dpopBoundAccessTokensRequired" | "logoUri" | "resource" | "resourceName" | "scopes">) => {
|
|
43
46
|
resource: string;
|
|
44
47
|
scopes_supported: string[];
|
|
45
48
|
resource_name?: string | undefined;
|
|
46
49
|
resource_logo_uri?: string | undefined;
|
|
50
|
+
dpop_bound_access_tokens_required?: boolean | undefined;
|
|
47
51
|
authorization_servers: string[];
|
|
48
52
|
bearer_methods_supported: string[];
|
|
49
53
|
};
|
package/dist/agents/index.js
CHANGED
|
@@ -186,6 +186,7 @@ var DEFAULT_AGENT_RESOURCE_METADATA_ROUTE = "/.well-known/oauth-protected-resour
|
|
|
186
186
|
var agentProtectedResourceMetadata = (config) => ({
|
|
187
187
|
authorization_servers: [config.authorizationServer],
|
|
188
188
|
bearer_methods_supported: ["header"],
|
|
189
|
+
...config.dpopBoundAccessTokensRequired === true ? { dpop_bound_access_tokens_required: true } : {},
|
|
189
190
|
...config.logoUri === undefined ? {} : { resource_logo_uri: config.logoUri },
|
|
190
191
|
...config.resourceName === undefined ? {} : { resource_name: config.resourceName },
|
|
191
192
|
resource: config.resource,
|
|
@@ -1349,6 +1350,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
1349
1350
|
// src/oidc/dpop.ts
|
|
1350
1351
|
init_constants();
|
|
1351
1352
|
var DEFAULT_MAX_AGE_MS = 60000;
|
|
1353
|
+
var MAX_JTI_LENGTH = 128;
|
|
1352
1354
|
var SECONDS_TO_MS = 1000;
|
|
1353
1355
|
var NONCE_WINDOW_SECONDS = 120;
|
|
1354
1356
|
var NONCE_WINDOW_MS = NONCE_WINDOW_SECONDS * MILLISECONDS_IN_A_SECOND;
|
|
@@ -1389,17 +1391,38 @@ var verifyDpopNonce = async ({
|
|
|
1389
1391
|
const candidates = await Promise.all(Array.from({ length: NONCE_PREVIOUS_WINDOWS_ACCEPTED + 1 }, (_, offset) => hmacSha256(secret, String(currentWindow - offset))));
|
|
1390
1392
|
return candidates.some((expected) => expected === nonce);
|
|
1391
1393
|
};
|
|
1392
|
-
var decodeHeader = (segment) =>
|
|
1393
|
-
|
|
1394
|
+
var decodeHeader = (segment) => {
|
|
1395
|
+
try {
|
|
1396
|
+
const value = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
|
|
1397
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
1398
|
+
} catch {
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
var normalizeHtu = (value, proofClaim = false) => {
|
|
1394
1403
|
try {
|
|
1395
1404
|
const url = new URL(String(value));
|
|
1405
|
+
if (url.username || url.password || proofClaim && (url.search.length > 0 || url.hash.length > 0))
|
|
1406
|
+
return;
|
|
1396
1407
|
return `${url.origin}${url.pathname}`;
|
|
1397
1408
|
} catch {
|
|
1398
|
-
return
|
|
1409
|
+
return;
|
|
1399
1410
|
}
|
|
1400
1411
|
};
|
|
1412
|
+
var isPublicEs256Jwk = (value) => {
|
|
1413
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1414
|
+
return false;
|
|
1415
|
+
const kty = Reflect.get(value, "kty");
|
|
1416
|
+
const crv = Reflect.get(value, "crv");
|
|
1417
|
+
const xCoordinate = Reflect.get(value, "x");
|
|
1418
|
+
const yCoordinate = Reflect.get(value, "y");
|
|
1419
|
+
const privateComponent = Reflect.get(value, "d");
|
|
1420
|
+
const alg = Reflect.get(value, "alg");
|
|
1421
|
+
return kty === "EC" && crv === "P-256" && typeof xCoordinate === "string" && xCoordinate.length > 0 && typeof yCoordinate === "string" && yCoordinate.length > 0 && privateComponent === undefined && (alg === undefined || alg === "ES256");
|
|
1422
|
+
};
|
|
1401
1423
|
var verifyDpopProof = async ({
|
|
1402
1424
|
accessToken,
|
|
1425
|
+
consumeJti,
|
|
1403
1426
|
htm,
|
|
1404
1427
|
htu,
|
|
1405
1428
|
isUsedJti,
|
|
@@ -1409,14 +1432,20 @@ var verifyDpopProof = async ({
|
|
|
1409
1432
|
}) => {
|
|
1410
1433
|
if (proof === undefined)
|
|
1411
1434
|
return;
|
|
1412
|
-
const
|
|
1435
|
+
const segments = proof.split(".");
|
|
1436
|
+
if (segments.length !== 3)
|
|
1437
|
+
return;
|
|
1438
|
+
const [headerSegment] = segments;
|
|
1413
1439
|
if (headerSegment === undefined)
|
|
1414
1440
|
return;
|
|
1415
1441
|
const header = decodeHeader(headerSegment);
|
|
1416
|
-
|
|
1442
|
+
const publicJwk = header === undefined ? undefined : Reflect.get(header, "jwk");
|
|
1443
|
+
if (header === undefined || Reflect.get(header, "typ") !== "dpop+jwt" || Reflect.get(header, "alg") !== "ES256" || !isPublicEs256Jwk(publicJwk)) {
|
|
1417
1444
|
return;
|
|
1418
1445
|
}
|
|
1419
|
-
const verified = await verifyJwt(proof,
|
|
1446
|
+
const verified = await verifyJwt(proof, publicJwk).catch(() => {
|
|
1447
|
+
return;
|
|
1448
|
+
});
|
|
1420
1449
|
if (verified === undefined)
|
|
1421
1450
|
return;
|
|
1422
1451
|
const { payload } = verified;
|
|
@@ -1426,14 +1455,19 @@ var verifyDpopProof = async ({
|
|
|
1426
1455
|
return;
|
|
1427
1456
|
}
|
|
1428
1457
|
const iatMs = typeof payload.iat === "number" ? payload.iat * SECONDS_TO_MS : 0;
|
|
1429
|
-
|
|
1458
|
+
const claimedHtu = normalizeHtu(payload.htu, true);
|
|
1459
|
+
const expectedHtu = normalizeHtu(htu);
|
|
1460
|
+
const jti = typeof payload.jti === "string" ? payload.jti : undefined;
|
|
1461
|
+
if (payload.htm !== htm || claimedHtu === undefined || expectedHtu === undefined || claimedHtu !== expectedHtu || iatMs === 0 || Math.abs(now - iatMs) > maxAgeMs || jti === undefined || jti.length === 0 || jti.length > MAX_JTI_LENGTH) {
|
|
1430
1462
|
return;
|
|
1431
1463
|
}
|
|
1432
|
-
const
|
|
1433
|
-
if (
|
|
1464
|
+
const jkt = await jwkThumbprint(publicJwk);
|
|
1465
|
+
if (isUsedJti !== undefined && await isUsedJti(jti)) {
|
|
1434
1466
|
return;
|
|
1435
1467
|
}
|
|
1436
|
-
|
|
1468
|
+
if (consumeJti !== undefined && !await consumeJti({ expiresAt: iatMs + maxAgeMs, jkt, jti }))
|
|
1469
|
+
return;
|
|
1470
|
+
return { jkt, jti };
|
|
1437
1471
|
};
|
|
1438
1472
|
|
|
1439
1473
|
// src/agents/oidcAdapter.ts
|
|
@@ -1449,6 +1483,7 @@ var readAudience = (audience) => {
|
|
|
1449
1483
|
};
|
|
1450
1484
|
var createOidcAgentCredentialVerifier = ({
|
|
1451
1485
|
issuer,
|
|
1486
|
+
consumeDpopJti,
|
|
1452
1487
|
isUsedDpopJti,
|
|
1453
1488
|
maxDpopAgeMs,
|
|
1454
1489
|
publicJwk,
|
|
@@ -1477,6 +1512,7 @@ var createOidcAgentCredentialVerifier = ({
|
|
|
1477
1512
|
return;
|
|
1478
1513
|
const proof = await verifyDpopProof({
|
|
1479
1514
|
accessToken: token,
|
|
1515
|
+
consumeJti: consumeDpopJti,
|
|
1480
1516
|
htm: request.method,
|
|
1481
1517
|
htu: request.url,
|
|
1482
1518
|
isUsedJti: isUsedDpopJti,
|
|
@@ -13763,5 +13799,5 @@ export {
|
|
|
13763
13799
|
AGENT_CLAIM_GRANT_TYPE
|
|
13764
13800
|
};
|
|
13765
13801
|
|
|
13766
|
-
//# debugId=
|
|
13802
|
+
//# debugId=16DE04B83F43B7D264756E2164756E21
|
|
13767
13803
|
//# sourceMappingURL=index.js.map
|