Last active
September 6, 2021 04:05
-
-
Save algon-320/30d20e638b4d59acf04bf1b05faee643 to your computer and use it in GitHub Desktop.
Get a OAuth 2.0 access token in Rust (Google APIs)
This file contains hidden or 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
// [dependencies] | |
// jwt = { version = "0.14.0", features = ["openssl"] } | |
// openssl = "0.10.32" | |
// serde_json = "1" | |
// Docs: | |
// https://developers.google.com/identity/protocols/oauth2/service-account | |
use openssl::pkey::{PKey, Private}; | |
fn get_private_key<P>(json_filename: P) -> PKey<Private> | |
where | |
P: AsRef<std::path::Path>, | |
{ | |
let json_bytes = std::fs::read(json_filename).expect("cannot open the JSON"); | |
let json: serde_json::Value = serde_json::from_slice(&json_bytes).expect("invalid JSON"); | |
let pem = json | |
.get("private_key") | |
.expect("invalid credential JSON") | |
.as_str() | |
.unwrap(); | |
PKey::private_key_from_pem(pem.as_bytes()).unwrap() | |
} | |
fn create_jwt(service_account_email: &str, scopes: &[&str], key: PKey<Private>) -> String { | |
let header = jwt::Header { | |
algorithm: jwt::AlgorithmType::Rs256, | |
type_: Some(jwt::header::HeaderType::JsonWebToken), | |
..Default::default() | |
}; | |
use std::time::{SystemTime, UNIX_EPOCH}; | |
let now = SystemTime::now() | |
.duration_since(UNIX_EPOCH) | |
.unwrap() | |
.as_secs(); | |
let claims = jwt::claims::Claims { | |
registered: jwt::claims::RegisteredClaims { | |
issuer: Some(service_account_email.to_owned()), | |
audience: Some("https://www.googleapis.com/oauth2/v4/token".to_owned()), | |
expiration: Some(now + 3600), // 1-hour | |
issued_at: Some(now), | |
..Default::default() | |
}, | |
private: { | |
let mut claims = std::collections::BTreeMap::new(); | |
claims.insert("scope".into(), scopes.join(" ").into()); | |
claims | |
}, | |
}; | |
let digest = openssl::hash::MessageDigest::sha256(); | |
let signing_key = jwt::algorithm::openssl::PKeyWithDigest { digest, key }; | |
use jwt::SignWithKey as _; | |
let token = jwt::Token::new(header, claims) | |
.sign_with_key(&signing_key) | |
.unwrap(); | |
token.as_str().to_owned() | |
} | |
fn main() { | |
let key = get_private_key("./credential.json"); | |
let service_account_email = "[email protected]"; | |
let scope = ["https://www.googleapis.com/auth/firebase.messaging"]; | |
let token = create_jwt(service_account_email, &scope, key); | |
println!( | |
"curl -H 'Content-Type: application/x-www-form-urlencoded' \ | |
-d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \ | |
-d 'assertion={}' \ | |
https://www.googleapis.com/oauth2/v4/token", | |
token | |
); | |
} |
This file contains hidden or 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
// [dependencies] | |
// jwt = { version = "0.14.0", features = ["openssl"] } | |
// openssl = "0.10.32" | |
// serde_json = "1" | |
// reqwest = { version = "0.11.4", features = ["blocking", "json"] } | |
// Ref: https://developers.google.com/identity/protocols/oauth2/service-account | |
use openssl::pkey::{PKey, Private}; | |
fn get_private_key<P>(json_filename: P) -> PKey<Private> | |
where | |
P: AsRef<std::path::Path>, | |
{ | |
let json_bytes = std::fs::read(json_filename).expect("cannot open the JSON"); | |
let json: serde_json::Value = serde_json::from_slice(&json_bytes).expect("invalid JSON"); | |
let pem = json | |
.get("private_key") | |
.expect("invalid credential JSON") | |
.as_str() | |
.unwrap(); | |
PKey::private_key_from_pem(pem.as_bytes()).unwrap() | |
} | |
fn create_jwt(service_account_email: &str, scopes: &[&str], key: PKey<Private>) -> String { | |
let header = jwt::Header { | |
algorithm: jwt::AlgorithmType::Rs256, | |
type_: Some(jwt::header::HeaderType::JsonWebToken), | |
..Default::default() | |
}; | |
use std::time::{SystemTime, UNIX_EPOCH}; | |
let now = SystemTime::now() | |
.duration_since(UNIX_EPOCH) | |
.unwrap() | |
.as_secs(); | |
let claims = jwt::claims::Claims { | |
registered: jwt::claims::RegisteredClaims { | |
issuer: Some(service_account_email.to_owned()), | |
audience: Some("https://www.googleapis.com/oauth2/v4/token".to_owned()), | |
expiration: Some(now + 3600), // 1-hour | |
issued_at: Some(now), | |
..Default::default() | |
}, | |
private: { | |
let mut claims = std::collections::BTreeMap::new(); | |
claims.insert("scope".into(), scopes.join(" ").into()); | |
claims | |
}, | |
}; | |
let digest = openssl::hash::MessageDigest::sha256(); | |
let signing_key = jwt::algorithm::openssl::PKeyWithDigest { digest, key }; | |
use jwt::SignWithKey as _; | |
let token = jwt::Token::new(header, claims) | |
.sign_with_key(&signing_key) | |
.unwrap(); | |
token.as_str().to_owned() | |
} | |
fn push(project_id: &str, access_token: &str, title: &str, text: &str, destination_token: &str) { | |
let client = reqwest::blocking::Client::new(); | |
let req = serde_json::json!({ | |
"message": { | |
"data": { "title": title, "text": text }, | |
"token": destination_token, | |
} | |
}); | |
let resp = client | |
.post(format!( | |
"https://fcm.googleapis.com/v1/projects/{}/messages:send", | |
project_id | |
)) | |
.bearer_auth(&access_token) | |
.json(&req) | |
.send() | |
.expect("Failed to push the message"); | |
if resp.status().is_success() { | |
println!("successfully pushed!"); | |
} else { | |
println!("{}: {:?}", resp.status(), resp.text()); | |
} | |
} | |
fn main() { | |
let project_id = "yyyyyyyy"; | |
let service_account_email = "[email protected]"; | |
let device_token = "zzzzzzzz"; | |
let key = get_private_key("./credential.json"); | |
let scope = ["https://www.googleapis.com/auth/firebase.messaging"]; | |
let jwt = create_jwt(service_account_email, &scope, key); | |
// println!( | |
// "curl -H 'Content-Type: application/x-www-form-urlencoded' \ | |
// -d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \ | |
// -d 'assertion={}' \ | |
// https://www.googleapis.com/oauth2/v4/token", | |
// jwt | |
// ); | |
let client = reqwest::blocking::Client::new(); | |
let response = client | |
.post("https://www.googleapis.com/oauth2/v4/token") | |
.form(&[ | |
("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"), | |
("assertion", jwt.as_str()), | |
]) | |
.send() | |
.expect("Failed to get access token"); | |
let access_token = { | |
let response_json: serde_json::Value = response.json().unwrap(); | |
response_json | |
.get("access_token") | |
.unwrap() | |
.as_str() | |
.unwrap() | |
.to_owned() | |
}; | |
push( | |
project_id, | |
&access_token, | |
"Hello, World", // data.title | |
"This is a sample notification.", // data.text | |
device_token, | |
); | |
// Android side (Kotlin): | |
// override fun onMessageReceived(message: RemoteMessage) { | |
// message.data?.let { data -> | |
// val title = data["title"] ?: "(empty)" | |
// val text = data["text"] ?: "(empty)" | |
// // doSomethingCool(title, text) | |
// } | |
// } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment