1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
//!
//! This example showcases the Google OAuth2 process for requesting access to the Google Calendar features
//! and the user's profile.
//!
//! Before running it, you'll need to generate your own Google OAuth2 credentials.
//!
//! In order to run the example call:
//!
//! ```sh
//! GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=yyy cargo run --example google
//! ```
//!
//! ...and follow the instructions.
//!
use oauth2::reqwest;
use oauth2::{basic::BasicClient, StandardRevocableToken, TokenResponse};
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl,
RevocationUrl, Scope, TokenUrl,
};
use url::Url;
use std::env;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
fn main() {
let google_client_id = ClientId::new(
env::var("GOOGLE_CLIENT_ID").expect("Missing the GOOGLE_CLIENT_ID environment variable."),
);
let google_client_secret = ClientSecret::new(
env::var("GOOGLE_CLIENT_SECRET")
.expect("Missing the GOOGLE_CLIENT_SECRET environment variable."),
);
let auth_url = AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string())
.expect("Invalid authorization endpoint URL");
let token_url = TokenUrl::new("https://www.googleapis.com/oauth2/v3/token".to_string())
.expect("Invalid token endpoint URL");
// Set up the config for the Google OAuth2 process.
let client = BasicClient::new(google_client_id)
.set_client_secret(google_client_secret)
.set_auth_uri(auth_url)
.set_token_uri(token_url)
// This example will be running its own server at localhost:8080.
// See below for the server implementation.
.set_redirect_uri(
RedirectUrl::new("http://localhost:8080".to_string()).expect("Invalid redirect URL"),
)
// Google supports OAuth 2.0 Token Revocation (RFC-7009)
.set_revocation_url(
RevocationUrl::new("https://oauth2.googleapis.com/revoke".to_string())
.expect("Invalid revocation endpoint URL"),
);
let http_client = reqwest::blocking::ClientBuilder::new()
// Following redirects opens the client up to SSRF vulnerabilities.
.redirect(reqwest::redirect::Policy::none())
.build()
.expect("Client should build");
// Google supports Proof Key for Code Exchange (PKCE - https://oauth.net/2/pkce/).
// Create a PKCE code verifier and SHA-256 encode it as a code challenge.
let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();
// Generate the authorization URL to which we'll redirect the user.
let (authorize_url, csrf_state) = client
.authorize_url(CsrfToken::new_random)
// This example is requesting access to the "calendar" features and the user's profile.
.add_scope(Scope::new(
"https://www.googleapis.com/auth/calendar".to_string(),
))
.add_scope(Scope::new(
"https://www.googleapis.com/auth/plus.me".to_string(),
))
.set_pkce_challenge(pkce_code_challenge)
.url();
println!("Open this URL in your browser:\n{authorize_url}\n");
let (code, state) = {
// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
// The server will terminate itself after collecting the first code.
let Some(mut stream) = listener.incoming().flatten().next() else {
panic!("listener terminated without accepting a connection");
};
let mut reader = BufReader::new(&stream);
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let code = url
.query_pairs()
.find(|(key, _)| key == "code")
.map(|(_, code)| AuthorizationCode::new(code.into_owned()))
.unwrap();
let state = url
.query_pairs()
.find(|(key, _)| key == "state")
.map(|(_, state)| CsrfToken::new(state.into_owned()))
.unwrap();
let message = "Go back to your terminal :)";
let response = format!(
"HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
message.len(),
message
);
stream.write_all(response.as_bytes()).unwrap();
(code, state)
};
println!("Google returned the following code:\n{}\n", code.secret());
println!(
"Google returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);
// Exchange the code with a token.
let token_response = client
.exchange_code(code)
.set_pkce_verifier(pkce_code_verifier)
.request(&http_client);
println!("Google returned the following token:\n{token_response:?}\n");
// Revoke the obtained token
let token_response = token_response.unwrap();
let token_to_revoke: StandardRevocableToken = match token_response.refresh_token() {
Some(token) => token.into(),
None => token_response.access_token().into(),
};
client
.revoke_token(token_to_revoke)
.unwrap()
.request(&http_client)
.expect("Failed to revoke token");
}
|