@apoa/core 0.1.0 → 0.2.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 ADDED
@@ -0,0 +1,92 @@
1
+ # @apoa/core
2
+
3
+ Reference TypeScript SDK for the [Agentic Power of Attorney (APOA)](https://github.com/agenticpoa/apoa) standard -- authorization infrastructure for AI agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @apoa/core
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { createToken, checkScope, generateKeyPair, createClient } from '@apoa/core';
15
+
16
+ // Generate keys and create a client
17
+ const keys = await generateKeyPair();
18
+ const client = createClient({ defaultSigningOptions: { privateKey: keys.privateKey } });
19
+
20
+ // Create a signed authorization token
21
+ const token = await client.createToken({
22
+ principal: { id: "did:apoa:you" },
23
+ agent: { id: "did:apoa:your-agent", name: "HomeBot Pro" },
24
+ services: [{
25
+ service: "nationwidemortgage.com",
26
+ scopes: ["rate_lock:read", "documents:read"],
27
+ constraints: { signing: false },
28
+ accessMode: "browser",
29
+ browserConfig: {
30
+ allowedUrls: ["https://portal.nationwidemortgage.com/*"],
31
+ credentialVaultRef: "1password://vault/mortgage-portal",
32
+ },
33
+ }],
34
+ rules: [{ id: "no-signing", description: "Never sign anything", enforcement: "hard" }],
35
+ expires: "2026-09-01",
36
+ });
37
+
38
+ // Authorize actions
39
+ const result = await client.authorize(token, "nationwidemortgage.com", "rate_lock:read");
40
+ // { authorized: true, checks: { revoked: false, scopeAllowed: true, ... } }
41
+
42
+ const denied = await client.authorize(token, "nationwidemortgage.com", "documents:sign");
43
+ // { authorized: false, reason: "scope 'documents:sign' not in authorized scopes" }
44
+ ```
45
+
46
+ ## Features
47
+
48
+ - **Token lifecycle**: create, sign (Ed25519/ES256), validate, parse
49
+ - **Scope matching**: hierarchical patterns (`appointments:*` matches `appointments:read`)
50
+ - **Constraint enforcement**: boolean denial checks
51
+ - **Authorization**: revocation + scope + constraints + hard/soft rules in one call
52
+ - **Delegation chains**: capability attenuation (permissions only narrow, never expand)
53
+ - **Cascade revocation**: revoke parent, all children die instantly
54
+ - **Audit trail**: append-only action log per token
55
+ - **Browser mode**: credential vault injection config (the AI never sees passwords)
56
+ - **Comprehensive test suite** with cross-SDK fixture verification against the [Python SDK](https://pypi.org/project/apoa/)
57
+
58
+ ## Two Usage Styles
59
+
60
+ ```typescript
61
+ // Style 1: Client instance (recommended for apps)
62
+ const client = createClient({
63
+ revocationStore: new MemoryRevocationStore(),
64
+ auditStore: new MemoryAuditStore(),
65
+ defaultSigningOptions: { privateKey: keys.privateKey },
66
+ });
67
+ await client.authorize(token, "service.com", "action:read");
68
+
69
+ // Style 2: Standalone imports (for scripts and tests)
70
+ import { checkScope, authorize, createToken } from '@apoa/core';
71
+ checkScope(token, "service.com", "action:read");
72
+ ```
73
+
74
+ ## Cross-SDK Compatibility
75
+
76
+ Tokens created by `@apoa/core` validate in the [Python SDK](https://pypi.org/project/apoa/) and vice versa. The camelCase JWT payload round-trips correctly across both SDKs.
77
+
78
+ ## Ecosystem
79
+
80
+ - [`@apoa/mcp`](https://www.npmjs.com/package/@apoa/mcp) -- APOA authorization for MCP servers
81
+ - [`@apoa/a2a`](https://github.com/agenticpoa/apoa-a2a) -- APOA authorization for A2A agent-to-agent communication
82
+ - [`apoa`](https://pypi.org/project/apoa/) -- Python SDK
83
+
84
+ ## Links
85
+
86
+ - [Spec](https://github.com/agenticpoa/apoa/blob/main/SPEC.md)
87
+ - [Source](https://github.com/agenticpoa/apoa/tree/main/sdks/typescript)
88
+ - [Examples](https://github.com/agenticpoa/apoa/tree/main/sdks/typescript/examples)
89
+
90
+ ## License
91
+
92
+ Apache-2.0
package/dist/index.cjs CHANGED
@@ -42,10 +42,12 @@ __export(index_exports, {
42
42
  ScopeViolationError: () => ScopeViolationError,
43
43
  TokenExpiredError: () => TokenExpiredError,
44
44
  authorize: () => authorize,
45
+ buildJWKS: () => buildJWKS,
45
46
  cascadeRevoke: () => cascadeRevoke,
46
47
  checkConstraint: () => checkConstraint,
47
48
  checkScope: () => checkScope,
48
49
  createClient: () => createClient,
50
+ createJWKSResolver: () => createJWKSResolver,
49
51
  createToken: () => createToken,
50
52
  decodeHeader: () => decodeHeader,
51
53
  delegate: () => delegate,
@@ -59,6 +61,7 @@ __export(index_exports, {
59
61
  matchScope: () => matchScope,
60
62
  parseDefinition: () => parseDefinition,
61
63
  parseScope: () => parseScope,
64
+ publicKeyToJWK: () => publicKeyToJWK,
62
65
  revoke: () => revoke,
63
66
  sign: () => sign,
64
67
  signToken: () => signToken,
@@ -208,12 +211,14 @@ function parseScope(scope) {
208
211
  return scope.split(":");
209
212
  }
210
213
  function matchScope(pattern, requested) {
214
+ if (!pattern || !requested) return false;
211
215
  if (pattern === "*") return true;
212
216
  const patternParts = parseScope(pattern);
213
217
  const requestedParts = parseScope(requested);
214
218
  if (patternParts.length !== requestedParts.length) return false;
215
219
  for (let i = 0; i < patternParts.length; i++) {
216
220
  if (patternParts[i] === "*") continue;
221
+ if (!patternParts[i] || !requestedParts[i]) return false;
217
222
  if (patternParts[i] !== requestedParts[i]) return false;
218
223
  }
219
224
  return true;
@@ -379,8 +384,8 @@ async function authorize(token, service, action, options) {
379
384
  for (const rule of rules) {
380
385
  if (rule.enforcement === "hard") {
381
386
  const ruleKey = rule.id.startsWith("no-") ? rule.id.slice(3) : rule.id;
382
- const actionLower = action.toLowerCase();
383
- if (actionLower.includes(ruleKey.toLowerCase())) {
387
+ const actionSegments = action.toLowerCase().split(":");
388
+ if (actionSegments.includes(ruleKey.toLowerCase())) {
384
389
  return {
385
390
  authorized: false,
386
391
  reason: `hard rule '${rule.id}' violated`,
@@ -488,6 +493,13 @@ function validateDefinition(raw) {
488
493
  }
489
494
  if (!svc.scopes || !Array.isArray(svc.scopes) || svc.scopes.length === 0) {
490
495
  errors.push(`services[${i}].scopes must be a non-empty array`);
496
+ } else {
497
+ for (let j = 0; j < svc.scopes.length; j++) {
498
+ const s = svc.scopes[j];
499
+ if (typeof s !== "string" || s.length === 0) {
500
+ errors.push(`services[${i}].scopes[${j}] must be a non-empty string`);
501
+ }
502
+ }
491
503
  }
492
504
  validateServiceAccessMode(svc, i, errors, warnings);
493
505
  }
@@ -538,6 +550,11 @@ function validateMetadata(metadata, errors) {
538
550
  }
539
551
  const record = metadata;
540
552
  for (const key of keys) {
553
+ if (key.startsWith("_")) {
554
+ errors.push(
555
+ `metadata key '${key}' uses reserved prefix '_' (reserved for SDK internal use)`
556
+ );
557
+ }
541
558
  const value = record[key];
542
559
  if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
543
560
  errors.push(
@@ -614,9 +631,7 @@ function validateLegalFramework(legal, errors) {
614
631
  }
615
632
 
616
633
  // src/revocation/revoke.ts
617
- var defaultStore = new MemoryRevocationStore();
618
634
  async function revoke(tokenId, options, store) {
619
- const s = store ?? defaultStore;
620
635
  const record = {
621
636
  tokenId,
622
637
  revokedAt: /* @__PURE__ */ new Date(),
@@ -624,19 +639,18 @@ async function revoke(tokenId, options, store) {
624
639
  reason: options.reason,
625
640
  cascaded: []
626
641
  };
627
- await s.add(record);
642
+ await store.add(record);
628
643
  return record;
629
644
  }
630
645
  async function isRevoked(tokenId, store) {
631
- const s = store ?? defaultStore;
632
- const record = await s.check(tokenId);
646
+ const record = await store.check(tokenId);
633
647
  return record !== null;
634
648
  }
635
649
 
636
650
  // src/audit/log.ts
637
- var defaultStore2 = new MemoryAuditStore();
651
+ var defaultStore = new MemoryAuditStore();
638
652
  async function logAction(tokenId, entry, store) {
639
- const s = store ?? defaultStore2;
653
+ const s = store ?? defaultStore;
640
654
  const fullEntry = {
641
655
  ...entry,
642
656
  tokenId,
@@ -646,13 +660,13 @@ async function logAction(tokenId, entry, store) {
646
660
  }
647
661
 
648
662
  // src/audit/trail.ts
649
- var defaultStore3 = new MemoryAuditStore();
663
+ var defaultStore2 = new MemoryAuditStore();
650
664
  async function getAuditTrail(tokenId, options, store) {
651
- const s = store ?? defaultStore3;
665
+ const s = store ?? defaultStore2;
652
666
  return s.query(tokenId, options);
653
667
  }
654
668
  async function getAuditTrailByService(service, options, store) {
655
- const s = store ?? defaultStore3;
669
+ const s = store ?? defaultStore2;
656
670
  return s.queryByService(service, options);
657
671
  }
658
672
 
@@ -682,7 +696,7 @@ async function verifySignature(token, key) {
682
696
  }
683
697
 
684
698
  // src/token/create.ts
685
- async function createToken(definition, options) {
699
+ async function createToken(definition, options, parentTokenId) {
686
700
  if (definition.metadata) {
687
701
  validateMetadata2(definition.metadata);
688
702
  }
@@ -698,7 +712,10 @@ async function createToken(definition, options) {
698
712
  exp: Math.floor(
699
713
  (typeof definition.expires === "string" ? new Date(definition.expires) : definition.expires).getTime() / 1e3
700
714
  ),
701
- definition: serializeDefinition(definition)
715
+ definition: {
716
+ ...serializeDefinition(definition),
717
+ ...parentTokenId ? { parentToken: parentTokenId } : {}
718
+ }
702
719
  };
703
720
  if (definition.notBefore) {
704
721
  payload.nbf = Math.floor(
@@ -719,6 +736,7 @@ async function createToken(definition, options) {
719
736
  signature: raw.split(".")[2],
720
737
  issuer,
721
738
  audience,
739
+ parentToken: parentTokenId,
722
740
  raw
723
741
  };
724
742
  return token;
@@ -806,13 +824,29 @@ async function validateToken(token, options) {
806
824
  "No key provided: supply publicKey, keyResolver, or publicKeyResolver"
807
825
  );
808
826
  }
827
+ const permittedAlgorithms = options.algorithms ?? ["EdDSA", "ES256"];
828
+ let headerAlg;
829
+ try {
830
+ const header = decodeHeader(rawJwt);
831
+ headerAlg = typeof header.alg === "string" ? header.alg : void 0;
832
+ } catch {
833
+ }
834
+ if (headerAlg && !permittedAlgorithms.includes(headerAlg)) {
835
+ errors.push(
836
+ `Token alg '${headerAlg}' is not in the permitted list [${permittedAlgorithms.join(", ")}]`
837
+ );
838
+ }
809
839
  let payload;
810
- if (publicKey) {
840
+ let signatureVerified = false;
841
+ if (publicKey && headerAlg && permittedAlgorithms.includes(headerAlg)) {
811
842
  try {
812
843
  payload = await verifySignature(rawJwt, publicKey);
844
+ signatureVerified = true;
813
845
  } catch {
814
846
  errors.push("Signature verification failed");
815
847
  }
848
+ } else if (publicKey && !headerAlg) {
849
+ errors.push("Token header missing alg");
816
850
  }
817
851
  if (!payload) {
818
852
  try {
@@ -828,10 +862,12 @@ async function validateToken(token, options) {
828
862
  if (!payload) {
829
863
  return { valid: false, errors: errors.length > 0 ? errors : ["Unable to decode token"], warnings };
830
864
  }
831
- try {
832
- parsedToken = payloadToToken(payload, rawJwt);
833
- } catch {
834
- errors.push("Token payload has invalid structure");
865
+ if (signatureVerified) {
866
+ try {
867
+ parsedToken = payloadToToken(payload, rawJwt);
868
+ } catch {
869
+ errors.push("Token payload has invalid structure");
870
+ }
835
871
  }
836
872
  const clockSkew = options.clockSkew;
837
873
  if (payload.exp !== void 0) {
@@ -895,9 +931,7 @@ function base64urlDecode(input) {
895
931
  }
896
932
 
897
933
  // src/revocation/cascade.ts
898
- var defaultStore4 = new MemoryRevocationStore();
899
934
  async function cascadeRevoke(parentTokenId, childTokenIds, options, store) {
900
- const s = store ?? defaultStore4;
901
935
  const revokedAt = /* @__PURE__ */ new Date();
902
936
  for (const childId of childTokenIds) {
903
937
  const childRecord = {
@@ -907,7 +941,7 @@ async function cascadeRevoke(parentTokenId, childTokenIds, options, store) {
907
941
  reason: options.reason ? `Cascade: ${options.reason}` : `Cascade revocation from parent ${parentTokenId}`,
908
942
  cascaded: []
909
943
  };
910
- await s.add(childRecord);
944
+ await store.add(childRecord);
911
945
  }
912
946
  const parentRecord = {
913
947
  tokenId: parentTokenId,
@@ -916,7 +950,7 @@ async function cascadeRevoke(parentTokenId, childTokenIds, options, store) {
916
950
  reason: options.reason,
917
951
  cascaded: childTokenIds
918
952
  };
919
- await s.add(parentRecord);
953
+ await store.add(parentRecord);
920
954
  return parentRecord;
921
955
  }
922
956
 
@@ -965,7 +999,12 @@ function verifyAttenuation(parent, child, currentDepth = 0) {
965
999
  verifyConstraintsNotRelaxed(parentService, childService);
966
1000
  }
967
1001
  if (parentDef.rules && parentDef.rules.length > 0) {
968
- verifyRulesNotRemoved(parentDef.rules.map((r) => r.id), child);
1002
+ verifyRulesNotRemoved(
1003
+ parentDef.rules.map((r) => r.id),
1004
+ child,
1005
+ parentDef.services.flatMap((s) => s.scopes),
1006
+ child.services.flatMap((s) => s.scopes)
1007
+ );
969
1008
  }
970
1009
  }
971
1010
  function verifyScopeSubset(parent, child) {
@@ -987,26 +1026,23 @@ function verifyConstraintsNotRelaxed(parent, child) {
987
1026
  for (const [key, parentValue] of Object.entries(parent.constraints)) {
988
1027
  if (parentValue === false) {
989
1028
  const childValue = child.constraints?.[key];
990
- if (childValue === true || childValue === void 0) {
991
- if (childValue === true) {
992
- throw new AttenuationViolationError(
993
- `Child relaxes constraint '${key}' on service '${child.service}' (parent: false, child: true)`,
994
- parent.scopes,
995
- child.scopes
996
- );
997
- }
998
- }
1029
+ if (childValue === false) continue;
1030
+ throw new AttenuationViolationError(
1031
+ childValue === true ? `Child relaxes constraint '${key}' on service '${child.service}' (parent: false, child: true)` : `Child omits constraint '${key}' on service '${child.service}' (parent: false, child: undefined)`,
1032
+ parent.scopes,
1033
+ child.scopes
1034
+ );
999
1035
  }
1000
1036
  }
1001
1037
  }
1002
- function verifyRulesNotRemoved(parentRuleIds, child) {
1038
+ function verifyRulesNotRemoved(parentRuleIds, child, parentScopes, childScopes) {
1003
1039
  const childRuleIds = new Set(child.rules?.map((r) => r.id) ?? []);
1004
1040
  for (const parentRuleId of parentRuleIds) {
1005
1041
  if (!childRuleIds.has(parentRuleId)) {
1006
1042
  throw new AttenuationViolationError(
1007
1043
  `Child removes parent rule '${parentRuleId}'. Rules can only be added, not removed.`,
1008
- [],
1009
- []
1044
+ parentScopes,
1045
+ childScopes
1010
1046
  );
1011
1047
  }
1012
1048
  }
@@ -1015,7 +1051,6 @@ function verifyRulesNotRemoved(parentRuleIds, child) {
1015
1051
  // src/delegation/chain.ts
1016
1052
  async function delegate(parentToken, childDef, options) {
1017
1053
  const currentDepth = countDepth(parentToken);
1018
- verifyAttenuation(parentToken, childDef, currentDepth);
1019
1054
  const parentDef = parentToken.definition;
1020
1055
  const parentRules = parentDef.rules ?? [];
1021
1056
  const childExtraRules = (childDef.rules ?? []).filter(
@@ -1027,12 +1062,32 @@ async function delegate(parentToken, childDef, options) {
1027
1062
  ...childDef.metadata,
1028
1063
  _delegationDepth: childDepth
1029
1064
  };
1065
+ const inheritedServices = childDef.services.map((childSvc) => {
1066
+ const parentSvc = parentDef.services.find((s) => s.service === childSvc.service);
1067
+ if (!parentSvc?.constraints) return childSvc;
1068
+ const inherited = {};
1069
+ for (const [key, value] of Object.entries(parentSvc.constraints)) {
1070
+ if (value === false) {
1071
+ inherited[key] = false;
1072
+ }
1073
+ }
1074
+ if (Object.keys(inherited).length === 0) return childSvc;
1075
+ return {
1076
+ ...childSvc,
1077
+ constraints: { ...inherited, ...childSvc.constraints }
1078
+ };
1079
+ });
1080
+ verifyAttenuation(
1081
+ parentToken,
1082
+ { ...childDef, services: inheritedServices, rules: mergedRules },
1083
+ currentDepth
1084
+ );
1030
1085
  const fullDefinition = {
1031
1086
  principal: parentDef.principal,
1032
1087
  // Inherited — cannot be overridden
1033
1088
  agent: childDef.agent,
1034
1089
  agentProvider: parentDef.agentProvider,
1035
- services: childDef.services,
1090
+ services: inheritedServices,
1036
1091
  rules: mergedRules.length > 0 ? mergedRules : void 0,
1037
1092
  expires: childDef.expires ?? parentDef.expires,
1038
1093
  revocable: parentDef.revocable,
@@ -1041,8 +1096,7 @@ async function delegate(parentToken, childDef, options) {
1041
1096
  metadata: childMetadata,
1042
1097
  legal: parentDef.legal
1043
1098
  };
1044
- const childToken = await createToken(fullDefinition, options);
1045
- childToken.parentToken = parentToken.jti;
1099
+ const childToken = await createToken(fullDefinition, options, parentToken.jti);
1046
1100
  return childToken;
1047
1101
  }
1048
1102
  function countDepth(token) {
@@ -1136,6 +1190,18 @@ function checkAttenuation(parent, child, index, errors) {
1136
1190
  );
1137
1191
  }
1138
1192
  }
1193
+ if (parentService.constraints) {
1194
+ for (const [key, parentValue] of Object.entries(parentService.constraints)) {
1195
+ if (parentValue === false) {
1196
+ const childValue = childService.constraints?.[key];
1197
+ if (childValue !== false) {
1198
+ errors.push(
1199
+ `Chain link ${index}: constraint '${key}' on '${childService.service}' relaxed by child (parent: false, child: ${childValue === void 0 ? "undefined" : JSON.stringify(childValue)})`
1200
+ );
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1139
1205
  }
1140
1206
  const childExp = child.definition.expires instanceof Date ? child.definition.expires.getTime() : new Date(child.definition.expires).getTime();
1141
1207
  const parentExp = parent.definition.expires instanceof Date ? parent.definition.expires.getTime() : new Date(parent.definition.expires).getTime();
@@ -1146,6 +1212,82 @@ function checkAttenuation(parent, child, index, errors) {
1146
1212
  }
1147
1213
  }
1148
1214
 
1215
+ // src/jwks/index.ts
1216
+ var jose3 = __toESM(require("jose"), 1);
1217
+ async function publicKeyToJWK(publicKey, options) {
1218
+ const exported = await jose3.exportJWK(publicKey);
1219
+ const alg = options.alg ?? defaultAlgorithm(exported);
1220
+ return {
1221
+ ...exported,
1222
+ kid: options.kid,
1223
+ use: options.use ?? "sig",
1224
+ alg
1225
+ };
1226
+ }
1227
+ function buildJWKS(keys) {
1228
+ return { keys };
1229
+ }
1230
+ function createJWKSResolver(url, options = {}) {
1231
+ if (!options.fetch && !options.allowInsecure && !url.startsWith("https://")) {
1232
+ throw new Error(
1233
+ `JWKS URL must use https:// (got: ${JSON.stringify(url)}). Pass allowInsecure: true or a custom fetch for local development.`
1234
+ );
1235
+ }
1236
+ const cacheMaxAgeMs = options.cacheMaxAgeMs ?? 60 * 60 * 1e3;
1237
+ const cooldownMs = options.cooldownMs ?? 24 * 60 * 60 * 1e3;
1238
+ const fetchImpl = options.fetch ?? fetch;
1239
+ let cache = null;
1240
+ let inflight = null;
1241
+ async function fetchJWKS() {
1242
+ const response = await fetchImpl(url, {
1243
+ headers: { accept: "application/jwk-set+json, application/json" }
1244
+ });
1245
+ if (!response.ok) {
1246
+ throw new Error(`JWKS fetch failed: ${response.status} ${response.statusText}`);
1247
+ }
1248
+ const body = await response.json();
1249
+ if (!body || !Array.isArray(body.keys)) {
1250
+ throw new Error("JWKS response did not contain a `keys` array");
1251
+ }
1252
+ return body;
1253
+ }
1254
+ async function getJWKS() {
1255
+ const now = Date.now();
1256
+ if (cache && now - cache.fetchedAt < cacheMaxAgeMs) {
1257
+ return cache.jwks;
1258
+ }
1259
+ if (inflight) {
1260
+ return inflight;
1261
+ }
1262
+ inflight = fetchJWKS().then((jwks) => {
1263
+ cache = { fetchedAt: Date.now(), jwks };
1264
+ return jwks;
1265
+ }).catch((err) => {
1266
+ if (cache && now - cache.fetchedAt < cooldownMs) {
1267
+ return cache.jwks;
1268
+ }
1269
+ throw err;
1270
+ }).finally(() => {
1271
+ inflight = null;
1272
+ });
1273
+ return inflight;
1274
+ }
1275
+ return {
1276
+ async resolve(kid) {
1277
+ const jwks = await getJWKS();
1278
+ const match = jwks.keys.find((k) => k.kid === kid);
1279
+ if (!match) return null;
1280
+ const key = await jose3.importJWK(match, match.alg);
1281
+ return key;
1282
+ }
1283
+ };
1284
+ }
1285
+ function defaultAlgorithm(jwk) {
1286
+ if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "EdDSA";
1287
+ if (jwk.kty === "EC" && jwk.crv === "P-256") return "ES256";
1288
+ return jwk.alg ?? "EdDSA";
1289
+ }
1290
+
1149
1291
  // src/client.ts
1150
1292
  function createClient(options) {
1151
1293
  const revocationStore = options?.revocationStore ?? new MemoryRevocationStore();
@@ -1229,10 +1371,12 @@ function createClient(options) {
1229
1371
  ScopeViolationError,
1230
1372
  TokenExpiredError,
1231
1373
  authorize,
1374
+ buildJWKS,
1232
1375
  cascadeRevoke,
1233
1376
  checkConstraint,
1234
1377
  checkScope,
1235
1378
  createClient,
1379
+ createJWKSResolver,
1236
1380
  createToken,
1237
1381
  decodeHeader,
1238
1382
  delegate,
@@ -1246,6 +1390,7 @@ function createClient(options) {
1246
1390
  matchScope,
1247
1391
  parseDefinition,
1248
1392
  parseScope,
1393
+ publicKeyToJWK,
1249
1394
  revoke,
1250
1395
  sign,
1251
1396
  signToken,