fix(auth): fix getClaims() crash with asymmetric JWTs on first call#1300
Merged
grdsdev merged 1 commit intoJan 22, 2026
Conversation
Fixed a crash in getClaims() that occurred when verifying JWTs signed with asymmetric algorithms (RS256, ES256) on the first call. The issue was that _jwks was force-unwrapped (_jwks!) before it was initialized. On the first call to getClaims() with an asymmetric JWT containing a 'kid' header, _jwks would be null, causing a null check operator error. The fix passes an empty JWKSet when the cache is null: _jwks ?? JWKSet(keys: []). This allows _fetchJwk() to handle the first call gracefully by fetching from the server and populating the cache. Changes: - Updated getClaims() to use null-coalescing operator instead of force-unwrap - Added test case to reproduce and verify the fix for SDK-627 - Enhanced documentation to clarify JWKS caching behavior for asymmetric JWTs Linear: SDK-627 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Pull Request Test Coverage Report for Build 21220881240Details
💛 - Coveralls |
mandarini
approved these changes
Jan 22, 2026
|
@grdsdev This will fail for JWTs not using RSA asymmetric signing. Here's a integration test I wrote to prove it in a project using a signing keys generated by import 'package:test/test.dart';
import 'package:supabase/supabase.dart';
void main() {
test(
'auth.getClaims throws with some Asymmetric key JWT',
() async {
final env = Env();
final supabase = SupabaseClient(
env.supabaseUrl,
env.supabaseKey,
);
final auth = supabase.auth;
Object? thrown;
StackTrace? thrownStack;
await supabase.auth.signInWithPassword(
password: 'Mango123@',
email: 'admin@mail.com',
);
try {
await auth.getClaims();
} catch (error, stackTrace) {
thrown = error;
thrownStack = stackTrace;
// Logged for easy debugging when running this test locally.
// ignore: avoid_print
print('auth.getClaims error: $error');
// ignore: avoid_print
print(stackTrace);
}
expect(thrown, isNotNull, reason: 'Expected auth.getClaims() to throw.');
expect(thrownStack, isNotNull);
},
timeout: const Timeout(Duration(minutes: 1)),
);
} |
|
Hi @mandarini, this is also affecting my project. When will the pub.dev package contain this fix? I see it was merged a month ago. Thanks. Edit: getClaims() is similar to getUser() but supposed to be faster since it does not hit the db everytime. However, if I cache the results of getUser() to obtain id/email in my mobile app, is it any worse than getClaims()? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Fixes a crash in
getClaims()that occurred when verifying JWTs signed with asymmetric algorithms (RS256, ES256, etc.) on the first call.Problem
GoTrueClient.getClaims()would crash withNull check operator used on a null valuewhen called for the first time with a JWT that:kid(key ID) in the JWT headerRoot Cause
In
gotrue_client.dart:1431, the code force-unwrapped_jwks!before it was initialized:The
_jwkscache is declared as nullable and is only populated inside_fetchJwk()after fetching from/.well-known/jwks.json. This created a circular dependency:_jwksto call_fetchJwk_fetchJwkis responsible for initializing_jwksWhy CI Tests Passed
The existing tests use a GoTrue instance configured with symmetric JWT signing (HS256 via
GOTRUE_JWT_SECRET). In that case:decoded.header.alg.startsWith('HS')returnstruesigningKeybecomesnullgetUser(token)for server verification_jwks!) is never executedSolution
Replace the force-unwrap with a null-coalescing operator:
When
_jwksis null on the first call, an emptyJWKSetis passed to_fetchJwk(). The method then:/.well-known/jwks.jsonand populates_jwksChanges
getClaims()now handles null_jwkscache gracefully (lib/src/gotrue_client.dart:1436)Testing
New Test
Added
getClaims() with RS256 JWT on first call should not crash (SDK-627)that:kidheadergetClaims()on a fresh client (null_jwkscache)Verification
Impact
Apps using Supabase Auth with asymmetric JWT signing (RS256, ES256) will no longer crash when calling
getClaims()for the first time. This is a critical bug fix with no breaking changes.Related
🤖 Generated with Claude Code