Skip to content

Commit 7f41e06

Browse files
grdsdevclaude
andcommitted
feat(gotrue): make getClaims() non-experimental, add options parameter
Following up on the initial getClaims implementation, this commit: - Removes experimental status from getClaims() method - Adds GetClaimsOptions class with allowExpired parameter - Updates getClaims() to accept optional options parameter - Improves documentation to better describe the method's behavior - Exports helper functions (decodeJwt, validateExp) for public use - Adds tests for allowExpired option The allowExpired option allows users to extract claims from expired JWTs without throwing an error during expiration validation. This is useful for scenarios where you need to access JWT data even after expiration. Ported from: supabase/auth-js#1078 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent bc59513 commit 7f41e06

File tree

4 files changed

+69
-12
lines changed

4 files changed

+69
-12
lines changed

packages/gotrue/lib/gotrue.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export 'src/constants.dart'
44
hide Constants, GenerateLinkTypeExtended, AuthChangeEventExtended;
55
export 'src/gotrue_admin_api.dart';
66
export 'src/gotrue_client.dart';
7+
export 'src/helper.dart' show decodeJwt, validateExp;
78
export 'src/types/auth_exception.dart';
89
export 'src/types/auth_response.dart' hide ToSnakeCase;
910
export 'src/types/auth_state.dart';

packages/gotrue/lib/src/gotrue_client.dart

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,17 +1337,24 @@ class GoTrueClient {
13371337
return exception;
13381338
}
13391339

1340-
/// Gets the claims from a JWT token.
1340+
/// Extracts the JWT claims present in the access token by first verifying the
1341+
/// JWT against the server. Prefer this method over [getUser] when you only
1342+
/// need to access the claims and not the full user object.
13411343
///
1342-
/// This method verifies the JWT by calling [getUser] to validate against the server.
1343-
/// It supports both symmetric (HS256) and asymmetric (RS256, ES256) JWTs.
1344+
/// If the project is not using an asymmetric JWT signing key (like ECC or
1345+
/// RSA), it always sends a request to the Auth server (similar to [getUser])
1346+
/// to verify the JWT.
13441347
///
1345-
/// [jwt] The JWT token to get claims from. If not provided, uses the current session's access token.
1348+
/// [jwt] An optional specific JWT you wish to verify, not the one you
1349+
/// can obtain from [currentSession].
1350+
/// [options] Various additional options that allow you to customize the
1351+
/// behavior of this method.
13461352
///
13471353
/// Returns a [GetClaimsResponse] containing the JWT claims, or throws an [AuthException] on error.
1348-
///
1349-
/// Note: This is an experimental API and may change in future versions.
1350-
Future<GetClaimsResponse> getClaims([String? jwt]) async {
1354+
Future<GetClaimsResponse> getClaims([
1355+
String? jwt,
1356+
GetClaimsOptions? options,
1357+
]) async {
13511358
try {
13521359
String token = jwt ?? '';
13531360

@@ -1362,8 +1369,10 @@ class GoTrueClient {
13621369
// Decode the JWT to get the payload
13631370
final decoded = decodeJwt(token);
13641371

1365-
// Validate expiration
1366-
validateExp(decoded.payload.exp);
1372+
// Validate expiration unless allowExpired is true
1373+
if (!(options?.allowExpired ?? false)) {
1374+
validateExp(decoded.payload.exp);
1375+
}
13671376

13681377
// Verify the JWT by calling getUser
13691378
// This works for both symmetric and asymmetric JWTs

packages/gotrue/lib/src/types/jwt.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,14 @@ class GetClaimsResponse {
134134

135135
GetClaimsResponse({required this.claims});
136136
}
137+
138+
/// Options for getClaims method
139+
class GetClaimsOptions {
140+
/// If set to `true`, the `exp` claim will not be validated against the current time.
141+
/// This allows you to extract claims from expired JWTs without getting an error.
142+
final bool allowExpired;
143+
144+
const GetClaimsOptions({
145+
this.allowExpired = false,
146+
});
147+
}

packages/gotrue/test/get_claims_test.dart

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import 'dart:convert';
2-
31
import 'package:dotenv/dotenv.dart';
42
import 'package:gotrue/gotrue.dart';
53
import 'package:http/http.dart' as http;
@@ -47,7 +45,6 @@ void main() {
4745
);
4846

4947
expect(response.session, isNotNull);
50-
final session = response.session!;
5148

5249
// Get claims from current session
5350
final claimsResponse = await client.getClaims();
@@ -111,6 +108,45 @@ void main() {
111108
);
112109
});
113110

111+
test('getClaims() with allowExpired option allows expired JWT', () async {
112+
// This is an expired JWT token (exp is in the past)
113+
const expiredJwt =
114+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNTE2MjM5MDIyfQ.4Adcj0vVzr2Nzz_KKAKrVZsLZyTBGv9-Ey8SN0p7Kzs';
115+
116+
// With allowExpired, we should be able to decode the JWT
117+
// Note: This will still fail at getUser() because the token is invalid on the server
118+
// but the expiration check should pass
119+
try {
120+
await client.getClaims(
121+
expiredJwt,
122+
GetClaimsOptions(allowExpired: true),
123+
);
124+
// If we get here, the exp validation was skipped
125+
} on AuthException catch (e) {
126+
// We expect this to fail during getUser() verification,
127+
// not during exp validation
128+
expect(e.message, isNot(contains('expired')));
129+
}
130+
});
131+
132+
test('getClaims() with options parameter (allowExpired false)', () async {
133+
final response = await client.signUp(
134+
email: newEmail,
135+
password: password,
136+
);
137+
138+
expect(response.session, isNotNull);
139+
140+
// Should work normally with allowExpired: false
141+
final claimsResponse = await client.getClaims(
142+
null,
143+
GetClaimsOptions(allowExpired: false),
144+
);
145+
146+
expect(claimsResponse.claims, isNotNull);
147+
expect(claimsResponse.claims['email'], newEmail);
148+
});
149+
114150
test('getClaims() verifies JWT with server', () async {
115151
// Sign up a user
116152
final response = await client.signUp(

0 commit comments

Comments
 (0)