11import 'dart:async' ;
22import 'dart:convert' ;
33
4- import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart' ;
54import 'package:gotrue/gotrue.dart' ;
65import 'package:http/http.dart' ;
76import 'package:test/test.dart' ;
@@ -50,8 +49,8 @@ class _SetSessionMockClient extends BaseClient {
5049 // Refresh-token fallback response with a freshly minted access token.
5150 final exp = DateTime .now ().millisecondsSinceEpoch ~ / 1000 + 3600 ;
5251 final iat = exp - 3600 ;
53- final freshAt = JWT ({ 'exp' : exp, 'iat' : iat}, subject : 'mock-user-id' )
54- . sign ( SecretKey ( 'test-secret' ) );
52+ final freshAt =
53+ _makeRawJwt ({ 'exp' : exp, 'iat' : iat, 'sub' : 'mock-user-id' } );
5554 return StreamedResponse (
5655 Stream .value (utf8.encode (jsonEncode ({
5756 'access_token' : freshAt,
@@ -68,12 +67,17 @@ class _SetSessionMockClient extends BaseClient {
6867 }
6968}
7069
71- /// Returns a signed JWT with the given [exp] and optional [iat] claims.
72- /// If [iat] is null, the claim is omitted entirely.
73- String _makeJwt ({required int exp, int ? iat}) {
74- final payload = < String , dynamic > {'exp' : exp};
75- if (iat != null ) payload['iat' ] = iat;
76- return JWT (payload, subject: 'mock-user-id' ).sign (SecretKey ('test-secret' ));
70+ /// Crafts a JWT by base64url-encoding [payload] directly.
71+ ///
72+ /// Unlike using dart_jsonwebtoken, this gives exact control over every claim —
73+ /// no auto-injected `iat` , no claim overrides. The signature is a stub;
74+ /// [decodeJwt] does not verify signatures.
75+ String _makeRawJwt (Map <String , dynamic > payload) {
76+ final header =
77+ base64Url.encode (utf8.encode (jsonEncode ({'alg' : 'HS256' , 'typ' : 'JWT' })));
78+ final body = base64Url.encode (utf8.encode (jsonEncode (payload)));
79+ const sig = 'AAAA' ;
80+ return '$header .$body .$sig ' ;
7781}
7882
7983void main () {
@@ -94,7 +98,8 @@ void main() {
9498 'empty refresh token with a non-null access token throws before '
9599 'inspecting the access token' , () async {
96100 final exp = DateTime .now ().millisecondsSinceEpoch ~ / 1000 + 3600 ;
97- final at = _makeJwt (exp: exp, iat: exp - 3600 );
101+ final at =
102+ _makeRawJwt ({'exp' : exp, 'iat' : exp - 3600 , 'sub' : 'mock-user-id' });
98103
99104 await expectLater (
100105 () => client.setSession ('' , accessToken: at),
@@ -109,7 +114,8 @@ void main() {
109114 'as expired and falls back to the refresh-token path' , () async {
110115 final timeNow = DateTime .now ().millisecondsSinceEpoch ~ / 1000 ;
111116 // exp is 20 s in the future, inside the 30 s Constants.expiryMargin.
112- final at = _makeJwt (exp: timeNow + 20 , iat: timeNow - 3580 );
117+ final at = _makeRawJwt (
118+ {'exp' : timeNow + 20 , 'iat' : timeNow - 3580 , 'sub' : 'mock-user-id' });
113119
114120 final response =
115121 await client.setSession ('some-refresh-token' , accessToken: at);
@@ -124,8 +130,7 @@ void main() {
124130 'access token with no exp claim is treated as expired and falls back '
125131 'to the refresh-token path' , () async {
126132 // JWT without an exp claim: decodeJwt succeeds but exp == null.
127- final at = JWT ({'role' : 'authenticated' }, subject: 'mock-user-id' )
128- .sign (SecretKey ('test-secret' ));
133+ final at = _makeRawJwt ({'role' : 'authenticated' , 'sub' : 'mock-user-id' });
129134
130135 final response =
131136 await client.setSession ('some-refresh-token' , accessToken: at);
@@ -141,7 +146,7 @@ void main() {
141146 () async {
142147 final iat = DateTime .now ().millisecondsSinceEpoch ~ / 1000 - 60 ;
143148 final exp = iat + 3600 ;
144- final at = _makeJwt ( exp: exp, iat: iat);
149+ final at = _makeRawJwt ({ ' exp' : exp, ' iat' : iat, 'sub' : 'mock-user-id' } );
145150
146151 final response =
147152 await client.setSession ('some-refresh-token' , accessToken: at);
@@ -153,7 +158,7 @@ void main() {
153158 test ('expiresIn is null when iat claim is absent' , () async {
154159 final exp = DateTime .now ().millisecondsSinceEpoch ~ / 1000 + 3600 ;
155160 // JWT without iat.
156- final at = _makeJwt ( exp: exp);
161+ final at = _makeRawJwt ({ ' exp' : exp, 'sub' : 'mock-user-id' } );
157162
158163 final response =
159164 await client.setSession ('some-refresh-token' , accessToken: at);
@@ -164,7 +169,7 @@ void main() {
164169 test ('expiresAt matches the exp claim in the JWT' , () async {
165170 final iat = DateTime .now ().millisecondsSinceEpoch ~ / 1000 - 60 ;
166171 final exp = iat + 3600 ;
167- final at = _makeJwt ( exp: exp, iat: iat);
172+ final at = _makeRawJwt ({ ' exp' : exp, ' iat' : iat, 'sub' : 'mock-user-id' } );
168173
169174 final response =
170175 await client.setSession ('some-refresh-token' , accessToken: at);
@@ -178,7 +183,7 @@ void main() {
178183 final iat = DateTime .now ().millisecondsSinceEpoch ~ / 1000 - 60 ;
179184 final exp = iat + 3600 ;
180185 const refreshToken = 'my-refresh-token' ;
181- final at = _makeJwt ( exp: exp, iat: iat);
186+ final at = _makeRawJwt ({ ' exp' : exp, ' iat' : iat, 'sub' : 'mock-user-id' } );
182187
183188 final response = await client.setSession (refreshToken, accessToken: at);
184189
@@ -192,7 +197,7 @@ void main() {
192197 test ('fast path emits signedIn (not tokenRefreshed)' , () async {
193198 final iat = DateTime .now ().millisecondsSinceEpoch ~ / 1000 - 60 ;
194199 final exp = iat + 3600 ;
195- final at = _makeJwt ( exp: exp, iat: iat);
200+ final at = _makeRawJwt ({ ' exp' : exp, ' iat' : iat, 'sub' : 'mock-user-id' } );
196201
197202 expect (
198203 client.onAuthStateChange,
@@ -205,7 +210,8 @@ void main() {
205210 test ('expired-fallback path emits tokenRefreshed (not signedIn)' , () async {
206211 final timeNow = DateTime .now ().millisecondsSinceEpoch ~ / 1000 ;
207212 // Clearly expired token (exp well in the past).
208- final at = _makeJwt (exp: timeNow - 100 , iat: timeNow - 3700 );
213+ final at = _makeRawJwt (
214+ {'exp' : timeNow - 100 , 'iat' : timeNow - 3700 , 'sub' : 'mock-user-id' });
209215
210216 expect (
211217 client.onAuthStateChange,
@@ -223,7 +229,7 @@ void main() {
223229 'and both receive the same response' , () async {
224230 final iat = DateTime .now ().millisecondsSinceEpoch ~ / 1000 - 60 ;
225231 final exp = iat + 3600 ;
226- final at = _makeJwt ( exp: exp, iat: iat);
232+ final at = _makeRawJwt ({ ' exp' : exp, ' iat' : iat, 'sub' : 'mock-user-id' } );
227233
228234 // Pause the mock /user response so both calls are in-flight together.
229235 final pause = Completer <void >();
@@ -245,7 +251,7 @@ void main() {
245251 test ('deduplicated call emits signedIn exactly once' , () async {
246252 final iat = DateTime .now ().millisecondsSinceEpoch ~ / 1000 - 60 ;
247253 final exp = iat + 3600 ;
248- final at = _makeJwt ( exp: exp, iat: iat);
254+ final at = _makeRawJwt ({ ' exp' : exp, ' iat' : iat, 'sub' : 'mock-user-id' } );
249255
250256 final pause = Completer <void >();
251257 mockClient.userCallPause = pause;
0 commit comments