@cfworker/jwt 1.12.1 → 3.0.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
@@ -8,7 +8,7 @@
8
8
 
9
9
  Tiny lib for decoding JWTs and verifying signatures, using native crypto APIs.
10
10
 
11
- Currently supports `alg:'RS256'` only.
11
+ Currently supports RS256, RS384, and RS512.
12
12
 
13
13
  ```js
14
14
  const jwt = request.headers.get('Authorization');
package/dist/algs.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const algToHash: Record<string, string>;
2
+ export declare const algs: string[];
package/dist/algs.js ADDED
@@ -0,0 +1,6 @@
1
+ export const algToHash = {
2
+ RS256: 'SHA-256',
3
+ RS384: 'SHA-384',
4
+ RS512: 'SHA-512'
5
+ };
6
+ export const algs = Object.keys(algToHash);
package/dist/decode.js CHANGED
@@ -1,10 +1,11 @@
1
- import { decode } from '@cfworker/base64url';
1
+ import { base64url } from 'rfc4648';
2
2
  export function decodeJwt(token) {
3
3
  const [header, payload, signature] = token.split('.');
4
+ const decoder = new TextDecoder();
4
5
  return {
5
- header: JSON.parse(decode(header)),
6
- payload: JSON.parse(decode(payload)),
7
- signature: decode(signature),
6
+ header: JSON.parse(decoder.decode(base64url.parse(header, { loose: true }))),
7
+ payload: JSON.parse(decoder.decode(base64url.parse(payload, { loose: true }))),
8
+ signature: base64url.parse(signature, { loose: true }),
8
9
  raw: { header, payload, signature }
9
10
  };
10
11
  }
package/dist/jwks.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { algToHash } from './algs.js';
1
2
  export async function getJwks(issuer) {
2
3
  const url = new URL(issuer);
3
4
  if (!url.pathname.endsWith('/')) {
@@ -12,16 +13,16 @@ export async function getJwks(issuer) {
12
13
  }
13
14
  const importedKeys = {};
14
15
  export async function importKey(iss, jwk) {
15
- const input = {
16
- kty: 'RSA',
17
- e: 'AQAB',
18
- n: jwk.n,
19
- alg: 'RS256',
20
- ext: true
21
- };
22
- const key = await crypto.subtle.importKey('jwk', input, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);
16
+ if (jwk.kty !== 'RSA') {
17
+ throw new Error(`Unsupported jwk key type (kty) "${jwk.kty}".`);
18
+ }
19
+ const hash = jwk.alg ? algToHash[jwk.alg] : null;
20
+ if (!hash) {
21
+ throw new Error(`Unsupported jwk Algorithm (alg) "${jwk.alg}".`);
22
+ }
23
+ const key = await crypto.subtle.importKey('jwk', jwk, { name: 'RSASSA-PKCS1-v1_5', hash }, false, ['verify']);
23
24
  importedKeys[iss] = importedKeys[iss] || {};
24
- importedKeys[iss][jwk.kid || 'default'] = key;
25
+ importedKeys[iss][jwk.kid ?? 'default'] = key;
25
26
  }
26
27
  export async function getKey(decoded) {
27
28
  let { header: { kid = 'default' }, payload: { iss } } = decoded;
package/dist/parse.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { JwtParseResult } from './types.js';
2
- export declare function parseJwt(encodedToken: string, issuer: string, audience: string): Promise<JwtParseResult>;
1
+ import { DecodedJwt, JwtParseResult } from './types.js';
2
+ export declare function parseJwt(encodedToken: string, issuer: string, audience: string, resolveKey?: (decoded: DecodedJwt) => Promise<CryptoKey | null>): Promise<JwtParseResult>;
package/dist/parse.js CHANGED
@@ -1,7 +1,9 @@
1
+ import { algs, algToHash } from './algs.js';
1
2
  import { decodeJwt } from './decode.js';
2
3
  import { getKey } from './jwks.js';
3
4
  import { verifyJwtSignature } from './verify.js';
4
- export async function parseJwt(encodedToken, issuer, audience) {
5
+ const skewMs = 30 * 1000;
6
+ export async function parseJwt(encodedToken, issuer, audience, resolveKey = getKey) {
5
7
  let decoded;
6
8
  try {
7
9
  decoded = decodeJwt(encodedToken);
@@ -9,57 +11,120 @@ export async function parseJwt(encodedToken, issuer, audience) {
9
11
  catch {
10
12
  return { valid: false, reason: `Unable to decode JWT.` };
11
13
  }
12
- if (typeof decoded.header.typ !== 'undefined' &&
13
- decoded.header.typ !== 'JWT') {
14
+ const { typ, alg } = decoded.header;
15
+ if (typeof typ !== 'undefined' && typ !== 'JWT') {
14
16
  return {
15
17
  valid: false,
16
- reason: `Invalid JWT type "${decoded.header.typ}". Expected "JWT".`
18
+ reason: `Invalid JWT type ${JSON.stringify(typ)}. Expected "JWT".`
17
19
  };
18
20
  }
19
- if (decoded.header.alg !== 'RS256') {
21
+ if (!algToHash[alg]) {
20
22
  return {
21
23
  valid: false,
22
- reason: `Invalid JWT algorithm "${decoded.header.alg}". Expected "RS256".`
24
+ reason: `Invalid JWT algorithm ${JSON.stringify(alg)}. Supported: ${algs}.`
23
25
  };
24
26
  }
25
- if (typeof decoded.payload.aud === 'string' &&
26
- decoded.payload.aud !== audience) {
27
+ const { sub, aud, iss, iat, exp, nbf } = decoded.payload;
28
+ if (typeof sub !== 'string') {
27
29
  return {
28
30
  valid: false,
29
- reason: `Invalid JWT audience "${decoded.payload.aud}". Expected "${audience}".`
31
+ reason: `Subject claim (sub) is required and must be a string. Received ${JSON.stringify(sub)}.`
30
32
  };
31
33
  }
32
- else if (Array.isArray(decoded.payload.aud) &&
33
- !decoded.payload.aud.includes(audience)) {
34
+ if (typeof aud === 'string') {
35
+ if (aud !== audience) {
36
+ return {
37
+ valid: false,
38
+ reason: `Invalid JWT audience claim (aud) ${JSON.stringify(aud)}. Expected "${audience}".`
39
+ };
40
+ }
41
+ }
42
+ else if (Array.isArray(aud) &&
43
+ aud.length > 0 &&
44
+ aud.every(a => typeof a === 'string')) {
45
+ if (!aud.includes(audience)) {
46
+ return {
47
+ valid: false,
48
+ reason: `Invalid JWT audience claim array (aud) ${JSON.stringify(aud)}. Does not include "${audience}".`
49
+ };
50
+ }
51
+ }
52
+ else {
34
53
  return {
35
54
  valid: false,
36
- reason: `Invalid JWT audience in array "${decoded.payload.aud}". Does not include "${audience}".`
55
+ reason: `Invalid JWT audience claim (aud) ${JSON.stringify(aud)}. Expected a string or a non-empty array of strings.`
37
56
  };
38
57
  }
39
- if (decoded.payload.iss !== issuer) {
58
+ if (iss !== issuer) {
40
59
  return {
41
60
  valid: false,
42
- reason: `Invalid JWT issuer "${decoded.payload.iss}". Expected "${issuer}".`
61
+ reason: `Invalid JWT issuer claim (iss) ${JSON.stringify(decoded.payload.iss)}. Expected "${issuer}".`
62
+ };
63
+ }
64
+ if (typeof exp !== 'number') {
65
+ return {
66
+ valid: false,
67
+ reason: `Invalid JWT expiry date claim (exp) ${JSON.stringify(exp)}. Expected number.`
43
68
  };
44
69
  }
45
- const expiryDate = new Date(0);
46
- expiryDate.setUTCSeconds(decoded.payload.exp);
47
70
  const currentDate = new Date(Date.now());
48
- const expired = expiryDate <= currentDate;
71
+ const expiryDate = new Date(0);
72
+ expiryDate.setUTCSeconds(exp);
73
+ const expired = expiryDate.getTime() <= currentDate.getTime() - skewMs;
49
74
  if (expired) {
50
75
  return {
51
76
  valid: false,
52
77
  reason: `JWT is expired. Expiry date: ${expiryDate}; Current date: ${currentDate};`
53
78
  };
54
79
  }
80
+ if (nbf !== undefined) {
81
+ if (typeof nbf !== 'number') {
82
+ return {
83
+ valid: false,
84
+ reason: `Invalid JWT not before date claim (nbf) ${JSON.stringify(nbf)}. Expected number.`
85
+ };
86
+ }
87
+ const notBeforeDate = new Date(0);
88
+ notBeforeDate.setUTCSeconds(nbf);
89
+ const early = notBeforeDate.getTime() > currentDate.getTime() + skewMs;
90
+ if (early) {
91
+ return {
92
+ valid: false,
93
+ reason: `JWT cannot be used prior to not before date claim (nbf). Not before date: ${notBeforeDate}; Current date: ${currentDate};`
94
+ };
95
+ }
96
+ }
97
+ if (iat !== undefined) {
98
+ if (typeof iat !== 'number') {
99
+ return {
100
+ valid: false,
101
+ reason: `Invalid JWT issued at date claim (iat) ${JSON.stringify(iat)}. Expected number.`
102
+ };
103
+ }
104
+ const issuedAtDate = new Date(0);
105
+ issuedAtDate.setUTCSeconds(iat);
106
+ const postIssued = issuedAtDate.getTime() > currentDate.getTime() + skewMs;
107
+ if (postIssued) {
108
+ return {
109
+ valid: false,
110
+ reason: `JWT issued at date claim (iat) is in the future. Issued at date: ${issuedAtDate}; Current date: ${currentDate};`
111
+ };
112
+ }
113
+ }
55
114
  let key;
56
115
  try {
57
- key = await getKey(decoded);
116
+ key = await resolveKey(decoded);
58
117
  }
59
118
  catch {
60
119
  return {
61
120
  valid: false,
62
- reason: `Error retrieving key to verify JWT signature.`
121
+ reason: `Error retrieving public key to verify JWT signature.`
122
+ };
123
+ }
124
+ if (!key) {
125
+ return {
126
+ valid: false,
127
+ reason: `Unable to resolve public key to verify JWT signature.`
63
128
  };
64
129
  }
65
130
  let signatureValid;
package/dist/types.d.ts CHANGED
@@ -4,6 +4,8 @@ export interface JwtPayload {
4
4
  aud: string | Array<string>;
5
5
  iat: number;
6
6
  exp: number;
7
+ nbf?: number;
8
+ jti?: string;
7
9
  }
8
10
  export interface JwtHeader {
9
11
  typ?: string;
@@ -18,7 +20,7 @@ export interface RawJwt {
18
20
  export interface DecodedJwt {
19
21
  header: JwtHeader;
20
22
  payload: JwtPayload;
21
- signature: string;
23
+ signature: Uint8Array;
22
24
  raw: RawJwt;
23
25
  }
24
26
  export interface JsonWebKeyset {
package/dist/verify.js CHANGED
@@ -1,6 +1,5 @@
1
1
  export async function verifyJwtSignature(decoded, key) {
2
2
  const encoder = new TextEncoder();
3
3
  const data = encoder.encode(`${decoded.raw.header}.${decoded.raw.payload}`);
4
- const signature = new Uint8Array(Array.from(decoded.signature).map(c => c.charCodeAt(0)));
5
- return crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, signature, data);
4
+ return crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, decoded.signature, data);
6
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfworker/jwt",
3
- "version": "1.12.1",
3
+ "version": "3.0.0",
4
4
  "description": "Tiny lib for decoding JWTs and verifying signatures, using native crypto APIs",
5
5
  "keywords": [
6
6
  "jwt",
@@ -34,16 +34,16 @@
34
34
  "test": "cfworker test test/**/*.spec.ts --nocheck"
35
35
  },
36
36
  "dependencies": {
37
- "@cfworker/base64url": "^1.12.1",
38
37
  "@cfworker/worker-types": "^1.12.1",
39
- "@cloudflare/workers-types": "^2.2.2"
38
+ "@cloudflare/workers-types": "^2.2.2",
39
+ "rfc4648": "^1.5.1"
40
40
  },
41
41
  "devDependencies": {
42
- "@cfworker/dev": "^1.13.1",
42
+ "@cfworker/dev": "^1.14.0",
43
43
  "@types/chai": "^4.3.0",
44
44
  "@types/mocha": "^9.1.0",
45
- "chai": "^4.3.4",
46
- "mocha": "^9.1.4",
45
+ "chai": "^4.3.6",
46
+ "mocha": "^9.2.1",
47
47
  "typescript": "^4.5.5"
48
48
  }
49
49
  }
package/src/algs.ts ADDED
@@ -0,0 +1,7 @@
1
+ export const algToHash: Record<string, string> = {
2
+ RS256: 'SHA-256',
3
+ RS384: 'SHA-384',
4
+ RS512: 'SHA-512'
5
+ };
6
+
7
+ export const algs = Object.keys(algToHash);
package/src/decode.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { decode } from '@cfworker/base64url';
1
+ import { base64url } from 'rfc4648';
2
2
  import { DecodedJwt } from './types.js';
3
3
 
4
4
  /**
@@ -6,10 +6,15 @@ import { DecodedJwt } from './types.js';
6
6
  */
7
7
  export function decodeJwt(token: string): DecodedJwt {
8
8
  const [header, payload, signature] = token.split('.');
9
+ const decoder = new TextDecoder();
9
10
  return {
10
- header: JSON.parse(decode(header)),
11
- payload: JSON.parse(decode(payload)),
12
- signature: decode(signature),
11
+ header: JSON.parse(
12
+ decoder.decode(base64url.parse(header, { loose: true }))
13
+ ),
14
+ payload: JSON.parse(
15
+ decoder.decode(base64url.parse(payload, { loose: true }))
16
+ ),
17
+ signature: base64url.parse(signature, { loose: true }),
13
18
  raw: { header, payload, signature }
14
19
  };
15
20
  }
package/src/jwks.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { algToHash } from './algs.js';
1
2
  import { DecodedJwt, JsonWebKeyset } from './types.js';
2
3
 
3
4
  /**
@@ -26,22 +27,22 @@ const importedKeys: Record<string, Record<string, CryptoKey>> = {};
26
27
  * @param jwks The JsonWebKeyset to import.
27
28
  */
28
29
  export async function importKey(iss: string, jwk: JsonWebKey) {
29
- const input = {
30
- kty: 'RSA',
31
- e: 'AQAB',
32
- n: jwk.n,
33
- alg: 'RS256',
34
- ext: true
35
- };
30
+ if (jwk.kty !== 'RSA') {
31
+ throw new Error(`Unsupported jwk key type (kty) "${jwk.kty}".`);
32
+ }
33
+ const hash = jwk.alg ? algToHash[jwk.alg] : null;
34
+ if (!hash) {
35
+ throw new Error(`Unsupported jwk Algorithm (alg) "${jwk.alg}".`);
36
+ }
36
37
  const key = await crypto.subtle.importKey(
37
38
  'jwk',
38
- input,
39
- { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
39
+ jwk,
40
+ { name: 'RSASSA-PKCS1-v1_5', hash },
40
41
  false,
41
42
  ['verify']
42
43
  );
43
44
  importedKeys[iss] = importedKeys[iss] || {};
44
- importedKeys[iss][jwk.kid || 'default'] = key;
45
+ importedKeys[iss][jwk.kid ?? 'default'] = key;
45
46
  }
46
47
 
47
48
  /**
package/src/parse.ts CHANGED
@@ -1,15 +1,19 @@
1
+ import { algs, algToHash } from './algs.js';
1
2
  import { decodeJwt } from './decode.js';
2
3
  import { getKey } from './jwks.js';
3
4
  import { DecodedJwt, JwtParseResult } from './types.js';
4
5
  import { verifyJwtSignature } from './verify.js';
5
6
 
7
+ const skewMs = 30 * 1000;
8
+
6
9
  /**
7
10
  * Parse a JWT.
8
11
  */
9
12
  export async function parseJwt(
10
13
  encodedToken: string,
11
14
  issuer: string,
12
- audience: string
15
+ audience: string,
16
+ resolveKey: (decoded: DecodedJwt) => Promise<CryptoKey | null> = getKey
13
17
  ): Promise<JwtParseResult> {
14
18
  let decoded: DecodedJwt;
15
19
  try {
@@ -17,62 +21,144 @@ export async function parseJwt(
17
21
  } catch {
18
22
  return { valid: false, reason: `Unable to decode JWT.` };
19
23
  }
20
- if (
21
- typeof decoded.header.typ !== 'undefined' &&
22
- decoded.header.typ !== 'JWT'
23
- ) {
24
+ const { typ, alg } = decoded.header;
25
+ if (typeof typ !== 'undefined' && typ !== 'JWT') {
24
26
  return {
25
27
  valid: false,
26
- reason: `Invalid JWT type "${decoded.header.typ}". Expected "JWT".`
28
+ reason: `Invalid JWT type ${JSON.stringify(typ)}. Expected "JWT".`
27
29
  };
28
30
  }
29
- if (decoded.header.alg !== 'RS256') {
31
+ if (!algToHash[alg]) {
30
32
  return {
31
33
  valid: false,
32
- reason: `Invalid JWT algorithm "${decoded.header.alg}". Expected "RS256".`
34
+ reason: `Invalid JWT algorithm ${JSON.stringify(
35
+ alg
36
+ )}. Supported: ${algs}.`
33
37
  };
34
38
  }
35
- if (
36
- typeof decoded.payload.aud === 'string' &&
37
- decoded.payload.aud !== audience
38
- ) {
39
+
40
+ const { sub, aud, iss, iat, exp, nbf } = decoded.payload;
41
+ if (typeof sub !== 'string') {
39
42
  return {
40
43
  valid: false,
41
- reason: `Invalid JWT audience "${decoded.payload.aud}". Expected "${audience}".`
44
+ reason: `Subject claim (sub) is required and must be a string. Received ${JSON.stringify(
45
+ sub
46
+ )}.`
42
47
  };
48
+ }
49
+
50
+ if (typeof aud === 'string') {
51
+ if (aud !== audience) {
52
+ return {
53
+ valid: false,
54
+ reason: `Invalid JWT audience claim (aud) ${JSON.stringify(
55
+ aud
56
+ )}. Expected "${audience}".`
57
+ };
58
+ }
43
59
  } else if (
44
- Array.isArray(decoded.payload.aud) &&
45
- !decoded.payload.aud.includes(audience)
60
+ Array.isArray(aud) &&
61
+ aud.length > 0 &&
62
+ aud.every(a => typeof a === 'string')
46
63
  ) {
64
+ if (!aud.includes(audience)) {
65
+ return {
66
+ valid: false,
67
+ reason: `Invalid JWT audience claim array (aud) ${JSON.stringify(
68
+ aud
69
+ )}. Does not include "${audience}".`
70
+ };
71
+ }
72
+ } else {
47
73
  return {
48
74
  valid: false,
49
- reason: `Invalid JWT audience in array "${decoded.payload.aud}". Does not include "${audience}".`
75
+ reason: `Invalid JWT audience claim (aud) ${JSON.stringify(
76
+ aud
77
+ )}. Expected a string or a non-empty array of strings.`
50
78
  };
51
79
  }
52
80
 
53
- if (decoded.payload.iss !== issuer) {
81
+ if (iss !== issuer) {
54
82
  return {
55
83
  valid: false,
56
- reason: `Invalid JWT issuer "${decoded.payload.iss}". Expected "${issuer}".`
84
+ reason: `Invalid JWT issuer claim (iss) ${JSON.stringify(
85
+ decoded.payload.iss
86
+ )}. Expected "${issuer}".`
87
+ };
88
+ }
89
+
90
+ if (typeof exp !== 'number') {
91
+ return {
92
+ valid: false,
93
+ reason: `Invalid JWT expiry date claim (exp) ${JSON.stringify(
94
+ exp
95
+ )}. Expected number.`
57
96
  };
58
97
  }
59
- const expiryDate = new Date(0);
60
- expiryDate.setUTCSeconds(decoded.payload.exp);
61
98
  const currentDate = new Date(Date.now());
62
- const expired = expiryDate <= currentDate;
99
+ const expiryDate = new Date(0);
100
+ expiryDate.setUTCSeconds(exp);
101
+ const expired = expiryDate.getTime() <= currentDate.getTime() - skewMs;
63
102
  if (expired) {
64
103
  return {
65
104
  valid: false,
66
105
  reason: `JWT is expired. Expiry date: ${expiryDate}; Current date: ${currentDate};`
67
106
  };
68
107
  }
69
- let key: CryptoKey;
108
+
109
+ if (nbf !== undefined) {
110
+ if (typeof nbf !== 'number') {
111
+ return {
112
+ valid: false,
113
+ reason: `Invalid JWT not before date claim (nbf) ${JSON.stringify(
114
+ nbf
115
+ )}. Expected number.`
116
+ };
117
+ }
118
+ const notBeforeDate = new Date(0);
119
+ notBeforeDate.setUTCSeconds(nbf);
120
+ const early = notBeforeDate.getTime() > currentDate.getTime() + skewMs;
121
+ if (early) {
122
+ return {
123
+ valid: false,
124
+ reason: `JWT cannot be used prior to not before date claim (nbf). Not before date: ${notBeforeDate}; Current date: ${currentDate};`
125
+ };
126
+ }
127
+ }
128
+
129
+ if (iat !== undefined) {
130
+ if (typeof iat !== 'number') {
131
+ return {
132
+ valid: false,
133
+ reason: `Invalid JWT issued at date claim (iat) ${JSON.stringify(
134
+ iat
135
+ )}. Expected number.`
136
+ };
137
+ }
138
+ const issuedAtDate = new Date(0);
139
+ issuedAtDate.setUTCSeconds(iat);
140
+ const postIssued = issuedAtDate.getTime() > currentDate.getTime() + skewMs;
141
+ if (postIssued) {
142
+ return {
143
+ valid: false,
144
+ reason: `JWT issued at date claim (iat) is in the future. Issued at date: ${issuedAtDate}; Current date: ${currentDate};`
145
+ };
146
+ }
147
+ }
148
+
149
+ let key: CryptoKey | null;
70
150
  try {
71
- key = await getKey(decoded);
151
+ key = await resolveKey(decoded);
72
152
  } catch {
73
153
  return {
74
154
  valid: false,
75
- reason: `Error retrieving key to verify JWT signature.`
155
+ reason: `Error retrieving public key to verify JWT signature.`
156
+ };
157
+ }
158
+ if (!key) {
159
+ return {
160
+ valid: false,
161
+ reason: `Unable to resolve public key to verify JWT signature.`
76
162
  };
77
163
  }
78
164
  let signatureValid: boolean;
package/src/types.ts CHANGED
@@ -4,6 +4,8 @@ export interface JwtPayload {
4
4
  aud: string | Array<string>;
5
5
  iat: number;
6
6
  exp: number;
7
+ nbf?: number;
8
+ jti?: string;
7
9
  }
8
10
 
9
11
  export interface JwtHeader {
@@ -28,7 +30,7 @@ export interface RawJwt {
28
30
  export interface DecodedJwt {
29
31
  header: JwtHeader;
30
32
  payload: JwtPayload;
31
- signature: string;
33
+ signature: Uint8Array;
32
34
  raw: RawJwt;
33
35
  }
34
36
 
package/src/verify.ts CHANGED
@@ -7,9 +7,11 @@ import { DecodedJwt } from './types.js';
7
7
  export async function verifyJwtSignature(decoded: DecodedJwt, key: CryptoKey) {
8
8
  const encoder = new TextEncoder();
9
9
  const data = encoder.encode(`${decoded.raw.header}.${decoded.raw.payload}`);
10
- const signature = new Uint8Array(
11
- Array.from(decoded.signature).map(c => c.charCodeAt(0))
12
- );
13
10
 
14
- return crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, signature, data);
11
+ return crypto.subtle.verify(
12
+ 'RSASSA-PKCS1-v1_5',
13
+ key,
14
+ decoded.signature,
15
+ data
16
+ );
15
17
  }