@@ -20,6 +20,9 @@ class JWT {
2020 /// value is a timestamp (number of seconds since epoch) in UTC if
2121 /// [issueAtUtc] is true, it is compared to the value of the 'iat' claim.
2222 /// Verification fails if the 'iat' claim is before [issueAt] .
23+ ///
24+ /// If the embedded `payload` is not a JSON map (but rather just a plain string),
25+ /// none of the verifications are executed. In that case only the signature is verified.
2326 static JWT verify (
2427 String token,
2528 JWTKey key, {
@@ -35,6 +38,13 @@ class JWT {
3538 }) {
3639 try {
3740 final parts = token.split ('.' );
41+
42+ if (parts.length != 3 ) {
43+ throw JWTInvalidException (
44+ 'token does not use JWS Compact Serialization' ,
45+ );
46+ }
47+
3848 final header = jsonBase64.decode (base64Padded (parts[0 ]));
3949
4050 if (header == null || header is ! Map <String , dynamic >) {
@@ -54,10 +64,11 @@ class JWT {
5464 throw JWTInvalidException ('invalid signature' );
5565 }
5666
57- dynamic payload;
67+ Object payload;
5868
5969 try {
60- payload = jsonBase64.decode (base64Padded (parts[1 ]));
70+ payload =
71+ jsonBase64.decode (base64Padded (parts[1 ])) as Map <String , dynamic >;
6172 } catch (ex) {
6273 payload = utf8.decode (base64Url.decode (base64Padded (parts[1 ])));
6374 }
@@ -186,6 +197,7 @@ class JWT {
186197 jwtId: jwtId,
187198 );
188199 } catch (ex) {
200+ print (ex);
189201 return null ;
190202 }
191203 }
@@ -194,18 +206,15 @@ class JWT {
194206 ///
195207 /// This also sets [JWT.audience] , [JWT.subject] , [JWT.issuer] , and
196208 /// [JWT.jwtId] even though they are not verified. Use with caution.
209+ ///
210+ /// This methods only supports map payloads. For `String` payloads use `verify` .
197211 static JWT decode (String token) {
198212 try {
199213 final parts = token.split ('.' );
200- var header = jsonBase64.decode (base64Padded (parts[0 ]));
214+ final header = jsonBase64.decode (base64Padded (parts[0 ]));
201215
202- dynamic payload;
203-
204- try {
205- payload = jsonBase64.decode (base64Padded (parts[1 ]));
206- } catch (ex) {
207- payload = utf8.decode (base64Url.decode (base64Padded (parts[1 ])));
208- }
216+ final payload =
217+ (jsonBase64.decode (base64Padded (parts[1 ])) as Map <String , dynamic >);
209218
210219 final audiance = _parseAud (payload['aud' ]);
211220 final issuer = payload['iss' ]? .toString ();
@@ -246,10 +255,20 @@ class JWT {
246255 this .issuer,
247256 this .jwtId,
248257 this .header,
249- });
258+ }) {
259+ if (payload is ! String &&
260+ payload is ! Map <dynamic , dynamic > &&
261+ payload is ! Map <String , dynamic >) {
262+ throw Exception ('Unexpected payload type `${payload .runtimeType }`' );
263+ }
264+ }
250265
251- /// Custom claims
252- dynamic payload;
266+ /// The token's payload, either as a `Map<String, dynamic>` , `Map<dynamic, dynamic>` ,
267+ /// or plain `String` (in case it was not a JSON-encoded map).
268+ ///
269+ /// If it's a map, it has all claims, containing the utilized registered claims
270+ /// as well custom ones added.
271+ Object payload;
253272
254273 /// Audience claim
255274 Audience ? audience;
@@ -281,9 +300,17 @@ class JWT {
281300 bool noIssueAt = false ,
282301 }) {
283302 try {
284- if (payload is Map <String , dynamic > || payload is Map <dynamic , dynamic >) {
285- try {
286- payload = Map <String , dynamic >.from (payload);
303+ final tokenHeader = Map .from (header ?? {});
304+ tokenHeader.putIfAbsent ('alg' , () => algorithm.name);
305+ tokenHeader.putIfAbsent ('typ' , () => 'JWT' );
306+
307+ final b64Header = base64Unpadded (jsonBase64.encode (tokenHeader));
308+
309+ final String b64Payload;
310+
311+ try {
312+ if (payload is Map ) {
313+ final payload = < String , dynamic > {...(this .payload as Map )};
287314
288315 if (! noIssueAt) {
289316 payload['iat' ] = secondsSinceEpoch (timeNowUTC ());
@@ -298,34 +325,20 @@ class JWT {
298325 if (subject != null ) payload['sub' ] = subject;
299326 if (issuer != null ) payload['iss' ] = issuer;
300327 if (jwtId != null ) payload['jti' ] = jwtId;
301- } catch (ex) {
302- assert (
303- payload is Map <String , dynamic >,
304- 'If payload is a Map its must be a Map<String, dynamic>' ,
305- );
306- }
307- }
308-
309- final tokenHeader = Map .from (header ?? {});
310- tokenHeader.putIfAbsent ('alg' , () => algorithm.name);
311- tokenHeader.putIfAbsent ('typ' , () => 'JWT' );
312-
313- final b64Header = base64Unpadded (jsonBase64.encode (tokenHeader));
314328
315- String b64Payload;
316- try {
317- b64Payload = base64Unpadded (
318- payload is String
319- ? base64Url.encode (utf8.encode (payload))
320- : jsonBase64.encode (payload),
321- );
322- } catch (ex) {
329+ b64Payload = jsonBase64.encode (payload);
330+ } else {
331+ b64Payload = base64Url.encode (utf8.encode (payload as String ));
332+ }
333+ } catch (e, s) {
334+ print (e);
335+ print (s);
323336 throw JWTException (
324- 'invalid payload json format (Map keys must be String type) ' ,
337+ 'invalid payload, must be String or Map<String, dynamic>, but got ${ payload . runtimeType } ' ,
325338 );
326339 }
327340
328- final body = '$b64Header .$b64Payload ' ;
341+ final body = '$b64Header .${ base64Unpadded ( b64Payload )} ' ;
329342 final signature = base64Unpadded (
330343 base64Url.encode (
331344 algorithm.sign (
0 commit comments