Skip to content

Commit fb963c7

Browse files
authored
Remove dependency on pkg:jose (#87)
1 parent db4f670 commit fb963c7

File tree

8 files changed

+59
-47
lines changed

8 files changed

+59
-47
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ jobs:
8181
run: dart --version
8282

8383
- name: Install dependencies
84-
run: dart pub get
84+
run: dart pub upgrade
8585

8686
- name: Verify formatting
8787
run: dart format --output=none --set-exit-if-changed .
@@ -117,11 +117,11 @@ jobs:
117117
sdk: stable
118118

119119
- name: Install dependencies (root)
120-
run: dart pub get
120+
run: dart pub upgrade
121121

122122
- name: Install dependencies (fixture)
123123
working-directory: test/fixtures/dart_reference
124-
run: dart pub get
124+
run: dart pub upgrade
125125

126126
- name: Run build_runner
127127
working-directory: test/fixtures/dart_reference
@@ -170,7 +170,7 @@ jobs:
170170
cache-dependency-path: test/fixtures/nodejs_reference/package-lock.json
171171

172172
- name: Install Dart dependencies
173-
run: dart pub get
173+
run: dart pub upgrade
174174

175175
- name: Download Dart manifest from builder-tests
176176
uses: actions/download-artifact@v4
@@ -281,7 +281,7 @@ jobs:
281281
run: firebase --version
282282

283283
- name: Install dependencies (root)
284-
run: dart pub get
284+
run: dart pub upgrade
285285

286286
- name: Download Dart manifest from builder-tests
287287
uses: actions/download-artifact@v4

.github/workflows/validate-local.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ echo "========================================="
6060
echo ""
6161

6262
echo "Installing dependencies..."
63-
dart pub get
63+
dart pub upgrade
6464

6565
echo "Checking formatting..."
6666
if dart format --output=none --set-exit-if-changed .; then
@@ -104,7 +104,7 @@ echo ""
104104

105105
echo "Installing fixture dependencies..."
106106
cd test/fixtures/dart_reference
107-
dart pub get
107+
dart pub upgrade
108108

109109
echo "Running build_runner..."
110110
if dart run build_runner build --delete-conflicting-outputs; then

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ All submissions, including submissions by project members, require review. We us
2929

3030
```bash
3131
cd firebase-functions-dart
32-
dart pub get
32+
dart pub upgrade
3333
```
3434

3535
3. Verify your setup by running the analyzer and tests:

example/client_app/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ A simple web client demonstrating how to call Firebase Functions (both `onReques
88

99
```bash
1010
cd ../basic
11-
dart pub get
11+
dart pub upgrade
1212
dart run build_runner build --delete-conflicting-outputs
1313
firebase emulators:start --only functions,auth
1414
```

lib/src/identity/token_verifier.dart

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ library;
1717

1818
import 'dart:convert';
1919

20+
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
2021
import 'package:http/http.dart' as http;
21-
import 'package:jose/jose.dart'
22-
show JoseException, JsonWebKey, JsonWebKeyStore, JsonWebToken;
2322

2423
import '../https/error.dart';
2524

@@ -48,8 +47,8 @@ class AuthBlockingTokenVerifier {
4847
final bool isEmulator;
4948
final http.Client _httpClient;
5049

51-
/// Cached JsonWebKeyStore with Google's public keys.
52-
static JsonWebKeyStore? _cachedKeyStore;
50+
/// Cached public keys.
51+
static Map<String, JWTKey>? _cachedKeys;
5352

5453
/// When the cached keys expire.
5554
static DateTime? _keysExpireAt;
@@ -75,21 +74,40 @@ class AuthBlockingTokenVerifier {
7574
return _unsafeDecode(token);
7675
}
7776

78-
// Get the key store with Google's keys
79-
final keyStore = await _getGoogleKeyStore();
77+
// Decode the token first to get the header
78+
JWT decoded;
79+
try {
80+
decoded = JWT.decode(token);
81+
} catch (e) {
82+
throw UnauthenticatedError('Invalid JWT format: $e');
83+
}
84+
85+
final kid = decoded.header?['kid'];
86+
if (kid is! String) {
87+
throw UnauthenticatedError(
88+
'Invalid JWT: missing "kid" header or not String',
89+
);
90+
}
91+
92+
// Get the keys from Google
93+
final keys = await _getGoogleKeys();
94+
final key = keys[kid];
8095

81-
// Verify the token using jose
82-
JsonWebToken jwt;
96+
if (key == null) {
97+
throw UnauthenticatedError('Invalid JWT: unknown "kid"');
98+
}
99+
100+
// Verify the token using dart_jsonwebtoken
83101
try {
84-
jwt = await JsonWebToken.decodeAndVerify(token, keyStore);
85-
} on JoseException catch (e) {
102+
JWT.verify(token, key);
103+
} on JWTException catch (e) {
86104
throw UnauthenticatedError('Invalid JWT: ${e.message}');
87105
} catch (e) {
88106
throw UnauthenticatedError('Invalid JWT: $e');
89107
}
90108

91109
// Extract the payload as a map
92-
final payload = jwt.claims.toJson();
110+
final payload = decoded.payload as Map<String, dynamic>;
93111

94112
// Validate Firebase-specific claims
95113
_validateClaims(payload, audience);
@@ -99,31 +117,24 @@ class AuthBlockingTokenVerifier {
99117

100118
/// Decodes a JWT without verification (for emulator mode only).
101119
Map<String, dynamic> _unsafeDecode(String token) {
102-
final parts = token.split('.');
103-
if (parts.length != 3) {
120+
try {
121+
final decoded = JWT.decode(token);
122+
return decoded.payload as Map<String, dynamic>;
123+
} catch (e) {
104124
throw InvalidArgumentError('Invalid JWT format');
105125
}
106-
107-
final payloadJson = _decodeBase64Url(parts[1]);
108-
return jsonDecode(payloadJson) as Map<String, dynamic>;
109-
}
110-
111-
/// Decodes a base64url string to a UTF-8 string.
112-
String _decodeBase64Url(String input) {
113-
final normalized = base64Url.normalize(input);
114-
return utf8.decode(base64Url.decode(normalized));
115126
}
116127

117-
/// Fetches Google's public keys and creates a JsonWebKeyStore.
128+
/// Fetches Google's public keys and returns a map of kid to JWTKey.
118129
///
119130
/// Uses the JWK endpoint which returns keys directly in JSON Web Key format,
120131
/// so no manual certificate parsing is needed.
121-
Future<JsonWebKeyStore> _getGoogleKeyStore() async {
122-
// Return cached key store if still valid
123-
if (_cachedKeyStore != null &&
132+
Future<Map<String, JWTKey>> _getGoogleKeys() async {
133+
// Return cached keys if still valid
134+
if (_cachedKeys != null &&
124135
_keysExpireAt != null &&
125136
DateTime.now().isBefore(_keysExpireAt!)) {
126-
return _cachedKeyStore!;
137+
return _cachedKeys!;
127138
}
128139

129140
// Fetch keys from Google's JWK endpoint
@@ -137,15 +148,18 @@ class AuthBlockingTokenVerifier {
137148

138149
// Parse the JWK Set response
139150
final jwksJson = jsonDecode(response.body) as Map<String, dynamic>;
140-
final keyStore = JsonWebKeyStore();
151+
final newKeys = <String, JWTKey>{};
141152

142153
// The response contains a "keys" array with JWK objects
143154
final keys = jwksJson['keys'] as List<dynamic>?;
144155
if (keys != null) {
145156
for (final keyJson in keys) {
146157
try {
147-
final jwk = JsonWebKey.fromJson(keyJson as Map<String, dynamic>);
148-
keyStore.addKey(jwk);
158+
final jwk = keyJson as Map<String, dynamic>;
159+
final kid = jwk['kid'] as String?;
160+
if (kid != null) {
161+
newKeys[kid] = JWTKey.fromJWK(jwk);
162+
}
149163
} catch (e) {
150164
// Skip keys that fail to parse
151165
continue;
@@ -164,10 +178,10 @@ class AuthBlockingTokenVerifier {
164178
}
165179
}
166180

167-
_cachedKeyStore = keyStore;
181+
_cachedKeys = newKeys;
168182
_keysExpireAt = DateTime.now().add(cacheDuration);
169183

170-
return keyStore;
184+
return newKeys;
171185
}
172186

173187
/// Validates JWT claims.
@@ -238,7 +252,7 @@ class AuthBlockingTokenVerifier {
238252

239253
/// Clears the key cache (useful for testing).
240254
static void clearCertificateCache() {
241-
_cachedKeyStore = null;
255+
_cachedKeys = null;
242256
_keysExpireAt = null;
243257
}
244258
}

pubspec.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,14 @@ dependencies:
5353
stack_trace: ^1.12.1
5454
protobuf: ^6.0.0
5555
http: ^1.6.0
56-
# Pin as this is an external dependency.
57-
# TODO: Vendor in this package.
58-
jose: 0.3.5
5956
# Required for builder
6057
build: ^4.0.4
6158
source_gen: ^4.2.0
6259
analyzer: ^10.0.1
6360
glob: ^2.1.3
6461
yaml_edit: ^2.2.3
6562
google_cloud_storage: ^0.5.1
63+
dart_jsonwebtoken: ^3.1.1
6664

6765
dev_dependencies:
6866
build_runner: ^2.10.5

test/fixtures/dart_reference/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ This example demonstrates the core functionality of Firebase Functions for Dart.
3131
### Install Dependencies
3232

3333
```bash
34-
dart pub get
34+
dart pub upgrade
3535
```
3636

3737
### Run Locally

test/snapshots/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Add to CI pipeline:
117117
- name: Run Snapshot Tests
118118
run: |
119119
cd firebase-functions-dart
120-
dart pub get
120+
dart pub upgrade
121121
dart run build_runner build
122122
dart test test/snapshots/
123123
```

0 commit comments

Comments
 (0)