diff --git a/packages/dart_firebase_admin/lib/src/auth/token_verifier.dart b/packages/dart_firebase_admin/lib/src/auth/token_verifier.dart index e65f78a..8a065f1 100644 --- a/packages/dart_firebase_admin/lib/src/auth/token_verifier.dart +++ b/packages/dart_firebase_admin/lib/src/auth/token_verifier.dart @@ -300,11 +300,12 @@ class DecodedIdToken { required this.phoneNumber, required this.picture, required this.sub, - required this.uid, + this.uid, }); @internal factory DecodedIdToken.fromMap(Map map) { + final firebaseMap = Map.from(map['firebase']! as Map); return DecodedIdToken( aud: map['aud']! as String, authTime: DateTime.fromMillisecondsSinceEpoch( @@ -314,18 +315,19 @@ class DecodedIdToken { emailVerified: map['email_verified'] as bool?, exp: map['exp']! as int, firebase: TokenProvider( - identities: Map.from(map['firebase']! as Map), - signInProvider: map['sign_in_provider']! as String, - signInSecondFactor: map['sign_in_second_factor'] as String?, - secondFactorIdentifier: map['second_factor_identifier'] as String?, - tenant: map['tenant'] as String?, + identities: firebaseMap, + signInProvider: firebaseMap['sign_in_provider']! as String, + signInSecondFactor: firebaseMap['sign_in_second_factor'] as String?, + secondFactorIdentifier: + firebaseMap['second_factor_identifier'] as String?, + tenant: firebaseMap['tenant'] as String?, ), iat: map['iat']! as int, iss: map['iss']! as String, phoneNumber: map['phone_number'] as String?, picture: map['picture'] as String?, sub: map['sub']! as String, - uid: map['uid']! as String, + uid: map['uid'] != null ? map['uid']! as String : null, ); } @@ -398,7 +400,7 @@ class DecodedIdToken { /// /// This value is not actually in the JWT token claims itself. It is added as a /// convenience, and is set as the value of the [`sub`](#sub) property. - String uid; + String? uid; /** * Other arbitrary claims included in the ID token. diff --git a/packages/dart_firebase_admin/lib/src/utils/jwt.dart b/packages/dart_firebase_admin/lib/src/utils/jwt.dart index ce546cf..9bc2a21 100644 --- a/packages/dart_firebase_admin/lib/src/utils/jwt.dart +++ b/packages/dart_firebase_admin/lib/src/utils/jwt.dart @@ -96,9 +96,40 @@ class PublicKeySignatureVerifier implements SignatureVerifier { final KeyFetcher keyFetcher; @override - Future verify(String token) { - throw UnimplementedError(); - // verifyJwtSignature(token); + Future verify(String token) async { + try { + final jwt = JWT.decode(token); + final kid = jwt.header?['kid'] as String?; + + if (kid == null) { + throw JwtError( + JwtErrorCode.noKidInHeader, + 'no-kid-in-header-error', + ); + } + + final publicKeys = await keyFetcher.fetchPublicKeys(); + final publicKey = publicKeys[kid]; + + if (publicKey == null) { + throw JwtError( + JwtErrorCode.noMatchingKid, + 'no-matching-kid-error', + ); + } + JWT.verify( + token, + RSAPublicKey.cert(publicKey), + ); + } on JWTExpiredException { + throw JwtError( + JwtErrorCode.tokenExpired, + 'The provided token has expired. Get a fresh token from your client app and try again.', + ); + } on JWTException catch (e) { + // TODO: Handle specific JWTException types to provide detailed error messages. + throw JwtError(JwtErrorCode.unknown, e.message); + } } } @@ -160,7 +191,8 @@ enum JwtErrorCode { invalidSignature('invalid-token'), noMatchingKid('no-matching-kid-error'), noKidInHeader('no-kid-error'), - keyFetchError('key-fetch-error'); + keyFetchError('key-fetch-error'), + unknown('unknown'); const JwtErrorCode(this.value);