1+ import 'dart:convert' ;
2+
13import 'package:clock/clock.dart' ;
4+ import 'package:collection/collection.dart' ;
25import 'package:firebase_admin/firebase_admin.dart' ;
36import 'package:firebase_admin/src/auth/credential.dart' ;
47import 'package:jose/jose.dart' ;
8+ import 'package:googleapis/iamcredentials/v1.dart' as iamcredentials;
9+ import 'package:googleapis/iam/v1.dart' as iam;
510
11+ import '../utils/api_request.dart' ;
612import '../utils/validator.dart' as validator;
713
814/// Class for generating different types of Firebase Auth tokens (JWTs).
@@ -14,9 +20,6 @@ class FirebaseTokenGenerator {
1420
1521 FirebaseTokenGenerator (this .app);
1622
17- Certificate get certificate =>
18- (app.options.credential as ServiceAccountCredential ).certificate;
19-
2023 // List of blacklisted claims which cannot be provided when creating a custom token
2124 static const blacklistedClaims = [
2225 'acr' ,
@@ -33,15 +36,17 @@ class FirebaseTokenGenerator {
3336 'jti' ,
3437 'nbf' ,
3538 'nonce' ,
39+ 'sub' ,
40+ 'firebase' ,
41+ 'user_id' ,
3642 ];
3743
3844 // Audience to use for Firebase Auth Custom tokens
3945 static const firebaseAudience =
4046 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit' ;
4147
42- /// Creates a new Firebase Auth Custom token.
43- Future <String > createCustomToken (
44- String uid, Map <String , dynamic > developerClaims) async {
48+ Map <String , dynamic > _createCustomTokenPayload (String uid,
49+ Map <String , dynamic > developerClaims, String serviceAccountId) {
4550 if (! validator.isUid (uid)) {
4651 throw FirebaseAuthError .invalidArgument (
4752 'First argument to createCustomToken() must be a non-empty string uid.' );
@@ -55,21 +60,66 @@ class FirebaseTokenGenerator {
5560 }
5661
5762 var iat = clock.now ();
58- var claims = {
63+ return {
5964 'aud' : firebaseAudience,
6065 'iat' : iat.millisecondsSinceEpoch ~ / 1000 ,
6166 'exp' : iat.add (Duration (hours: 1 )).millisecondsSinceEpoch ~ / 1000 ,
62- 'iss' : certificate.clientEmail ,
63- 'sub' : certificate.clientEmail ,
67+ 'iss' : serviceAccountId ,
68+ 'sub' : serviceAccountId ,
6469 'uid' : uid,
65- ... developerClaims
70+ 'claims' : developerClaims,
6671 };
72+ }
73+
74+ /// Creates a new Firebase Auth Custom token.
75+ Future <String > createCustomToken (
76+ String uid, Map <String , dynamic > developerClaims) async {
77+ var credential = app.options.credential;
78+ // If the SDK was initialized with a service account, use it to sign bytes.
79+ if (credential is ServiceAccountCredential &&
80+ credential.certificate.projectId == app.options.projectId) {
81+ var certificate = credential.certificate;
82+ var claims = _createCustomTokenPayload (
83+ uid, developerClaims, certificate.clientEmail);
6784
68- var builder = JsonWebSignatureBuilder ()
69- ..jsonContent = claims
70- ..setProtectedHeader ('typ' , 'JWT' )
71- ..addRecipient (certificate.privateKey, algorithm: 'RS256' );
85+ var builder = JsonWebSignatureBuilder ()
86+ ..jsonContent = claims
87+ ..setProtectedHeader ('typ' , 'JWT' )
88+ ..addRecipient (certificate.privateKey, algorithm: 'RS256' );
89+
90+ return builder.build ().toCompactSerialization ();
91+ }
92+
93+ // If the SDK was initialized with a service account email, use it with the IAM service
94+ // to sign bytes.
95+ var serviceAccountId = app.options.serviceAccountId;
96+
97+ if (serviceAccountId == null ) {
98+ /// Find a service account id in the project
99+ var iamApi = iam.IamApi (AuthorizedHttpClient (app));
100+ var accounts = await iamApi.projects.serviceAccounts
101+ .list ('projects/${app .options .projectId }' );
102+
103+ var account = accounts.accounts! .firstWhereOrNull (
104+ (a) => a.email? .startsWith ('firebase-adminsdk-' ) ?? false );
105+ serviceAccountId = account? .email;
106+ }
107+
108+ if (serviceAccountId != null ) {
109+ var claims =
110+ _createCustomTokenPayload (uid, developerClaims, serviceAccountId);
111+ var client = iamcredentials.IAMCredentialsApi (AuthorizedHttpClient (app));
112+
113+ var r = await client.projects.serviceAccounts.signJwt (
114+ iamcredentials.SignJwtRequest (
115+ payload: json.encode (claims),
116+ ),
117+ 'projects/-/serviceAccounts/$serviceAccountId ' );
118+
119+ return r.signedJwt! ;
120+ }
72121
73- return builder.build ().toCompactSerialization ();
122+ throw FirebaseAuthError .invalidServiceAccount (
123+ 'Failed to determine service account ID. Initialize the SDK with service account credentials or specify a service account ID with iam.serviceAccounts.signBlob permission.' );
74124 }
75125}
0 commit comments