Skip to content

Instantly share code, notes, and snippets.

@hotdang-ca
Last active September 15, 2022 15:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hotdang-ca/597c8f1e360bf4688e47d1115b0cba1c to your computer and use it in GitHub Desktop.
Save hotdang-ca/597c8f1e360bf4688e47d1115b0cba1c to your computer and use it in GitHub Desktop.
Exchange Google Service Account Private Key for OAuth2.0 Token
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