@hyper.authio/client 0.7.0 → 0.8.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/README.md CHANGED
@@ -26,15 +26,22 @@ const authio = createAuthioClient({
26
26
  | --- | --- | --- |
27
27
  | `issuer`, or `authority` + `realm` | | The realm's issuer. Must be HTTPS, except on loopback or with `requireHttps: false`. |
28
28
  | `clientId` | | Required. |
29
- | `clientAuthentication` | `private_key_jwt` with only a key, else `client_secret_basic` | Also `client_secret_post` and `none` (a public client). |
29
+ | `tokenEndpointAuthMethod` | `client_secret_basic` with a secret, `private_key_jwt` with only a key, `none` with neither | The client's authentication method in Authio, which matches it exactly: `client_secret_basic`, `client_secret_post`, `private_key_jwt` or `none` (a public client). |
30
30
  | `clientSecret` | | For `client_secret_basic` / `client_secret_post`. A function returning the secret is called for every request, so a rotated secret is used without a restart. |
31
31
  | `clientPrivateKey`, `clientPrivateKeyPath`, `clientKeyId`, `clientSigningAlgorithm` | RS256 for RSA, the curve's ES algorithm for EC | For `private_key_jwt`: PEM (PKCS#8, PKCS#1, SEC1) or JWK; the file is re-read when it changes; RSA keys need 2048 bits. |
32
32
  | `clientAssertionLifetime` | 120 s | At most 3600. |
33
- | `clientEncryptionKey`, `clientEncryptionKeyPath` | | The private half of the client's encryption key (JWK or PEM, or a function returning it; the path is Node only), to read ID tokens, UserInfo and logout tokens Authio encrypts (RSA-OAEP, RSA-OAEP-256). |
33
+ | `checks` | none | What the sign-in checks: `pkce`, `state`, `nonce` (or `none`). Only the listed checks are made. |
34
+ | `codeChallengeMethod` | `S256` | `S256` or `plain`, with the `pkce` check: one of the client's allowed PKCE methods. |
35
+ | `responseMode` | not sent | `query` or `web_message`. |
36
+ | `idTokenSignedResponseAlg` | the asymmetric algorithms the discovery document announces | The client's signing algorithm: RS/PS/ES 256/384/512, or HS256/384/512 verified with `clientSecret` (at least 32, 48 or 64 bytes, RFC 7518 §3.2). Also used for logout tokens. |
37
+ | `accessTokenSignedResponseAlg` | the asymmetric algorithms the discovery document announces | The algorithm of the client's access tokens (Authio's second signing setting), for `readAccessTokenClaims`; HS* with `clientSecret`. |
38
+ | `userinfoSignedResponseAlg` | `idTokenSignedResponseAlg` | For UserInfo answered as a signed JWT. |
39
+ | `clientEncryptionKey`, `clientEncryptionKeyPath` | | The private half of the client's encryption key (JWK or PEM, or a function returning it; the path is Node only), to read ID tokens, UserInfo and logout tokens Authio encrypts. Without it nothing is decrypted. |
40
+ | `encryptionAlgorithms` | `RSA-OAEP`, `RSA-OAEP-256` | The JWE `alg` accepted. RSA1_5 cannot be listed: Node and WebCrypto cannot decrypt it. |
41
+ | `encryptionMethods` | `A128CBC-HS256`, `A192CBC-HS384`, `A256CBC-HS512` | The JWE `enc` accepted; `A128GCM`, `A192GCM` and `A256GCM` may be listed. |
34
42
  | `allowLegacyEncryptionAlgorithms` | `false` | Accepted for parity; RSA1_5 stays refused in Node, which cannot decrypt it (`legacy_algorithm`). |
35
43
  | `redirectUri`, `postLogoutRedirectUri` | | Defaults for sign-in and sign-out requests. |
36
44
  | `clockSkew` | 30 s | Tolerated on ID tokens, logout tokens and signed UserInfo. |
37
- | `validAlgorithms` | RS*, PS*, ES* | Accepted signing algorithms for those tokens. |
38
45
  | `discoveryCacheDuration` | 3600 s | |
39
46
  | `httpTimeout` | 30 s | |
40
47
  | `tokenRefreshSkew` | 60 s | How early cached tokens are renewed (at most half their lifetime). |
@@ -45,11 +52,30 @@ when it names another issuer.
45
52
 
46
53
  Option names follow the shared `conformance/options.md`; there are no aliases for older names.
47
54
 
55
+ ## Mirror the client
56
+
57
+ The options describe the client as it is registered in Authio's console, and an option left out is off: the library
58
+ hard-codes none of it. Authio refuses a request that does not match the client and accepts what the client does not
59
+ require.
60
+
61
+ | In the console | Options |
62
+ | --- | --- |
63
+ | PKCE required, method S256 | `checks: ["pkce", "state"]` (add `"nonce"` to bind the ID token), `codeChallengeMethod: "S256"` |
64
+ | PKCE required, method plain only | `checks: ["pkce", "state"]`, `codeChallengeMethod: "plain"` |
65
+ | PKCE off | `checks: ["state"]`, or leave `checks` out to check nothing (sending PKCE anyway is accepted) |
66
+ | Authentication on, `client_secret_post` | `clientSecret`, `tokenEndpointAuthMethod: "client_secret_post"` |
67
+ | Authentication on, `private_key_jwt` | `clientPrivateKey` (or `clientPrivateKeyPath`), `clientKeyId` |
68
+ | Authentication off (public client) | no secret nor key: `tokenEndpointAuthMethod` is `none` |
69
+ | ID token signing algorithm HS256 | `clientSecret`, `idTokenSignedResponseAlg: "HS256"` |
70
+ | Access token signing algorithm HS256 | `clientSecret`, `accessTokenSignedResponseAlg: "HS256"` (an API: the verifier's `accessTokenSignedResponseAlg`) |
71
+ | Tokens encrypted with the client's key, RSA-OAEP-256 | `clientEncryptionKey`, `encryptionAlgorithms: ["RSA-OAEP-256"]` |
72
+
48
73
  ## Sign people in
49
74
 
50
75
  ```ts
51
76
  const authio = createAuthioClient({
52
- issuer, clientId: "storefront", clientAuthentication: "none", // or a secret / private key for a confidential client
77
+ issuer, clientId: "storefront", // a public client: no secret nor key, so tokenEndpointAuthMethod is none
78
+ checks: ["pkce", "state", "nonce"], codeChallengeMethod: "S256", // the client requires PKCE with S256
53
79
  redirectUri: "https://shop.example.com/callback",
54
80
  });
55
81
 
@@ -62,12 +88,15 @@ const { url, transaction } = await authio.createAuthorizationUrl({
62
88
  maxAge: 900, acrValues: "2", loginHint, uiLocales: "pt-BR", idpHint: "google",
63
89
  });
64
90
 
65
- // 2. On the callback: state and iss (RFC 9207) are checked, the code is redeemed with the PKCE verifier,
66
- // and the ID token validated (nonce, at_hash, auth_time against maxAge).
91
+ // 2. On the callback: state (with the state check) and iss (RFC 9207) are checked, the code is redeemed (with the
92
+ // PKCE verifier, with the pkce check), and the ID token validated (nonce with the nonce check, at_hash, auth_time
93
+ // against maxAge).
67
94
  const { tokens, idToken } = await authio.handleAuthorizationCallback(request.url, transaction);
68
95
  ```
69
96
 
70
- PKCE uses S256 unless `codeChallengeMethod: "plain"` is asked for. `popup: true` sets `display=popup` and
97
+ Each check generates its parameter and is verified on the callback; the transaction lists the checks it was made with,
98
+ and a `state` it did not ask for is ignored. A request may pass its own `checks`, `codeChallengeMethod` and
99
+ `responseMode`. `popup: true` sets `display=popup` and
71
100
  `response_mode=web_message`; pass the parameters the popup posted to the opener to `handleAuthorizationCallback`. A
72
101
  callback that is not for the transaction throws `AuthioCallbackError` (`reason`: `state_mismatch`, `issuer_mismatch`,
73
102
  `missing_issuer`, `missing_code`); an OAuth error Authio sent back (`login_required` after `prompt: "none"`, for one)
@@ -81,13 +110,19 @@ const logoutUrl = await authio.createEndSessionUrl({ idTokenHint, postLogoutRedi
81
110
  const logout = await authio.validateLogoutToken(form.logout_token); // { subject, sessionId, tokenId, claims }
82
111
  ```
83
112
 
84
- ID tokens are checked against the realm's JWKS (cached, re-read on an unknown `kid`): `iss`, `aud` = the client id,
113
+ ID tokens are checked against the realm's JWKS (cached, re-read on an unknown `kid`), or against `clientSecret` for
114
+ the HS* algorithm `idTokenSignedResponseAlg` names: `iss`, `aud` = the client id,
85
115
  `exp` with `clockSkew`, payload `typ` = `ID`, `azp` when there are several audiences, `nonce`, `at_hash` (hash of the
86
116
  signing algorithm) and `auth_time` against `maxAge`. An encrypted ID token is decrypted with `clientEncryptionKey`
87
117
  first. A back-channel logout token must be typed `logout+jwt`, carry the back-channel event, no `nonce`, and a `sub`
88
118
  or `sid`. Every refusal is an `AuthioTokenValidationError` whose `reason` uses the outcome names of
89
- `conformance/tokens.json` (`invalid_nonce`, `invalid_at_hash`, `invalid_logout_token`, `expired`, …). In Express,
90
- `authioBackchannelLogout` from `@hyper.authio/express` is the endpoint.
119
+ `conformance/tokens.json` (`invalid_nonce`, `invalid_at_hash`, `invalid_logout_token`, `invalid_algorithm`, `expired`,
120
+ …). In Express, `authioBackchannelLogout` from `@hyper.authio/express` is the endpoint.
121
+
122
+ `readAccessTokenClaims(accessToken)` returns the payload of an access token the client received once it is decrypted
123
+ and its signature, `iss` and `exp` verify (`undefined` for an opaque token). For front-channel logout, Authio loads the
124
+ client's front-channel logout URI in an iframe; `validateFrontChannelLogout(request.url)` checks `iss` and returns the
125
+ `sessionId` (`sid`) to end (`authioFrontchannelLogout` in `@hyper.authio/express`).
91
126
 
92
127
  ### Step-up
93
128
 
@@ -104,7 +139,7 @@ const { url, transaction } = await authio.createStepUpAuthorizationUrl(challenge
104
139
  ```ts
105
140
  await authio.tokens.clientCredentials({ scopes: ["orders:read"], resource: "https://orders.example.com" });
106
141
  await authio.tokens.authorizationCode({ code, redirectUri, codeVerifier });
107
- await authio.tokens.refresh({ refreshToken }); // store the rotated refresh_token it returns
142
+ await authio.tokens.refresh({ refreshToken, resource }); // store the rotated refresh_token it returns; resource: RFC 8707
108
143
  await authio.tokens.exchange({ subjectToken, resource }); // on-behalf-of (RFC 8693)
109
144
  await authio.tokens.password({ username, password });
110
145
  await authio.tokens.personalAccessToken({ token: "pat_…", clientId: "billing-api" }); // clientId only for a consumer's PAT
package/dist/index.cjs CHANGED
@@ -149,6 +149,8 @@ var AuthioAsymmetricAlgorithms = [
149
149
  "ES384",
150
150
  "ES512"
151
151
  ];
152
+ var AuthioHmacAlgorithms = ["HS256", "HS384", "HS512"];
153
+ var AuthioHmacMinimumSecretBytes = Object.freeze({ HS256: 32, HS384: 48, HS512: 64 });
152
154
  var AuthioDefaults = {
153
155
  DiscoveryPath: "/.well-known/openid-configuration",
154
156
  ProtectedResourceMetadataPath: "/.well-known/oauth-protected-resource",
@@ -280,6 +282,12 @@ var systemClock = {
280
282
  signal?.addEventListener("abort", onAbort, { once: true });
281
283
  })
282
284
  };
285
+ var AuthioTokenEndpointAuthMethods = [
286
+ "client_secret_basic",
287
+ "client_secret_post",
288
+ "private_key_jwt",
289
+ "none"
290
+ ];
283
291
  function resolveConnection(options) {
284
292
  const requireHttps = options.requireHttps ?? true;
285
293
  const issuer = resolveIssuer(options);
@@ -595,7 +603,9 @@ function createClientAuthenticator(settings, connection) {
595
603
  const clientId = settings.clientId?.trim();
596
604
  if (!clientId) throw new AuthioConfigurationError("The Authio clientId is required.");
597
605
  const hasKey = settings.clientPrivateKey !== void 0 || !!settings.clientPrivateKeyPath;
598
- const method = settings.clientAuthentication ?? (!settings.clientSecret && hasKey ? "private_key_jwt" : "client_secret_basic");
606
+ const method = settings.tokenEndpointAuthMethod ?? (settings.clientSecret ? "client_secret_basic" : hasKey ? "private_key_jwt" : "none");
607
+ if (!AuthioTokenEndpointAuthMethods.includes(method))
608
+ throw new AuthioConfigurationError(`The Authio tokenEndpointAuthMethod '${String(method)}' is not one of ${AuthioTokenEndpointAuthMethods.join(", ")}.`);
599
609
  if ((method === "client_secret_basic" || method === "client_secret_post") && !settings.clientSecret)
600
610
  throw new AuthioConfigurationError(`The Authio clientSecret is required with ${method}.`);
601
611
  if (method === "private_key_jwt" && !hasKey)
@@ -677,6 +687,7 @@ function createKeyLoader(settings) {
677
687
  var AuthioKeyManagementAlgorithms = ["RSA-OAEP", "RSA-OAEP-256"];
678
688
  var AuthioLegacyKeyManagementAlgorithms = ["RSA1_5"];
679
689
  var AuthioContentEncryptionAlgorithms = ["A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512"];
690
+ var AuthioSupportedContentEncryptionAlgorithms = [...AuthioContentEncryptionAlgorithms, "A128GCM", "A192GCM", "A256GCM"];
680
691
  var rsaAlgorithmIdentifier = [48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0];
681
692
  function isAuthioTokenEncrypted(token) {
682
693
  return typeof token === "string" && token.split(".").length === 5;
@@ -684,6 +695,13 @@ function isAuthioTokenEncrypted(token) {
684
695
  function createAuthioTokenDecrypter(key, options = {}) {
685
696
  const settings = typeof options === "string" ? { setting: options } : options;
686
697
  const setting = settings.setting ?? "clientEncryptionKey";
698
+ const keyManagementAlgorithms = resolveAllowlist(settings.encryptionAlgorithms, AuthioKeyManagementAlgorithms, AuthioKeyManagementAlgorithms, "encryptionAlgorithms");
699
+ const contentEncryptionAlgorithms = resolveAllowlist(
700
+ settings.encryptionMethods,
701
+ AuthioContentEncryptionAlgorithms,
702
+ AuthioSupportedContentEncryptionAlgorithms,
703
+ "encryptionMethods"
704
+ );
687
705
  const imported = /* @__PURE__ */ new Map();
688
706
  const importFor = (algorithm) => {
689
707
  let pending = imported.get(algorithm);
@@ -702,10 +720,16 @@ function createAuthioTokenDecrypter(key, options = {}) {
702
720
  "legacy_algorithm",
703
721
  settings.allowLegacyEncryptionAlgorithms ? "The token is encrypted with RSA1_5 (PKCS#1 v1.5), which Node and WebCrypto cannot decrypt even with allowLegacyEncryptionAlgorithms; register RSA-OAEP-256 as the client's encryption algorithm in Authio." : "The token is encrypted with the legacy RSA1_5 algorithm, which is refused; register RSA-OAEP-256 as the client's encryption algorithm in Authio."
704
722
  );
705
- if (!AuthioKeyManagementAlgorithms.includes(algorithm))
723
+ if (!keyManagementAlgorithms.includes(algorithm))
724
+ throw new AuthioTokenValidationError(
725
+ "decryption_failed",
726
+ `The token is encrypted with '${algorithm}', and encryptionAlgorithms accepts ${keyManagementAlgorithms.join(" or ")}.`
727
+ );
728
+ const method = contentMethodOf(token);
729
+ if (!contentEncryptionAlgorithms.includes(method))
706
730
  throw new AuthioTokenValidationError(
707
731
  "decryption_failed",
708
- `The token is encrypted with '${algorithm}'; Authio encrypts with ${AuthioKeyManagementAlgorithms.join(" or ")}.`
732
+ `The token's content is encrypted with '${method}', and encryptionMethods accepts ${contentEncryptionAlgorithms.join(" or ")}.`
709
733
  );
710
734
  if (key === void 0 || key === null || key === "")
711
735
  throw new AuthioTokenValidationError(
@@ -714,8 +738,8 @@ function createAuthioTokenDecrypter(key, options = {}) {
714
738
  );
715
739
  try {
716
740
  const { plaintext } = await jose.compactDecrypt(token, (header) => importFor(header.alg), {
717
- keyManagementAlgorithms: [...AuthioKeyManagementAlgorithms],
718
- contentEncryptionAlgorithms: [...AuthioContentEncryptionAlgorithms]
741
+ keyManagementAlgorithms: [...keyManagementAlgorithms],
742
+ contentEncryptionAlgorithms: [...contentEncryptionAlgorithms]
719
743
  });
720
744
  return new TextDecoder().decode(plaintext);
721
745
  } catch (error) {
@@ -749,6 +773,24 @@ function algorithmOf(token) {
749
773
  throw new AuthioTokenValidationError("decryption_failed", "The encrypted token's header could not be read.", { cause: error });
750
774
  }
751
775
  }
776
+ function contentMethodOf(token) {
777
+ try {
778
+ return String(jose.decodeProtectedHeader(token).enc ?? "");
779
+ } catch (error) {
780
+ throw new AuthioTokenValidationError("decryption_failed", "The encrypted token's header could not be read.", { cause: error });
781
+ }
782
+ }
783
+ function resolveAllowlist(configured, defaults, supported, setting) {
784
+ if (configured === void 0 || configured.length === 0) return defaults;
785
+ for (const value of configured) {
786
+ if (AuthioLegacyKeyManagementAlgorithms.includes(value))
787
+ throw new AuthioConfigurationError(
788
+ `${setting} cannot hold ${value}: Node and WebCrypto cannot decrypt PKCS#1 v1.5; register RSA-OAEP-256 as the client's encryption algorithm in Authio.`
789
+ );
790
+ if (!supported.includes(value)) throw new AuthioConfigurationError(`${setting} holds '${value}'; it accepts ${supported.join(", ")}.`);
791
+ }
792
+ return [...new Set(configured)];
793
+ }
752
794
  async function importDecryptionKey(key, algorithm, setting) {
753
795
  try {
754
796
  if (typeof key !== "string") return await importPrivateJwk(key, algorithm, setting);
@@ -805,7 +847,7 @@ async function verifyAuthioJwt(token, keys, expected) {
805
847
  try {
806
848
  const { payload, protectedHeader } = await jose.jwtVerify(token, keys, {
807
849
  issuer: expected.issuer,
808
- audience: [...expected.audiences],
850
+ audience: expected.audiences ? [...expected.audiences] : void 0,
809
851
  algorithms: [...expected.algorithms && expected.algorithms.length > 0 ? expected.algorithms : AuthioAsymmetricAlgorithms],
810
852
  clockTolerance: expected.clockSkew ?? AuthioDefaults.ClockSkew,
811
853
  requiredClaims: [.../* @__PURE__ */ new Set(["exp", ...expected.requiredClaims ?? []])],
@@ -813,9 +855,34 @@ async function verifyAuthioJwt(token, keys, expected) {
813
855
  });
814
856
  return { payload, header: protectedHeader };
815
857
  } catch (error) {
816
- throw toTokenValidationError(error, expected.audiences, token);
858
+ if (error instanceof AuthioConfigurationError) throw error;
859
+ throw toTokenValidationError(error, expected.audiences ?? [], token);
817
860
  }
818
861
  }
862
+ function withClientSecret(keys, secret) {
863
+ if (!secret) return keys;
864
+ return async (header, token) => {
865
+ const algorithm = String(header.alg);
866
+ if (!AuthioHmacAlgorithms.includes(algorithm)) return keys(header, token);
867
+ const bytes = new TextEncoder().encode(await secret());
868
+ requireHmacSecretLength(algorithm, bytes.length);
869
+ return bytes;
870
+ };
871
+ }
872
+ function requireHmacSecretLength(algorithm, length) {
873
+ const minimum = AuthioHmacMinimumSecretBytes[algorithm];
874
+ if (minimum !== void 0 && length < minimum)
875
+ throw new AuthioConfigurationError(
876
+ `The Authio clientSecret has ${length} bytes, and ${algorithm} needs at least ${minimum} (RFC 7518 \xA73.2): Authio refuses a shorter secret for it.`
877
+ );
878
+ }
879
+ function acceptedSigningAlgorithms(configured, announced) {
880
+ if (configured) return [configured];
881
+ const asymmetric = (Array.isArray(announced) ? announced : []).filter(
882
+ (algorithm) => typeof algorithm === "string" && AuthioAsymmetricAlgorithms.includes(algorithm)
883
+ );
884
+ return asymmetric.length > 0 ? asymmetric : AuthioAsymmetricAlgorithms;
885
+ }
819
886
  function toTokenValidationError(error, audiences, token) {
820
887
  if (error instanceof AuthioTokenValidationError) return error;
821
888
  const refuse = (reason, message) => new AuthioTokenValidationError(reason, message, { cause: error });
@@ -947,6 +1014,7 @@ function toBase64Url(bytes) {
947
1014
  }
948
1015
 
949
1016
  // src/authorization.ts
1017
+ var AuthioChecks = ["pkce", "state", "nonce", "none"];
950
1018
  async function createAuthioPkce(method = "S256") {
951
1019
  if (method !== "S256" && method !== "plain") throw new TypeError(`The PKCE method '${String(method)}' is not S256 or plain.`);
952
1020
  const codeVerifier = randomValue(32);
@@ -956,14 +1024,26 @@ async function createAuthioPkce(method = "S256") {
956
1024
  function randomValue(bytes = 32) {
957
1025
  return toBase64Url2(crypto.getRandomValues(new Uint8Array(bytes)));
958
1026
  }
1027
+ function resolveChecks(checks, setting = "checks") {
1028
+ const resolved = [];
1029
+ for (const check of checks ?? []) {
1030
+ if (!AuthioChecks.includes(check))
1031
+ throw new AuthioConfigurationError(`The Authio ${setting} value '${String(check)}' is not one of ${AuthioChecks.join(", ")}.`);
1032
+ if (check !== "none" && !resolved.includes(check)) resolved.push(check);
1033
+ }
1034
+ return Object.freeze(resolved);
1035
+ }
959
1036
  async function buildAuthorizationUrl(endpoint, clientId, request) {
960
- const pkce = await createAuthioPkce(request.codeChallengeMethod ?? "S256");
961
- const state = request.state?.trim() || randomValue();
962
- const nonce = request.nonce?.trim() || randomValue();
1037
+ const checks = resolveChecks(request.checks);
1038
+ const pkce = checks.includes("pkce") ? await createAuthioPkce(request.codeChallengeMethod ?? "S256") : void 0;
1039
+ const state = checks.includes("state") ? request.state?.trim() || randomValue() : void 0;
1040
+ const nonce = checks.includes("nonce") ? request.nonce?.trim() || randomValue() : void 0;
963
1041
  if (request.maxAge !== void 0 && (!Number.isInteger(request.maxAge) || request.maxAge < 0))
964
1042
  throw new RangeError("maxAge must be a non-negative whole number of seconds.");
965
1043
  const display = request.popup ? "popup" : request.display;
966
1044
  const responseMode = request.popup ? "web_message" : request.responseMode;
1045
+ if (responseMode !== void 0 && responseMode !== "query" && responseMode !== "web_message")
1046
+ throw new AuthioConfigurationError(`The Authio responseMode '${String(responseMode)}' is not query or web_message.`);
967
1047
  const url = new URL(endpoint);
968
1048
  const parameters = {
969
1049
  ...request.extraParameters,
@@ -971,10 +1051,10 @@ async function buildAuthorizationUrl(endpoint, clientId, request) {
971
1051
  client_id: clientId,
972
1052
  redirect_uri: request.redirectUri,
973
1053
  scope: toScopeString(request.scopes) ?? "openid",
974
- state,
975
- nonce,
976
- code_challenge: pkce.codeChallenge,
977
- code_challenge_method: pkce.codeChallengeMethod,
1054
+ state: state ?? request.state?.trim(),
1055
+ nonce: nonce ?? request.nonce?.trim(),
1056
+ code_challenge: pkce?.codeChallenge,
1057
+ code_challenge_method: pkce?.codeChallengeMethod,
978
1058
  resource: request.resource,
979
1059
  organization: request.organization,
980
1060
  prompt: joined(request.prompt),
@@ -991,10 +1071,10 @@ async function buildAuthorizationUrl(endpoint, clientId, request) {
991
1071
  return {
992
1072
  url: url.toString(),
993
1073
  transaction: Object.freeze({
994
- state,
995
- nonce,
996
- codeVerifier: pkce.codeVerifier,
997
- codeChallengeMethod: pkce.codeChallengeMethod,
1074
+ checks,
1075
+ ...state ? { state } : {},
1076
+ ...nonce ? { nonce } : {},
1077
+ ...pkce ? { codeVerifier: pkce.codeVerifier, codeChallengeMethod: pkce.codeChallengeMethod } : {},
998
1078
  redirectUri: request.redirectUri,
999
1079
  ...request.resource ? { resource: request.resource } : {},
1000
1080
  ...request.maxAge !== void 0 ? { maxAge: request.maxAge } : {}
@@ -1016,6 +1096,16 @@ function readCallbackParameters(callback) {
1016
1096
  }
1017
1097
  return parameters;
1018
1098
  }
1099
+ function readAuthioFrontChannelLogout(request, issuer) {
1100
+ const parameters = readCallbackParameters(request);
1101
+ const sessionId = parameters.sid?.trim() || void 0;
1102
+ const sentIssuer = parameters.iss?.trim() || void 0;
1103
+ if (sentIssuer !== void 0 && sentIssuer.replace(/\/+$/, "") !== issuer.replace(/\/+$/, ""))
1104
+ throw new AuthioCallbackError("issuer_mismatch", `The front-channel logout comes from the issuer '${sentIssuer}', not '${issuer}'.`);
1105
+ if (sessionId !== void 0 && sentIssuer === void 0)
1106
+ throw new AuthioCallbackError("missing_issuer", "The front-channel logout names a sid without its iss.");
1107
+ return Object.freeze({ sessionId, issuer: sentIssuer });
1108
+ }
1019
1109
  function parseAuthioBearerChallenge(header) {
1020
1110
  if (!header || !/^\s*bearer(\s|$)/i.test(header)) return void 0;
1021
1111
  const values = {};
@@ -1051,22 +1141,35 @@ function createAuthioClient(options) {
1051
1141
  const authenticator = createClientAuthenticator(options, connection);
1052
1142
  const refreshSkewMs = seconds(options.tokenRefreshSkew, AuthioDefaults.TokenRefreshSkew, "tokenRefreshSkew");
1053
1143
  const clockSkew = seconds(options.clockSkew, AuthioDefaults.ClockSkew, "clockSkew") / 1e3;
1054
- const decrypt = createAuthioTokenDecrypter(resolveEncryptionKey(options), {
1144
+ const encryptionKey = resolveEncryptionKey(options);
1145
+ const decrypt = createAuthioTokenDecrypter(encryptionKey, {
1055
1146
  setting: options.clientEncryptionKey === void 0 && options.clientEncryptionKeyPath ? "clientEncryptionKeyPath" : "clientEncryptionKey",
1056
- allowLegacyEncryptionAlgorithms: options.allowLegacyEncryptionAlgorithms
1147
+ allowLegacyEncryptionAlgorithms: options.allowLegacyEncryptionAlgorithms,
1148
+ encryptionAlgorithms: options.encryptionAlgorithms,
1149
+ encryptionMethods: options.encryptionMethods
1057
1150
  });
1151
+ const checks = resolveChecks(options.checks);
1152
+ const codeChallengeMethod = resolveCodeChallengeMethod(options.codeChallengeMethod);
1153
+ const responseMode = resolveResponseMode(options.responseMode);
1154
+ const idTokenAlgorithm = resolveSigningAlgorithm(options.idTokenSignedResponseAlg, "idTokenSignedResponseAlg", options.clientSecret);
1155
+ const userInfoAlgorithm = resolveSigningAlgorithm(options.userinfoSignedResponseAlg, "userinfoSignedResponseAlg", options.clientSecret) ?? idTokenAlgorithm;
1156
+ const accessTokenAlgorithm = resolveSigningAlgorithm(options.accessTokenSignedResponseAlg, "accessTokenSignedResponseAlg", options.clientSecret);
1157
+ const clientSecret = options.clientSecret === void 0 ? void 0 : () => readSecret(options.clientSecret);
1058
1158
  const signingKeys = createAuthioSigningKeys(connection, discovery);
1059
1159
  const tokens = createTokenClient(connection, discovery, authenticator);
1060
1160
  const clientCredentials = createClientCredentialsCache(connection, authenticator.clientId, tokens, refreshSkewMs);
1061
- const validationContext = async (signal) => ({
1062
- keys: await signingKeys(signal),
1063
- decrypt,
1064
- issuer: connection.issuer,
1065
- clientId: authenticator.clientId,
1066
- algorithms: options.validAlgorithms,
1067
- clockSkew,
1068
- now: connection.clock.now()
1069
- });
1161
+ const validationContext = async (signal) => {
1162
+ const document = await discovery.get(signal);
1163
+ return {
1164
+ keys: withClientSecret(await signingKeys(signal), clientSecret),
1165
+ decrypt,
1166
+ issuer: connection.issuer,
1167
+ clientId: authenticator.clientId,
1168
+ algorithms: acceptedSigningAlgorithms(idTokenAlgorithm, document.id_token_signing_alg_values_supported),
1169
+ clockSkew,
1170
+ now: connection.clock.now()
1171
+ };
1172
+ };
1070
1173
  const validateIdToken = async (idToken, expectations = {}) => {
1071
1174
  requireValue(idToken, "idToken");
1072
1175
  return validateAuthioIdToken(idToken, await validationContext(expectations.signal), expectations);
@@ -1075,7 +1178,13 @@ function createAuthioClient(options) {
1075
1178
  const redirectUri = request.redirectUri ?? options.redirectUri;
1076
1179
  requireValue(redirectUri, "redirectUri");
1077
1180
  const document = await discovery.get(request.signal);
1078
- return buildAuthorizationUrl(requireEndpoint(document, "authorization_endpoint"), authenticator.clientId, { ...request, redirectUri });
1181
+ return buildAuthorizationUrl(requireEndpoint(document, "authorization_endpoint"), authenticator.clientId, {
1182
+ ...request,
1183
+ redirectUri,
1184
+ checks: request.checks ?? checks,
1185
+ codeChallengeMethod: request.codeChallengeMethod ?? codeChallengeMethod,
1186
+ responseMode: request.responseMode ?? responseMode
1187
+ });
1079
1188
  };
1080
1189
  return {
1081
1190
  issuer: connection.issuer,
@@ -1119,12 +1228,19 @@ function createAuthioClient(options) {
1119
1228
  );
1120
1229
  const mediaType = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
1121
1230
  if (response.ok && mediaType === "application/jwt")
1122
- return readUserInfoJwt((await response.text()).trim(), document, connection, authenticator.clientId, decrypt, signingKeys, {
1123
- algorithms: options.validAlgorithms,
1124
- clockSkew,
1125
- status: response.status,
1126
- signal: callOptions.signal
1127
- });
1231
+ return readUserInfoJwt(
1232
+ (await response.text()).trim(),
1233
+ document,
1234
+ connection,
1235
+ authenticator.clientId,
1236
+ decrypt,
1237
+ withClientSecret(await signingKeys(callOptions.signal), clientSecret),
1238
+ {
1239
+ algorithms: acceptedSigningAlgorithms(userInfoAlgorithm, document.userinfo_signing_alg_values_supported ?? document.id_token_signing_alg_values_supported),
1240
+ clockSkew,
1241
+ status: response.status
1242
+ }
1243
+ );
1128
1244
  if (!response.ok) {
1129
1245
  const body = await response.text();
1130
1246
  const challenge = toErrorResponse(body) ? void 0 : readBearerChallenge(response);
@@ -1134,9 +1250,10 @@ function createAuthioClient(options) {
1134
1250
  },
1135
1251
  createAuthorizationUrl,
1136
1252
  async handleAuthorizationCallback(callback, transaction, callOptions = {}) {
1137
- if (!transaction?.state || !transaction.redirectUri) throw new TypeError("transaction is required: keep what createAuthorizationUrl returned.");
1253
+ if (!transaction?.redirectUri || !Array.isArray(transaction.checks))
1254
+ throw new TypeError("transaction is required: keep what createAuthorizationUrl returned.");
1138
1255
  const parameters = readCallbackParameters(callback);
1139
- if (parameters.state !== transaction.state)
1256
+ if (transaction.checks.includes("state") && (!transaction.state || parameters.state !== transaction.state))
1140
1257
  throw new AuthioCallbackError("state_mismatch", "The callback's state is not the one this sign-in was started with.");
1141
1258
  const document = await discovery.get(callOptions.signal);
1142
1259
  if (parameters.iss !== void 0) {
@@ -1150,12 +1267,12 @@ function createAuthioClient(options) {
1150
1267
  const answer = await tokens.authorizationCode({
1151
1268
  code: parameters.code,
1152
1269
  redirectUri: transaction.redirectUri,
1153
- codeVerifier: transaction.codeVerifier,
1270
+ codeVerifier: transaction.checks.includes("pkce") ? transaction.codeVerifier : void 0,
1154
1271
  resource: transaction.resource,
1155
1272
  signal: callOptions.signal
1156
1273
  });
1157
1274
  const idToken = answer.id_token ? await validateIdToken(answer.id_token, {
1158
- nonce: transaction.nonce,
1275
+ nonce: transaction.checks.includes("nonce") ? transaction.nonce : void 0,
1159
1276
  accessToken: answer.access_token,
1160
1277
  maxAge: transaction.maxAge,
1161
1278
  signal: callOptions.signal
@@ -1167,6 +1284,23 @@ function createAuthioClient(options) {
1167
1284
  requireValue(logoutToken, "logoutToken");
1168
1285
  return validateAuthioLogoutToken(logoutToken, await validationContext(callOptions.signal));
1169
1286
  },
1287
+ validateFrontChannelLogout(request) {
1288
+ return readAuthioFrontChannelLogout(request, connection.issuer);
1289
+ },
1290
+ async readAccessTokenClaims(accessToken, callOptions = {}) {
1291
+ requireValue(accessToken, "accessToken");
1292
+ if (isAuthioTokenEncrypted(accessToken) && encryptionKey === void 0) return void 0;
1293
+ const signed = await decrypt(accessToken);
1294
+ if (signed.split(".").length !== 3) return void 0;
1295
+ const document = await discovery.get(callOptions.signal);
1296
+ const { payload } = await verifyAuthioJwt(signed, withClientSecret(await signingKeys(callOptions.signal), clientSecret), {
1297
+ issuer: connection.issuer,
1298
+ algorithms: acceptedSigningAlgorithms(accessTokenAlgorithm, document.id_token_signing_alg_values_supported),
1299
+ clockSkew,
1300
+ now: connection.clock.now()
1301
+ });
1302
+ return payload;
1303
+ },
1170
1304
  async createEndSessionUrl(request = {}) {
1171
1305
  const document = await discovery.get(request.signal);
1172
1306
  const url = new URL(requireEndpoint(document, "end_session_endpoint"));
@@ -1258,7 +1392,7 @@ function createTokenClient(connection, discovery, authenticator) {
1258
1392
  refresh: (options) => {
1259
1393
  requireValue(options?.refreshToken, "refreshToken");
1260
1394
  return requestToken(
1261
- { grant_type: AuthioGrantTypes.RefreshToken, refresh_token: options.refreshToken, scope: toScopeString(options.scopes) },
1395
+ { grant_type: AuthioGrantTypes.RefreshToken, refresh_token: options.refreshToken, scope: toScopeString(options.scopes), resource: options.resource },
1262
1396
  options.signal
1263
1397
  );
1264
1398
  },
@@ -1453,10 +1587,10 @@ async function postForm(connection, discovery, authenticator, endpoint, paramete
1453
1587
  await authenticator?.apply(form, headers);
1454
1588
  return send(connection, url, { method: "POST", headers, body: form.toString() }, signal);
1455
1589
  }
1456
- async function readUserInfoJwt(jwt, document, connection, clientId, decrypt, signingKeys, settings) {
1590
+ async function readUserInfoJwt(jwt, document, connection, clientId, decrypt, keys, settings) {
1457
1591
  try {
1458
1592
  const signed = isAuthioTokenEncrypted(jwt) ? await decrypt(jwt) : jwt;
1459
- const { payload } = await verifyAuthioJwt(signed, await signingKeys(settings.signal), {
1593
+ const { payload } = await verifyAuthioJwt(signed, keys, {
1460
1594
  issuer: document.issuer.replace(/\/+$/, ""),
1461
1595
  audiences: [clientId],
1462
1596
  algorithms: settings.algorithms,
@@ -1488,6 +1622,33 @@ function readBearerChallenge(response) {
1488
1622
  }
1489
1623
  return error ? { error, description } : void 0;
1490
1624
  }
1625
+ function resolveCodeChallengeMethod(method) {
1626
+ if (method !== void 0 && method !== "S256" && method !== "plain")
1627
+ throw new AuthioConfigurationError(`The Authio codeChallengeMethod '${String(method)}' is not S256 or plain.`);
1628
+ return method;
1629
+ }
1630
+ function resolveResponseMode(mode) {
1631
+ if (mode !== void 0 && mode !== "query" && mode !== "web_message")
1632
+ throw new AuthioConfigurationError(`The Authio responseMode '${String(mode)}' is not query or web_message.`);
1633
+ return mode;
1634
+ }
1635
+ function resolveSigningAlgorithm(algorithm, setting, clientSecret) {
1636
+ if (algorithm === void 0 || algorithm === "") return void 0;
1637
+ const symmetric = AuthioHmacAlgorithms.includes(algorithm);
1638
+ if (!symmetric && !AuthioAsymmetricAlgorithms.includes(algorithm))
1639
+ throw new AuthioConfigurationError(
1640
+ `The Authio ${setting} '${algorithm}' is not one of ${[...AuthioAsymmetricAlgorithms, ...AuthioHmacAlgorithms].join(", ")}.`
1641
+ );
1642
+ if (symmetric && !clientSecret)
1643
+ throw new AuthioConfigurationError(`The Authio ${setting} ${algorithm} is verified with the client secret (OpenID Connect Core \xA710.1): set clientSecret.`);
1644
+ if (symmetric && typeof clientSecret === "string") requireHmacSecretLength(algorithm, new TextEncoder().encode(clientSecret).length);
1645
+ return algorithm;
1646
+ }
1647
+ async function readSecret(source) {
1648
+ const value = typeof source === "function" ? await source() : source;
1649
+ if (typeof value !== "string" || !value) throw new AuthioConfigurationError("The Authio clientSecret function returned no secret.");
1650
+ return value;
1651
+ }
1491
1652
  function requireValue(value, name) {
1492
1653
  if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} is required.`);
1493
1654
  }
@@ -1540,7 +1701,7 @@ function createAuthioAccountCredential(options) {
1540
1701
  realm: AuthioDefaults.PlatformRealm,
1541
1702
  clientId: kind === "client_secret" ? toCredentialClientId(options.credentialId) : AuthioDefaults.PlatformClientId,
1542
1703
  clientSecret: kind === "client_secret" ? value : void 0,
1543
- clientAuthentication: kind === "client_secret" ? "client_secret_post" : "none"
1704
+ tokenEndpointAuthMethod: kind === "client_secret" ? "client_secret_post" : "none"
1544
1705
  });
1545
1706
  }
1546
1707
  const exchange = async (signal) => {
@@ -1917,6 +2078,7 @@ exports.AuthioAdminError = AuthioAdminError;
1917
2078
  exports.AuthioAsymmetricAlgorithms = AuthioAsymmetricAlgorithms;
1918
2079
  exports.AuthioCallbackError = AuthioCallbackError;
1919
2080
  exports.AuthioCallerKinds = AuthioCallerKinds;
2081
+ exports.AuthioChecks = AuthioChecks;
1920
2082
  exports.AuthioClaimTypes = AuthioClaimTypes;
1921
2083
  exports.AuthioClientAssertionAlgorithms = AuthioClientAssertionAlgorithms;
1922
2084
  exports.AuthioConfigurationError = AuthioConfigurationError;
@@ -1925,6 +2087,8 @@ exports.AuthioDefaults = AuthioDefaults;
1925
2087
  exports.AuthioDiscoveryError = AuthioDiscoveryError;
1926
2088
  exports.AuthioError = AuthioError;
1927
2089
  exports.AuthioGrantTypes = AuthioGrantTypes;
2090
+ exports.AuthioHmacAlgorithms = AuthioHmacAlgorithms;
2091
+ exports.AuthioHmacMinimumSecretBytes = AuthioHmacMinimumSecretBytes;
1928
2092
  exports.AuthioKeyManagementAlgorithms = AuthioKeyManagementAlgorithms;
1929
2093
  exports.AuthioLegacyKeyManagementAlgorithms = AuthioLegacyKeyManagementAlgorithms;
1930
2094
  exports.AuthioOAuthErrors = AuthioOAuthErrors;
@@ -1933,6 +2097,8 @@ exports.AuthioProtocolError = AuthioProtocolError;
1933
2097
  exports.AuthioResourceAccess = AuthioResourceAccess;
1934
2098
  exports.AuthioResourceAccessClaims = AuthioResourceAccessClaims;
1935
2099
  exports.AuthioScopes = AuthioScopes;
2100
+ exports.AuthioSupportedContentEncryptionAlgorithms = AuthioSupportedContentEncryptionAlgorithms;
2101
+ exports.AuthioTokenEndpointAuthMethods = AuthioTokenEndpointAuthMethods;
1936
2102
  exports.AuthioTokenTypes = AuthioTokenTypes;
1937
2103
  exports.AuthioTokenValidationError = AuthioTokenValidationError;
1938
2104
  exports.AuthioWebhookEventTypes = AuthioWebhookEventTypes;
@@ -1969,7 +2135,9 @@ exports.parseResourceAccessClaim = parseResourceAccessClaim;
1969
2135
  exports.parseWebhookEvent = parseWebhookEvent;
1970
2136
  exports.randomValue = randomValue;
1971
2137
  exports.readAuthioAdminResponse = readAuthioAdminResponse;
2138
+ exports.readAuthioFrontChannelLogout = readAuthioFrontChannelLogout;
1972
2139
  exports.readWebhookEventData = readWebhookEventData;
2140
+ exports.requireHmacSecretLength = requireHmacSecretLength;
1973
2141
  exports.resolveConnection = resolveConnection;
1974
2142
  exports.resolveIssuer = resolveIssuer;
1975
2143
  exports.resourceAccessSourceOf = resourceAccessSourceOf;
@@ -1986,5 +2154,6 @@ exports.toTokenValidationError = toTokenValidationError;
1986
2154
  exports.tryParseWebhookEvent = tryParseWebhookEvent;
1987
2155
  exports.verifyAuthioJwt = verifyAuthioJwt;
1988
2156
  exports.verifyWebhookSignature = verifyWebhookSignature;
2157
+ exports.withClientSecret = withClientSecret;
1989
2158
  //# sourceMappingURL=index.cjs.map
1990
2159
  //# sourceMappingURL=index.cjs.map