@absolutejs/auth 0.36.0-beta.0 → 0.36.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/dist/index.d.ts CHANGED
@@ -15154,6 +15154,7 @@ export type { DpopResult } from './oidc/dpop';
15154
15154
  export { CLIENT_ASSERTION_TYPE, verifyClientAssertion, verifyJwtSignedByClient } from './oidc/clientAuth';
15155
15155
  export { parseSignedRequestObject } from './oidc/jar';
15156
15156
  export type { JarParseResult } from './oidc/jar';
15157
+ export { computeCertThumbprint, extractRfc9440ClientCert, resolveClientCert, verifyCertificateBoundToken } from './oidc/mtls';
15157
15158
  export { createInMemoryAuthorizationCodeStore, createInMemoryBackchannelAuthStore, createInMemoryClientAssertionJtiStore, createInMemoryClientRegistrationTokenStore, createInMemoryDeviceAuthorizationStore, createInMemoryInitialAccessTokenStore, createInMemoryLogoutDeliveryStore, createInMemoryOAuthClientStore, createInMemoryOidcRefreshTokenStore, createInMemoryPushedAuthorizationRequestStore } from './oidc/inMemoryStores';
15158
15159
  export { consumePushedRequest, pushAuthorizationRequest, DEFAULT_PAR_TTL_MS, REQUEST_URI_PREFIX } from './oidc/par';
15159
15160
  export { fetchUserInfo, readUserInfoBearer, userInfoChallengeHeader } from './oidc/userinfo';
package/dist/index.js CHANGED
@@ -4492,6 +4492,7 @@ var RESERVED_ACCESS_CLAIMS = new Set([
4492
4492
  var buildAccessClaims = ({
4493
4493
  act,
4494
4494
  audience,
4495
+ clientCertThumbprint,
4495
4496
  clientId,
4496
4497
  dpopJkt,
4497
4498
  extraClaims,
@@ -4516,8 +4517,15 @@ var buildAccessClaims = ({
4516
4517
  };
4517
4518
  if (act !== undefined)
4518
4519
  claims.act = act;
4519
- if (dpopJkt !== undefined)
4520
- claims.cnf = { jkt: dpopJkt };
4520
+ if (dpopJkt !== undefined || clientCertThumbprint !== undefined) {
4521
+ const cnf = {};
4522
+ if (dpopJkt !== undefined)
4523
+ cnf.jkt = dpopJkt;
4524
+ if (clientCertThumbprint !== undefined) {
4525
+ cnf["x5t#S256"] = clientCertThumbprint;
4526
+ }
4527
+ claims.cnf = cnf;
4528
+ }
4521
4529
  return claims;
4522
4530
  };
4523
4531
  var exchangeToken = async ({
@@ -4567,6 +4575,7 @@ var exchangeToken = async ({
4567
4575
  var issueTokenSet = async ({
4568
4576
  acr,
4569
4577
  claims,
4578
+ clientCertThumbprint,
4570
4579
  clientId,
4571
4580
  config,
4572
4581
  dpopJkt,
@@ -4584,6 +4593,7 @@ var issueTokenSet = async ({
4584
4593
  sub
4585
4594
  });
4586
4595
  const accessPayload = buildAccessClaims({
4596
+ clientCertThumbprint,
4587
4597
  clientId,
4588
4598
  dpopJkt,
4589
4599
  extraClaims: { ...accessExtra, ...acr === undefined ? {} : { acr } },
@@ -4873,6 +4883,7 @@ var denyBackchannelAuth = async ({
4873
4883
  }) => decideBackchannel(config, authReqId, { status: "denied" });
4874
4884
  var exchangeBackchannelAuth = async ({
4875
4885
  authReqId,
4886
+ clientCertThumbprint,
4876
4887
  clientId,
4877
4888
  config,
4878
4889
  dpopJkt,
@@ -4901,6 +4912,7 @@ var exchangeBackchannelAuth = async ({
4901
4912
  }
4902
4913
  await config.backchannelAuthStore.deleteByAuthReqId(authReqId);
4903
4914
  const tokenSet = await issueTokenSet({
4915
+ clientCertThumbprint,
4904
4916
  clientId,
4905
4917
  config,
4906
4918
  dpopJkt,
@@ -5020,6 +5032,55 @@ var verifyJwtSignedByClient = ({
5020
5032
  client
5021
5033
  }) => verifyJwtSignedByClientImpl(client, jwt);
5022
5034
 
5035
+ // src/oidc/mtls.ts
5036
+ var RFC9440_HEADER = "client-cert";
5037
+ var SF_BINARY_PREFIX = ":";
5038
+ var SF_BINARY_SUFFIX = ":";
5039
+ var base64Decode2 = (value) => Uint8Array.from(atob(value), (char) => char.charCodeAt(0));
5040
+ var base64UrlEncode2 = (bytes) => {
5041
+ const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join("");
5042
+ return btoa(binary).replace(/\+/gu, "-").replace(/\//gu, "_").replace(/=+$/u, "");
5043
+ };
5044
+ var computeCertThumbprint = async (derBytes) => {
5045
+ const digest = await crypto.subtle.digest("SHA-256", derBytes);
5046
+ return base64UrlEncode2(new Uint8Array(digest));
5047
+ };
5048
+ var extractRfc9440ClientCert = (headers) => {
5049
+ const raw = headers.get(RFC9440_HEADER);
5050
+ if (raw === null)
5051
+ return;
5052
+ const trimmed = raw.trim();
5053
+ if (!trimmed.startsWith(SF_BINARY_PREFIX) || !trimmed.endsWith(SF_BINARY_SUFFIX) || trimmed.length <= 2) {
5054
+ return;
5055
+ }
5056
+ try {
5057
+ return base64Decode2(trimmed.slice(1, -1));
5058
+ } catch {
5059
+ return;
5060
+ }
5061
+ };
5062
+ var resolveClientCert = async ({
5063
+ extract,
5064
+ headers
5065
+ }) => {
5066
+ if (extract !== undefined)
5067
+ return extract(headers);
5068
+ return extractRfc9440ClientCert(headers);
5069
+ };
5070
+ var verifyCertificateBoundToken = async ({
5071
+ cnfThumbprint,
5072
+ extract,
5073
+ headers
5074
+ }) => {
5075
+ if (cnfThumbprint === undefined)
5076
+ return false;
5077
+ const cert = await resolveClientCert({ extract, headers });
5078
+ if (cert === undefined)
5079
+ return false;
5080
+ const presented = await computeCertThumbprint(cert);
5081
+ return presented === cnfThumbprint;
5082
+ };
5083
+
5023
5084
  // src/oidc/dpop.ts
5024
5085
  var DEFAULT_MAX_AGE_MS = 60000;
5025
5086
  var SECONDS_TO_MS = 1000;
@@ -5656,27 +5717,57 @@ var oidcProviderRoutes = (config) => {
5656
5717
  const matches = await constantTimeEqual(await hashToken(clientSecret), client.hashedSecret);
5657
5718
  return matches ? client : undefined;
5658
5719
  };
5720
+ const tryMtlsAuth = async ({
5721
+ candidate,
5722
+ extract,
5723
+ requestHeaders
5724
+ }) => {
5725
+ const registered = candidate?.tlsCertificateBoundThumbprints ?? [];
5726
+ if (candidate === undefined || registered.length === 0)
5727
+ return;
5728
+ const cert = await resolveClientCert({
5729
+ extract,
5730
+ headers: requestHeaders
5731
+ });
5732
+ if (cert === undefined)
5733
+ return;
5734
+ const presented = await computeCertThumbprint(cert);
5735
+ if (!registered.includes(presented))
5736
+ return;
5737
+ return { client: candidate, clientCertThumbprint: presented };
5738
+ };
5659
5739
  const authenticateTokenClient = async ({
5660
5740
  basicClientId,
5661
5741
  basicClientSecret,
5662
5742
  bodyClientAssertion,
5663
5743
  bodyClientAssertionType,
5664
5744
  bodyClientId,
5665
- bodyClientSecret
5745
+ bodyClientSecret,
5746
+ requestHeaders
5666
5747
  }) => {
5667
5748
  if (bodyClientAssertion !== undefined && bodyClientAssertionType === CLIENT_ASSERTION_TYPE) {
5668
- return verifyClientAssertion({
5749
+ const client2 = await verifyClientAssertion({
5669
5750
  assertion: bodyClientAssertion,
5670
5751
  expectedAudience: tokenUrl,
5671
5752
  jtiStore: config.clientAssertionJtiStore,
5672
5753
  resolveClient: clientStore.findClient
5673
5754
  });
5755
+ return client2 === undefined ? undefined : { client: client2, clientCertThumbprint: undefined };
5674
5756
  }
5675
5757
  const clientId = bodyClientId ?? basicClientId;
5676
- const clientSecret = bodyClientSecret ?? basicClientSecret;
5677
5758
  if (clientId === undefined)
5678
5759
  return;
5679
- return authenticateClient(clientId, clientSecret);
5760
+ const candidate = await clientStore.findClient(clientId);
5761
+ const mtlsResult = await tryMtlsAuth({
5762
+ candidate,
5763
+ extract: config.extractTlsClientCert,
5764
+ requestHeaders
5765
+ });
5766
+ if (mtlsResult !== undefined)
5767
+ return mtlsResult;
5768
+ const clientSecret = bodyClientSecret ?? basicClientSecret;
5769
+ const client = await authenticateClient(clientId, clientSecret);
5770
+ return client === undefined ? undefined : { client, clientCertThumbprint: undefined };
5680
5771
  };
5681
5772
  const dpopNonceChallenge = async (proof) => {
5682
5773
  if (proof === undefined || config.dpopNonce === undefined) {
@@ -5701,7 +5792,7 @@ var oidcProviderRoutes = (config) => {
5701
5792
  status: HTTP_UNAUTHORIZED2
5702
5793
  });
5703
5794
  };
5704
- const grantAuthorizationCode = async (client, body, dpop) => {
5795
+ const grantAuthorizationCode = async (client, body, dpop, clientCertThumbprint) => {
5705
5796
  const {
5706
5797
  code,
5707
5798
  code_verifier: codeVerifier,
@@ -5725,6 +5816,7 @@ var oidcProviderRoutes = (config) => {
5725
5816
  return tokenResponse(await issueTokenSet({
5726
5817
  acr: record.acr,
5727
5818
  claims: record.claims,
5819
+ clientCertThumbprint,
5728
5820
  clientId: client.clientId,
5729
5821
  config,
5730
5822
  dpopJkt: dpopResult?.jkt,
@@ -5733,7 +5825,7 @@ var oidcProviderRoutes = (config) => {
5733
5825
  sub: record.userId
5734
5826
  }));
5735
5827
  };
5736
- const grantRefreshToken = async (client, body, dpop) => {
5828
+ const grantRefreshToken = async (client, body, dpop, clientCertThumbprint) => {
5737
5829
  const presented = body.refresh_token;
5738
5830
  if (presented === undefined) {
5739
5831
  return oauthError2(HTTP_BAD_REQUEST2, "invalid_request");
@@ -5755,6 +5847,7 @@ var oidcProviderRoutes = (config) => {
5755
5847
  return tokenResponse(await issueTokenSet({
5756
5848
  acr: record.acr,
5757
5849
  claims: record.claims,
5850
+ clientCertThumbprint,
5758
5851
  clientId: client.clientId,
5759
5852
  config,
5760
5853
  dpopJkt: record.dpopJkt,
@@ -5792,7 +5885,7 @@ var oidcProviderRoutes = (config) => {
5792
5885
  token_type: dpopResult === undefined ? "Bearer" : "DPoP"
5793
5886
  }, HTTP_OK2);
5794
5887
  };
5795
- const grantBackchannel = async (client, body, dpop) => {
5888
+ const grantBackchannel = async (client, body, dpop, clientCertThumbprint) => {
5796
5889
  if (config.backchannelAuthStore === undefined) {
5797
5890
  return oauthError2(HTTP_BAD_REQUEST2, "unsupported_grant_type");
5798
5891
  }
@@ -5809,6 +5902,7 @@ var oidcProviderRoutes = (config) => {
5809
5902
  }
5810
5903
  const result = await exchangeBackchannelAuth({
5811
5904
  authReqId: body.auth_req_id,
5905
+ clientCertThumbprint,
5812
5906
  clientId: client.clientId,
5813
5907
  config,
5814
5908
  dpopJkt: dpopResult?.jkt
@@ -5885,12 +5979,14 @@ var oidcProviderRoutes = (config) => {
5885
5979
  response_types_supported: ["code"],
5886
5980
  revocation_endpoint: `${issuer}${revokeRoute}`,
5887
5981
  subject_types_supported: ["public"],
5982
+ tls_client_certificate_bound_access_tokens: true,
5888
5983
  token_endpoint: tokenUrl,
5889
5984
  token_endpoint_auth_methods_supported: [
5890
5985
  "client_secret_basic",
5891
5986
  "client_secret_post",
5892
5987
  "none",
5893
- "private_key_jwt"
5988
+ "private_key_jwt",
5989
+ "self_signed_tls_client_auth"
5894
5990
  ],
5895
5991
  token_endpoint_auth_signing_alg_values_supported: ["ES256"],
5896
5992
  userinfo_endpoint: `${issuer}${userinfoRoute}`
@@ -6097,27 +6193,29 @@ var oidcProviderRoutes = (config) => {
6097
6193
  scope: t12.Optional(t12.String()),
6098
6194
  state: t12.Optional(t12.String())
6099
6195
  })
6100
- }).post(tokenRoute, async ({ body, headers }) => {
6196
+ }).post(tokenRoute, async ({ body, headers, request }) => {
6101
6197
  const basic = readBasicAuth2(headers.authorization);
6102
- const client = await authenticateTokenClient({
6198
+ const auth = await authenticateTokenClient({
6103
6199
  basicClientId: basic.clientId,
6104
6200
  basicClientSecret: basic.clientSecret,
6105
6201
  bodyClientAssertion: body.client_assertion,
6106
6202
  bodyClientAssertionType: body.client_assertion_type,
6107
6203
  bodyClientId: body.client_id,
6108
- bodyClientSecret: body.client_secret
6204
+ bodyClientSecret: body.client_secret,
6205
+ requestHeaders: request.headers
6109
6206
  });
6110
- if (client === undefined) {
6207
+ if (auth === undefined) {
6111
6208
  return oauthError2(HTTP_UNAUTHORIZED2, "invalid_client");
6112
6209
  }
6210
+ const { client, clientCertThumbprint } = auth;
6113
6211
  const nonceChallenge = await dpopNonceChallenge(headers.dpop);
6114
6212
  if (nonceChallenge !== undefined)
6115
6213
  return nonceChallenge;
6116
6214
  if (body.grant_type === "authorization_code") {
6117
- return grantAuthorizationCode(client, body, headers.dpop);
6215
+ return grantAuthorizationCode(client, body, headers.dpop, clientCertThumbprint);
6118
6216
  }
6119
6217
  if (body.grant_type === "refresh_token") {
6120
- return grantRefreshToken(client, body, headers.dpop);
6218
+ return grantRefreshToken(client, body, headers.dpop, clientCertThumbprint);
6121
6219
  }
6122
6220
  if (body.grant_type === "urn:ietf:params:oauth:grant-type:token-exchange") {
6123
6221
  return grantTokenExchange(client, body, headers.dpop);
@@ -6126,7 +6224,7 @@ var oidcProviderRoutes = (config) => {
6126
6224
  return grantDeviceCode(client, body, headers.dpop);
6127
6225
  }
6128
6226
  if (body.grant_type === CIBA_GRANT_TYPE) {
6129
- return grantBackchannel(client, body, headers.dpop);
6227
+ return grantBackchannel(client, body, headers.dpop, clientCertThumbprint);
6130
6228
  }
6131
6229
  return oauthError2(HTTP_BAD_REQUEST2, "unsupported_grant_type");
6132
6230
  }, {
@@ -6148,22 +6246,24 @@ var oidcProviderRoutes = (config) => {
6148
6246
  subject_token: t12.Optional(t12.String()),
6149
6247
  subject_token_type: t12.Optional(t12.String())
6150
6248
  })
6151
- }).post(parRoute, async ({ body, headers }) => {
6249
+ }).post(parRoute, async ({ body, headers, request }) => {
6152
6250
  if (config.pushedAuthorizationRequestStore === undefined) {
6153
6251
  return oauthError2(HTTP_NOT_IMPLEMENTED, "unsupported_response_type");
6154
6252
  }
6155
6253
  const basic = readBasicAuth2(headers.authorization);
6156
- const client = await authenticateTokenClient({
6254
+ const auth = await authenticateTokenClient({
6157
6255
  basicClientId: basic.clientId,
6158
6256
  basicClientSecret: basic.clientSecret,
6159
6257
  bodyClientAssertion: body.client_assertion,
6160
6258
  bodyClientAssertionType: body.client_assertion_type,
6161
6259
  bodyClientId: body.client_id,
6162
- bodyClientSecret: body.client_secret
6260
+ bodyClientSecret: body.client_secret,
6261
+ requestHeaders: request.headers
6163
6262
  });
6164
- if (client === undefined) {
6263
+ if (auth === undefined) {
6165
6264
  return oauthError2(HTTP_UNAUTHORIZED2, "invalid_client");
6166
6265
  }
6266
+ const { client } = auth;
6167
6267
  const isAuthField = (key) => key === "client_assertion" || key === "client_assertion_type" || key === "client_secret";
6168
6268
  const params = Object.fromEntries(Object.entries(body).filter((entry) => typeof entry[1] === "string" && !isAuthField(entry[0])));
6169
6269
  const result = await pushAuthorizationRequest({
@@ -24240,6 +24340,7 @@ export {
24240
24340
  verifyDpopNonce,
24241
24341
  verifyCognitoSha256,
24242
24342
  verifyClientAssertion,
24343
+ verifyCertificateBoundToken,
24243
24344
  verifyAuth0Pbkdf2,
24244
24345
  verifyAuditChain,
24245
24346
  verifyApiKey,
@@ -24288,6 +24389,7 @@ export {
24288
24389
  resolveOAuthAuthorization,
24289
24390
  resolveCookieSecure,
24290
24391
  resolveClientProviderEntry,
24392
+ resolveClientCert,
24291
24393
  resolveAuthHtmxRenderers,
24292
24394
  resolveApiPrincipal,
24293
24395
  removeFromSessionRing,
@@ -24384,6 +24486,7 @@ export {
24384
24486
  fingerprintDevice,
24385
24487
  fetchUserInfo,
24386
24488
  fanOutBackchannelLogout,
24489
+ extractRfc9440ClientCert,
24387
24490
  extractPropFromIdentity,
24388
24491
  extractDpopNonceClaim,
24389
24492
  exportAuditCsv,
@@ -24543,6 +24646,7 @@ export {
24543
24646
  consumePushedRequest,
24544
24647
  consumeBackupCode,
24545
24648
  constantTimeEqual,
24649
+ computeCertThumbprint,
24546
24650
  complianceRoutes,
24547
24651
  check,
24548
24652
  buildClientProviders,
@@ -24599,5 +24703,5 @@ export {
24599
24703
  AuthIdentityConflictError
24600
24704
  };
24601
24705
 
24602
- //# debugId=6F90E4F856A11E4B64756E2164756E21
24706
+ //# debugId=1069DAAF739CAF9F64756E2164756E21
24603
24707
  //# sourceMappingURL=index.js.map