@insureco/bio 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/dist/index.d.mts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +89 -2
- package/dist/index.mjs +87 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -107,13 +107,22 @@ interface BioClientTokenPayload {
|
|
|
107
107
|
orgId?: string;
|
|
108
108
|
orgSlug?: string;
|
|
109
109
|
}
|
|
110
|
-
/** Options for local JWT verification */
|
|
110
|
+
/** Options for local JWT verification (HS256) */
|
|
111
111
|
interface VerifyOptions {
|
|
112
112
|
/** Expected issuer (default: config issuer) */
|
|
113
113
|
issuer?: string;
|
|
114
114
|
/** Expected audience (client_id) */
|
|
115
115
|
audience?: string;
|
|
116
116
|
}
|
|
117
|
+
/** Options for JWKS-based JWT verification (RS256) */
|
|
118
|
+
interface JWKSVerifyOptions {
|
|
119
|
+
/** JWKS endpoint URL (default: https://bio.tawa.insureco.io/.well-known/jwks.json) */
|
|
120
|
+
jwksUri?: string;
|
|
121
|
+
/** Expected issuer — defaults to accepting both bio.insureco.io and bio.tawa.insureco.io */
|
|
122
|
+
issuer?: string;
|
|
123
|
+
/** Expected audience (client_id) */
|
|
124
|
+
audience?: string;
|
|
125
|
+
}
|
|
117
126
|
/** User profile from /api/oauth/userinfo or admin API */
|
|
118
127
|
interface BioUser {
|
|
119
128
|
sub: string;
|
|
@@ -367,8 +376,10 @@ declare function generatePKCE(): {
|
|
|
367
376
|
};
|
|
368
377
|
|
|
369
378
|
/**
|
|
370
|
-
* Verify a Bio-ID JWT access token using HS256.
|
|
379
|
+
* Verify a Bio-ID JWT access token using HS256 (legacy / internal use).
|
|
371
380
|
* Checks algorithm, signature (constant-time), expiration, issuer, and audience.
|
|
381
|
+
*
|
|
382
|
+
* @deprecated Prefer verifyTokenJWKS() for RS256 tokens issued by Bio-ID ≥ 0.2.
|
|
372
383
|
*/
|
|
373
384
|
declare function verifyToken(token: string, secret: string, options?: VerifyOptions): BioTokenPayload;
|
|
374
385
|
/**
|
|
@@ -381,5 +392,20 @@ declare function decodeToken(token: string): BioTokenPayload | null;
|
|
|
381
392
|
* Returns true if expired or unparseable.
|
|
382
393
|
*/
|
|
383
394
|
declare function isTokenExpired(token: string, bufferSeconds?: number): boolean;
|
|
395
|
+
/**
|
|
396
|
+
* Verify a Bio-ID JWT access token using RS256 and the public JWKS endpoint.
|
|
397
|
+
*
|
|
398
|
+
* No shared secret required — fetches Bio-ID's public key and verifies locally.
|
|
399
|
+
* The JWKS is cached in-process for 24 hours and auto-refreshed on key rotation.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* import { verifyTokenJWKS } from '@insureco/bio'
|
|
404
|
+
*
|
|
405
|
+
* const payload = await verifyTokenJWKS(req.headers.authorization.slice(7))
|
|
406
|
+
* console.log(payload.bioId, payload.orgSlug)
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
declare function verifyTokenJWKS(token: string, options?: JWKSVerifyOptions): Promise<BioTokenPayload>;
|
|
384
410
|
|
|
385
|
-
export { type AdminResponse, type AuthorizeOptions, type AuthorizeResult, type BioAddress, BioAdmin, type BioAdminConfig, BioAuth, type BioAuthConfig, type BioClientTokenPayload, type BioDepartment, BioError, type BioMessaging, type BioOAuthClient, type BioRole, type BioTokenPayload, type BioUser, type CreateClientData, type CreateDepartmentData, type CreateRoleData, type IntrospectResult, type TokenResponse, type UpdateUserData, type UserFilters, type VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken };
|
|
411
|
+
export { type AdminResponse, type AuthorizeOptions, type AuthorizeResult, type BioAddress, BioAdmin, type BioAdminConfig, BioAuth, type BioAuthConfig, type BioClientTokenPayload, type BioDepartment, BioError, type BioMessaging, type BioOAuthClient, type BioRole, type BioTokenPayload, type BioUser, type CreateClientData, type CreateDepartmentData, type CreateRoleData, type IntrospectResult, type JWKSVerifyOptions, type TokenResponse, type UpdateUserData, type UserFilters, type VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken, verifyTokenJWKS };
|
package/dist/index.d.ts
CHANGED
|
@@ -107,13 +107,22 @@ interface BioClientTokenPayload {
|
|
|
107
107
|
orgId?: string;
|
|
108
108
|
orgSlug?: string;
|
|
109
109
|
}
|
|
110
|
-
/** Options for local JWT verification */
|
|
110
|
+
/** Options for local JWT verification (HS256) */
|
|
111
111
|
interface VerifyOptions {
|
|
112
112
|
/** Expected issuer (default: config issuer) */
|
|
113
113
|
issuer?: string;
|
|
114
114
|
/** Expected audience (client_id) */
|
|
115
115
|
audience?: string;
|
|
116
116
|
}
|
|
117
|
+
/** Options for JWKS-based JWT verification (RS256) */
|
|
118
|
+
interface JWKSVerifyOptions {
|
|
119
|
+
/** JWKS endpoint URL (default: https://bio.tawa.insureco.io/.well-known/jwks.json) */
|
|
120
|
+
jwksUri?: string;
|
|
121
|
+
/** Expected issuer — defaults to accepting both bio.insureco.io and bio.tawa.insureco.io */
|
|
122
|
+
issuer?: string;
|
|
123
|
+
/** Expected audience (client_id) */
|
|
124
|
+
audience?: string;
|
|
125
|
+
}
|
|
117
126
|
/** User profile from /api/oauth/userinfo or admin API */
|
|
118
127
|
interface BioUser {
|
|
119
128
|
sub: string;
|
|
@@ -367,8 +376,10 @@ declare function generatePKCE(): {
|
|
|
367
376
|
};
|
|
368
377
|
|
|
369
378
|
/**
|
|
370
|
-
* Verify a Bio-ID JWT access token using HS256.
|
|
379
|
+
* Verify a Bio-ID JWT access token using HS256 (legacy / internal use).
|
|
371
380
|
* Checks algorithm, signature (constant-time), expiration, issuer, and audience.
|
|
381
|
+
*
|
|
382
|
+
* @deprecated Prefer verifyTokenJWKS() for RS256 tokens issued by Bio-ID ≥ 0.2.
|
|
372
383
|
*/
|
|
373
384
|
declare function verifyToken(token: string, secret: string, options?: VerifyOptions): BioTokenPayload;
|
|
374
385
|
/**
|
|
@@ -381,5 +392,20 @@ declare function decodeToken(token: string): BioTokenPayload | null;
|
|
|
381
392
|
* Returns true if expired or unparseable.
|
|
382
393
|
*/
|
|
383
394
|
declare function isTokenExpired(token: string, bufferSeconds?: number): boolean;
|
|
395
|
+
/**
|
|
396
|
+
* Verify a Bio-ID JWT access token using RS256 and the public JWKS endpoint.
|
|
397
|
+
*
|
|
398
|
+
* No shared secret required — fetches Bio-ID's public key and verifies locally.
|
|
399
|
+
* The JWKS is cached in-process for 24 hours and auto-refreshed on key rotation.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* import { verifyTokenJWKS } from '@insureco/bio'
|
|
404
|
+
*
|
|
405
|
+
* const payload = await verifyTokenJWKS(req.headers.authorization.slice(7))
|
|
406
|
+
* console.log(payload.bioId, payload.orgSlug)
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
declare function verifyTokenJWKS(token: string, options?: JWKSVerifyOptions): Promise<BioTokenPayload>;
|
|
384
410
|
|
|
385
|
-
export { type AdminResponse, type AuthorizeOptions, type AuthorizeResult, type BioAddress, BioAdmin, type BioAdminConfig, BioAuth, type BioAuthConfig, type BioClientTokenPayload, type BioDepartment, BioError, type BioMessaging, type BioOAuthClient, type BioRole, type BioTokenPayload, type BioUser, type CreateClientData, type CreateDepartmentData, type CreateRoleData, type IntrospectResult, type TokenResponse, type UpdateUserData, type UserFilters, type VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken };
|
|
411
|
+
export { type AdminResponse, type AuthorizeOptions, type AuthorizeResult, type BioAddress, BioAdmin, type BioAdminConfig, BioAuth, type BioAuthConfig, type BioClientTokenPayload, type BioDepartment, BioError, type BioMessaging, type BioOAuthClient, type BioRole, type BioTokenPayload, type BioUser, type CreateClientData, type CreateDepartmentData, type CreateRoleData, type IntrospectResult, type JWKSVerifyOptions, type TokenResponse, type UpdateUserData, type UserFilters, type VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken, verifyTokenJWKS };
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,8 @@ __export(index_exports, {
|
|
|
36
36
|
decodeToken: () => decodeToken,
|
|
37
37
|
generatePKCE: () => generatePKCE,
|
|
38
38
|
isTokenExpired: () => isTokenExpired,
|
|
39
|
-
verifyToken: () => verifyToken
|
|
39
|
+
verifyToken: () => verifyToken,
|
|
40
|
+
verifyTokenJWKS: () => verifyTokenJWKS
|
|
40
41
|
});
|
|
41
42
|
module.exports = __toCommonJS(index_exports);
|
|
42
43
|
|
|
@@ -588,6 +589,57 @@ var DEFAULT_ISSUERS = [
|
|
|
588
589
|
"https://bio.tawa.insureco.io",
|
|
589
590
|
"http://localhost:6100"
|
|
590
591
|
];
|
|
592
|
+
var DEFAULT_JWKS_URI = "https://bio.tawa.insureco.io/.well-known/jwks.json";
|
|
593
|
+
var JWKS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
594
|
+
var jwksCache = /* @__PURE__ */ new Map();
|
|
595
|
+
async function fetchJWKS(uri) {
|
|
596
|
+
const cached = jwksCache.get(uri);
|
|
597
|
+
if (cached && Date.now() - cached.fetchedAt < JWKS_CACHE_TTL_MS) {
|
|
598
|
+
return cached.keys;
|
|
599
|
+
}
|
|
600
|
+
const response = await fetch(uri, {
|
|
601
|
+
headers: { Accept: "application/json" },
|
|
602
|
+
signal: AbortSignal.timeout(5e3)
|
|
603
|
+
});
|
|
604
|
+
if (!response.ok) {
|
|
605
|
+
throw new BioError(`JWKS fetch failed: ${response.status}`, "jwks_fetch_failed");
|
|
606
|
+
}
|
|
607
|
+
const data = await response.json();
|
|
608
|
+
jwksCache.set(uri, { keys: data.keys, fetchedAt: Date.now() });
|
|
609
|
+
return data.keys;
|
|
610
|
+
}
|
|
611
|
+
async function importRS256Key(jwk) {
|
|
612
|
+
return globalThis.crypto.subtle.importKey(
|
|
613
|
+
"jwk",
|
|
614
|
+
jwk,
|
|
615
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
616
|
+
false,
|
|
617
|
+
["verify"]
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
async function verifyRS256Signature(headerB64, payloadB64, signatureB64, jwksUri, kid, retrying = false) {
|
|
621
|
+
const keys = await fetchJWKS(jwksUri);
|
|
622
|
+
const targetKey = kid ? keys.find((k) => k.kid === kid) ?? keys[0] : keys[0];
|
|
623
|
+
if (!targetKey) {
|
|
624
|
+
throw new BioError("No matching key found in JWKS", "invalid_token");
|
|
625
|
+
}
|
|
626
|
+
const cryptoKey = await importRS256Key(targetKey);
|
|
627
|
+
const data = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
628
|
+
const signature = Buffer.from(signatureB64, "base64url");
|
|
629
|
+
const valid = await globalThis.crypto.subtle.verify(
|
|
630
|
+
"RSASSA-PKCS1-v1_5",
|
|
631
|
+
cryptoKey,
|
|
632
|
+
signature,
|
|
633
|
+
data
|
|
634
|
+
);
|
|
635
|
+
if (!valid) {
|
|
636
|
+
if (!retrying) {
|
|
637
|
+
jwksCache.delete(jwksUri);
|
|
638
|
+
return verifyRS256Signature(headerB64, payloadB64, signatureB64, jwksUri, kid, true);
|
|
639
|
+
}
|
|
640
|
+
throw new BioError("Invalid JWT signature", "invalid_signature");
|
|
641
|
+
}
|
|
642
|
+
}
|
|
591
643
|
function base64UrlDecode(str) {
|
|
592
644
|
return Buffer.from(str, "base64url").toString("utf8");
|
|
593
645
|
}
|
|
@@ -598,6 +650,12 @@ function verifyToken(token, secret, options) {
|
|
|
598
650
|
}
|
|
599
651
|
const [headerB64, payloadB64, signatureB64] = parts;
|
|
600
652
|
const header = JSON.parse(base64UrlDecode(headerB64));
|
|
653
|
+
if (header.alg === "RS256") {
|
|
654
|
+
throw new BioError(
|
|
655
|
+
"Token uses RS256 \u2014 use verifyTokenJWKS() instead of verifyToken()",
|
|
656
|
+
"unsupported_alg"
|
|
657
|
+
);
|
|
658
|
+
}
|
|
601
659
|
if (header.alg !== "HS256") {
|
|
602
660
|
throw new BioError(`Unsupported algorithm: ${header.alg}`, "unsupported_alg");
|
|
603
661
|
}
|
|
@@ -642,6 +700,34 @@ function isTokenExpired(token, bufferSeconds = 30) {
|
|
|
642
700
|
const now = Math.floor(Date.now() / 1e3);
|
|
643
701
|
return payload.exp < now + bufferSeconds;
|
|
644
702
|
}
|
|
703
|
+
async function verifyTokenJWKS(token, options) {
|
|
704
|
+
const parts = token.split(".");
|
|
705
|
+
if (parts.length !== 3) {
|
|
706
|
+
throw new BioError("Malformed JWT: expected 3 parts", "invalid_token");
|
|
707
|
+
}
|
|
708
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
709
|
+
const header = JSON.parse(base64UrlDecode(headerB64));
|
|
710
|
+
if (header.alg !== "RS256") {
|
|
711
|
+
throw new BioError(
|
|
712
|
+
`Expected RS256 token, got ${header.alg}. Use verifyToken() for HS256.`,
|
|
713
|
+
"unsupported_alg"
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
const jwksUri = options?.jwksUri ?? DEFAULT_JWKS_URI;
|
|
717
|
+
await verifyRS256Signature(headerB64, payloadB64, signatureB64, jwksUri, header.kid);
|
|
718
|
+
const payload = JSON.parse(base64UrlDecode(payloadB64));
|
|
719
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
720
|
+
if (!payload.exp) throw new BioError("Token missing expiration claim", "invalid_token");
|
|
721
|
+
if (payload.exp < now) throw new BioError("Token has expired", "token_expired");
|
|
722
|
+
const allowedIssuers = options?.issuer ? [options.issuer] : DEFAULT_ISSUERS;
|
|
723
|
+
if (!allowedIssuers.includes(payload.iss)) {
|
|
724
|
+
throw new BioError(`Unknown issuer: ${payload.iss}`, "invalid_issuer");
|
|
725
|
+
}
|
|
726
|
+
if (options?.audience && payload.aud !== options.audience) {
|
|
727
|
+
throw new BioError(`Invalid audience: ${payload.aud}`, "invalid_audience");
|
|
728
|
+
}
|
|
729
|
+
return payload;
|
|
730
|
+
}
|
|
645
731
|
// Annotate the CommonJS export names for ESM import in node:
|
|
646
732
|
0 && (module.exports = {
|
|
647
733
|
BioAdmin,
|
|
@@ -650,5 +736,6 @@ function isTokenExpired(token, bufferSeconds = 30) {
|
|
|
650
736
|
decodeToken,
|
|
651
737
|
generatePKCE,
|
|
652
738
|
isTokenExpired,
|
|
653
|
-
verifyToken
|
|
739
|
+
verifyToken,
|
|
740
|
+
verifyTokenJWKS
|
|
654
741
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -546,6 +546,57 @@ var DEFAULT_ISSUERS = [
|
|
|
546
546
|
"https://bio.tawa.insureco.io",
|
|
547
547
|
"http://localhost:6100"
|
|
548
548
|
];
|
|
549
|
+
var DEFAULT_JWKS_URI = "https://bio.tawa.insureco.io/.well-known/jwks.json";
|
|
550
|
+
var JWKS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
551
|
+
var jwksCache = /* @__PURE__ */ new Map();
|
|
552
|
+
async function fetchJWKS(uri) {
|
|
553
|
+
const cached = jwksCache.get(uri);
|
|
554
|
+
if (cached && Date.now() - cached.fetchedAt < JWKS_CACHE_TTL_MS) {
|
|
555
|
+
return cached.keys;
|
|
556
|
+
}
|
|
557
|
+
const response = await fetch(uri, {
|
|
558
|
+
headers: { Accept: "application/json" },
|
|
559
|
+
signal: AbortSignal.timeout(5e3)
|
|
560
|
+
});
|
|
561
|
+
if (!response.ok) {
|
|
562
|
+
throw new BioError(`JWKS fetch failed: ${response.status}`, "jwks_fetch_failed");
|
|
563
|
+
}
|
|
564
|
+
const data = await response.json();
|
|
565
|
+
jwksCache.set(uri, { keys: data.keys, fetchedAt: Date.now() });
|
|
566
|
+
return data.keys;
|
|
567
|
+
}
|
|
568
|
+
async function importRS256Key(jwk) {
|
|
569
|
+
return globalThis.crypto.subtle.importKey(
|
|
570
|
+
"jwk",
|
|
571
|
+
jwk,
|
|
572
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
573
|
+
false,
|
|
574
|
+
["verify"]
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
async function verifyRS256Signature(headerB64, payloadB64, signatureB64, jwksUri, kid, retrying = false) {
|
|
578
|
+
const keys = await fetchJWKS(jwksUri);
|
|
579
|
+
const targetKey = kid ? keys.find((k) => k.kid === kid) ?? keys[0] : keys[0];
|
|
580
|
+
if (!targetKey) {
|
|
581
|
+
throw new BioError("No matching key found in JWKS", "invalid_token");
|
|
582
|
+
}
|
|
583
|
+
const cryptoKey = await importRS256Key(targetKey);
|
|
584
|
+
const data = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
585
|
+
const signature = Buffer.from(signatureB64, "base64url");
|
|
586
|
+
const valid = await globalThis.crypto.subtle.verify(
|
|
587
|
+
"RSASSA-PKCS1-v1_5",
|
|
588
|
+
cryptoKey,
|
|
589
|
+
signature,
|
|
590
|
+
data
|
|
591
|
+
);
|
|
592
|
+
if (!valid) {
|
|
593
|
+
if (!retrying) {
|
|
594
|
+
jwksCache.delete(jwksUri);
|
|
595
|
+
return verifyRS256Signature(headerB64, payloadB64, signatureB64, jwksUri, kid, true);
|
|
596
|
+
}
|
|
597
|
+
throw new BioError("Invalid JWT signature", "invalid_signature");
|
|
598
|
+
}
|
|
599
|
+
}
|
|
549
600
|
function base64UrlDecode(str) {
|
|
550
601
|
return Buffer.from(str, "base64url").toString("utf8");
|
|
551
602
|
}
|
|
@@ -556,6 +607,12 @@ function verifyToken(token, secret, options) {
|
|
|
556
607
|
}
|
|
557
608
|
const [headerB64, payloadB64, signatureB64] = parts;
|
|
558
609
|
const header = JSON.parse(base64UrlDecode(headerB64));
|
|
610
|
+
if (header.alg === "RS256") {
|
|
611
|
+
throw new BioError(
|
|
612
|
+
"Token uses RS256 \u2014 use verifyTokenJWKS() instead of verifyToken()",
|
|
613
|
+
"unsupported_alg"
|
|
614
|
+
);
|
|
615
|
+
}
|
|
559
616
|
if (header.alg !== "HS256") {
|
|
560
617
|
throw new BioError(`Unsupported algorithm: ${header.alg}`, "unsupported_alg");
|
|
561
618
|
}
|
|
@@ -600,6 +657,34 @@ function isTokenExpired(token, bufferSeconds = 30) {
|
|
|
600
657
|
const now = Math.floor(Date.now() / 1e3);
|
|
601
658
|
return payload.exp < now + bufferSeconds;
|
|
602
659
|
}
|
|
660
|
+
async function verifyTokenJWKS(token, options) {
|
|
661
|
+
const parts = token.split(".");
|
|
662
|
+
if (parts.length !== 3) {
|
|
663
|
+
throw new BioError("Malformed JWT: expected 3 parts", "invalid_token");
|
|
664
|
+
}
|
|
665
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
666
|
+
const header = JSON.parse(base64UrlDecode(headerB64));
|
|
667
|
+
if (header.alg !== "RS256") {
|
|
668
|
+
throw new BioError(
|
|
669
|
+
`Expected RS256 token, got ${header.alg}. Use verifyToken() for HS256.`,
|
|
670
|
+
"unsupported_alg"
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
const jwksUri = options?.jwksUri ?? DEFAULT_JWKS_URI;
|
|
674
|
+
await verifyRS256Signature(headerB64, payloadB64, signatureB64, jwksUri, header.kid);
|
|
675
|
+
const payload = JSON.parse(base64UrlDecode(payloadB64));
|
|
676
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
677
|
+
if (!payload.exp) throw new BioError("Token missing expiration claim", "invalid_token");
|
|
678
|
+
if (payload.exp < now) throw new BioError("Token has expired", "token_expired");
|
|
679
|
+
const allowedIssuers = options?.issuer ? [options.issuer] : DEFAULT_ISSUERS;
|
|
680
|
+
if (!allowedIssuers.includes(payload.iss)) {
|
|
681
|
+
throw new BioError(`Unknown issuer: ${payload.iss}`, "invalid_issuer");
|
|
682
|
+
}
|
|
683
|
+
if (options?.audience && payload.aud !== options.audience) {
|
|
684
|
+
throw new BioError(`Invalid audience: ${payload.aud}`, "invalid_audience");
|
|
685
|
+
}
|
|
686
|
+
return payload;
|
|
687
|
+
}
|
|
603
688
|
export {
|
|
604
689
|
BioAdmin,
|
|
605
690
|
BioAuth,
|
|
@@ -607,5 +692,6 @@ export {
|
|
|
607
692
|
decodeToken,
|
|
608
693
|
generatePKCE,
|
|
609
694
|
isTokenExpired,
|
|
610
|
-
verifyToken
|
|
695
|
+
verifyToken,
|
|
696
|
+
verifyTokenJWKS
|
|
611
697
|
};
|