Last active
September 15, 2022 15:25
-
-
Save hotdang-ca/597c8f1e360bf4688e47d1115b0cba1c to your computer and use it in GitHub Desktop.
Exchange Google Service Account Private Key for OAuth2.0 Token
This file contains 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
import 'dart:convert'; | |
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; | |
import 'package:http/http.dart' as http; | |
const _PATH_TO_SERVICES_JSON = './secrets/service_account.json'; | |
const _GOOGLE_TOKEN_EXCHANGE_URL = 'https://oauth2.googleapis.com/token'; | |
final _cachedToken = <String, DateTime>{}; | |
/// Gets OAuth2.0 access token from a service account json | |
Future<String> fetchOAuth2Token() async { | |
// first see if the one we have stored isn't expired yet, | |
// and return early | |
if (_token.isNotEmpty) { | |
final token = _token.entries.first; | |
if (token.value.isAfter(DateTime.now())) { | |
print('Not expired yet. Use the cached copy.'); | |
return token.key; | |
} | |
} | |
// Cache doesn't exist or is expired. Generate a new one. | |
// Fetch the Services Json key file. May also be in an ENV, preferably. | |
final secrets = json.decode(await File(_PATH_TO_SERVICES_JSON).readAsString()); | |
// get some values | |
final email = secrets['client_email']; | |
final scopes = [ 'https://www.googleapis.com/auth/firebase.messaging' ]; | |
// generate JWT according to https://developers.google.com/identity/protocols/oauth2/service-account | |
final jwt = JWT({ | |
'iss': email, | |
'scope': scopes.join(' '), | |
'aud': 'https://oauth2.googleapis.com/token', | |
'iat': DateTime.now().millisecondsSinceEpoch ~/ 1000, | |
'exp': DateTime.now().add(Duration(minutes: 60)).millisecondsSinceEpoch ~/ 1000, | |
}); | |
// Sign it with the private key with RSA-256 | |
String token = jwt.sign( | |
RSAPrivateKey(secrets['private_key']), | |
algorithm: JWTAlgorithm.RS256, | |
); | |
// Send to google, hoping for a OAuth2 token back | |
final oauth2Response = await http.post( | |
Uri.parse(_GOOGLE_TOKEN_EXCHANGE_URL), | |
body: { | |
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', | |
'assertion': token, | |
} | |
); | |
final response = jsonDecode(oauth2Response.body); | |
// Pick up some variables for caching | |
final expirationInSeconds = response['expires_in']; | |
final rawToken = response['access_token'].toString(); | |
final tokenType = response['token_type']; | |
// We have some padding bytes. Remove them for brevity. | |
int splitPoint = 0; | |
for (int i = 0; i < rawToken.length; i++) { | |
if (rawToken[i] == '.' && rawToken[i - 1] == '.') { | |
splitPoint = i; | |
break; | |
} | |
} | |
final accessToken = rawToken.substring(0, splitPoint - 1); | |
// store our cached token | |
_cachedToken[accessToken] = DateTime.now().add(Duration(seconds: expirationInSeconds)); | |
// clear expired entries | |
_cachedToken.removeWhere((k, v) => v.isBefore(DateTime.now())); | |
// Return the access token | |
return accessToken; | |
} | |
// use it | |
void main() async { | |
final url = Uri.parse('https://fcm.googleapis.com/v1/projects/<your_project_id>/messages:send'); | |
final payload = <Map<String, Dynamic>>{ | |
"message": { | |
"token": "<your_recipient>", | |
"notification": { | |
"title": "Notification Title", | |
"body": "Notification Body" | |
} | |
} | |
}; | |
final headers = { | |
'Authorization': 'Bearer ${await fetchOAuth2Token()}', | |
'Content-Type': 'application/json', | |
}; | |
final fbmResponse = await http.post( | |
url, | |
headers: headers, | |
body: json.encode(payload), | |
); | |
Map<String, dynamic> responseBody = jsonDecode(fbmResponse.body); | |
if (responseBody['name'] != null) { | |
print(responseBody['name']); | |
} else { | |
print('handle your errors'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment